diff --git a/plugins/analysis/packet/metric_heartbeat.go b/plugins/analysis/packet/metric_heartbeat.go
new file mode 100644
index 0000000000000000000000000000000000000000..dfd291b48de2c9d0dee31ffb031afe233aeaf3d0
--- /dev/null
+++ b/plugins/analysis/packet/metric_heartbeat.go
@@ -0,0 +1,92 @@
+package packet
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/gob"
+	"errors"
+
+	"github.com/iotaledger/hive.go/protocol/message"
+	"github.com/iotaledger/hive.go/protocol/tlv"
+)
+
+var (
+	// ErrInvalidMetricHeartbeat is returned for invalid Metric heartbeats.
+	ErrInvalidMetricHeartbeat = errors.New("invalid Metric heartbeat")
+)
+
+var (
+	// MetricHeartbeatMessageDefinition defines a metric heartbeat message's format.
+	MetricHeartbeatMessageDefinition = &message.Definition{
+		ID:             MessageTypeMetricHeartbeat,
+		MaxBytesLength: 65535,
+		VariableLength: true,
+	}
+)
+
+// MetricHeartbeat represents a metric heartbeat packet.
+type MetricHeartbeat struct {
+	// The ID of the node who sent the heartbeat.
+	// Must be contained when a heartbeat is serialized.
+	OwnID       []byte
+	OS          string
+	Arch        string
+	NumCPU      int
+	CPUUsage    float64
+	MemoryUsage uint64
+}
+
+// ParseMetricHeartbeat parses a slice of bytes (serialized packet) into a Metric heartbeat.
+func ParseMetricHeartbeat(data []byte) (*MetricHeartbeat, error) {
+	hb := &MetricHeartbeat{}
+
+	buf := new(bytes.Buffer)
+	_, err := buf.Write(data)
+	if err != nil {
+		return nil, err
+	}
+
+	decoder := gob.NewDecoder(buf)
+	err = decoder.Decode(hb)
+	if err != nil {
+		return nil, err
+	}
+
+	return hb, nil
+}
+
+// Bytes return the Metric heartbeat encoded as bytes
+func (hb MetricHeartbeat) Bytes() ([]byte, error) {
+	buf := new(bytes.Buffer)
+	encoder := gob.NewEncoder(buf)
+	err := encoder.Encode(hb)
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+// NewMetricHeartbeatMessage serializes the given Metric heartbeat into a byte slice and adds a tlv header to the packet.
+// message = tlv header + serialized packet
+func NewMetricHeartbeatMessage(hb *MetricHeartbeat) ([]byte, error) {
+	packet, err := hb.Bytes()
+	if err != nil {
+		return nil, err
+	}
+
+	// calculate total needed bytes based on packet
+	packetSize := len(packet)
+
+	// create a buffer for tlv header plus the packet
+	buf := bytes.NewBuffer(make([]byte, 0, tlv.HeaderMessageDefinition.MaxBytesLength+uint16(packetSize)))
+	// write tlv header into buffer
+	if err := tlv.WriteHeader(buf, MessageTypeMetricHeartbeat, uint16(packetSize)); err != nil {
+		return nil, err
+	}
+	// write serialized packet bytes into the buffer
+	if err := binary.Write(buf, binary.BigEndian, packet); err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
diff --git a/plugins/analysis/packet/metric_heartbeat_test.go b/plugins/analysis/packet/metric_heartbeat_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2401da2a91ce57f37f094c41c460f12ba3c4d740
--- /dev/null
+++ b/plugins/analysis/packet/metric_heartbeat_test.go
@@ -0,0 +1,58 @@
+package packet
+
+import (
+	"crypto/sha256"
+	"runtime"
+	"testing"
+	"time"
+
+	"github.com/iotaledger/hive.go/protocol/message"
+	"github.com/iotaledger/hive.go/protocol/tlv"
+	"github.com/shirou/gopsutil/cpu"
+	"github.com/stretchr/testify/require"
+)
+
+var nodeID = sha256.Sum256([]byte{'A'})
+
+func testMetricHeartbeat() *MetricHeartbeat {
+	return &MetricHeartbeat{
+		OwnID:  nodeID[:],
+		OS:     runtime.GOOS,
+		Arch:   runtime.GOARCH,
+		NumCPU: runtime.NumCPU(),
+		CPUUsage: func() (p float64) {
+			percent, err := cpu.Percent(time.Second, false)
+			if err == nil {
+				p = percent[0]
+			}
+			return
+		}(),
+		MemoryUsage: func() uint64 {
+			var m runtime.MemStats
+			runtime.ReadMemStats(&m)
+			return m.Alloc
+		}(),
+	}
+}
+
+func TestMetricHeartbeat(t *testing.T) {
+	hb := testMetricHeartbeat()
+
+	packet, err := hb.Bytes()
+	require.NoError(t, err)
+
+	hbParsed, err := ParseMetricHeartbeat(packet)
+	require.NoError(t, err)
+
+	require.Equal(t, hb, hbParsed)
+
+	tlvHeaderLength := int(tlv.HeaderMessageDefinition.MaxBytesLength)
+	msg, err := NewMetricHeartbeatMessage(hb)
+	require.NoError(t, err)
+
+	require.Equal(t, MessageTypeMetricHeartbeat, message.Type(msg[0]))
+
+	hbParsed, err = ParseMetricHeartbeat(msg[tlvHeaderLength:])
+	require.NoError(t, err)
+	require.Equal(t, hb, hbParsed)
+}
diff --git a/plugins/analysis/packet/types.go b/plugins/analysis/packet/types.go
index 76016a6dd1527e1f2d8a9266817c70f4f8e991ad..e18c90a2d626d520b530ed0f407842fed9c85c36 100644
--- a/plugins/analysis/packet/types.go
+++ b/plugins/analysis/packet/types.go
@@ -7,4 +7,6 @@ const (
 	MessageTypeHeartbeat message.Type = iota + 1
 	// MessageTypeFPCHeartbeat defines the FPC Heartbeat msg type
 	MessageTypeFPCHeartbeat
+	// MessageTypeMetricHeartbeat defines the Metric Heartbeat msg type
+	MessageTypeMetricHeartbeat
 )