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 )