├── go.mod ├── .gitignore ├── _test ├── flow_sample.dump ├── host_sample.dump ├── flow_sample_3.dump ├── counter_sample.dump ├── flow_samples_2.dump └── event_discarded_packet.dump ├── record.go ├── fuzz.go ├── .github └── workflows │ └── go.yml ├── datagram.go ├── decode_benchmark_test.go ├── sample.go ├── LICENSE ├── README.md ├── flow_record_encode_test.go ├── encoder.go ├── binary.go ├── flow_sample_encode_test.go ├── counter_record_encode_test.go ├── decoder.go ├── encode_test.go ├── counter_sample_encode_test.go ├── flow_record.go ├── event_discarded_packet.go ├── counter_sample.go ├── flow_sample.go ├── decode_test.go └── counter_record.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Cistern/sflow 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sflow 2 | _collector/ 3 | workdir/ 4 | sflow-fuzz.zip 5 | -------------------------------------------------------------------------------- /_test/flow_sample.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cistern/sflow/HEAD/_test/flow_sample.dump -------------------------------------------------------------------------------- /_test/host_sample.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cistern/sflow/HEAD/_test/host_sample.dump -------------------------------------------------------------------------------- /_test/flow_sample_3.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cistern/sflow/HEAD/_test/flow_sample_3.dump -------------------------------------------------------------------------------- /_test/counter_sample.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cistern/sflow/HEAD/_test/counter_sample.dump -------------------------------------------------------------------------------- /_test/flow_samples_2.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cistern/sflow/HEAD/_test/flow_samples_2.dump -------------------------------------------------------------------------------- /_test/event_discarded_packet.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cistern/sflow/HEAD/_test/event_discarded_packet.dump -------------------------------------------------------------------------------- /record.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | var ( 9 | ErrEncodingRecord = errors.New("sflow: failed to encode record") 10 | ErrDecodingRecord = errors.New("sflow: failed to decode record") 11 | ) 12 | 13 | type Record interface { 14 | RecordType() int 15 | encode(w io.Writer) error 16 | } 17 | -------------------------------------------------------------------------------- /fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package sflow 4 | 5 | import "bytes" 6 | 7 | // Fuzz function to be used with https://github.com/dvyukov/go-fuzz 8 | func Fuzz(data []byte) int { 9 | 10 | sflow := NewDecoder(bytes.NewReader(data)) 11 | 12 | if _, err := sflow.Decode(); err != nil { 13 | return 0 14 | } 15 | 16 | return 1 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | pull_request: 7 | branches: ["master"] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.20" 19 | 20 | - name: Test 21 | run: go test -v ./... 22 | -------------------------------------------------------------------------------- /datagram.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | type Datagram struct { 9 | Version uint32 `json:"version"` 10 | IpVersion uint32 `json:"ipVersion"` 11 | IpAddress net.IP `json:"ipAddress"` 12 | SubAgentId uint32 `json:"subAgentId"` 13 | SequenceNumber uint32 `json:"sequenceNumber"` 14 | Uptime uint32 `json:"uptime"` 15 | NumSamples uint32 `json:"numSamples"` 16 | Samples []Sample `json:"samples"` 17 | } 18 | 19 | func (d Datagram) String() string { 20 | type X Datagram 21 | x := X(d) 22 | return fmt.Sprintf("Datagram: %+v", x) 23 | } 24 | -------------------------------------------------------------------------------- /decode_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkFlow1Sample(b *testing.B) { 9 | f, err := os.Open("_test/flow_sample.dump") 10 | if err != nil { 11 | b.Fatal(err) 12 | } 13 | 14 | d := NewDecoder(f) 15 | 16 | for i := 0; i < b.N; i++ { 17 | d.Decode() 18 | } 19 | 20 | f.Close() 21 | } 22 | 23 | func BenchmarkCounterSample(b *testing.B) { 24 | f, err := os.Open("_test/counter_sample.dump") 25 | if err != nil { 26 | b.Fatal(err) 27 | } 28 | 29 | d := NewDecoder(f) 30 | 31 | for i := 0; i < b.N; i++ { 32 | d.Decode() 33 | } 34 | 35 | f.Close() 36 | } 37 | -------------------------------------------------------------------------------- /sample.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | const ( 10 | TypeFlowSample = 1 11 | TypeCounterSample = 2 12 | TypeExpandedFlowSample = 3 13 | TypeExpandedCounterSample = 4 14 | TypeEventDiscardedPacket = 5 15 | ) 16 | 17 | var ( 18 | ErrUnknownSampleType = errors.New("sflow: Unknown sample type") 19 | ) 20 | 21 | type Sample interface { 22 | SampleType() int 23 | GetRecords() []Record 24 | encode(w io.Writer) error 25 | } 26 | 27 | func decodeSample(r io.ReadSeeker) (Sample, error) { 28 | format, length, err := uint32(0), uint32(0), error(nil) 29 | 30 | err = binary.Read(r, binary.BigEndian, &format) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | err = binary.Read(r, binary.BigEndian, &length) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | switch format { 41 | case TypeCounterSample: 42 | return decodeCounterSample(r) 43 | 44 | case TypeFlowSample: 45 | return decodeFlowSample(r) 46 | 47 | case TypeEventDiscardedPacket: 48 | return decodEventDiscardedPacket(r) 49 | 50 | default: 51 | _, err = r.Seek(int64(length), 1) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return nil, ErrUnknownSampleType 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Preetam Jinka 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sflow [![Circle CI](https://circleci.com/gh/Cistern/sflow.svg?style=svg&circle-token=e6b83ff5665619ad7615dd1e472c958f542cca3c)](https://circleci.com/gh/Cistern/sflow) [![GoDoc](https://godoc.org/github.com/PreetamJinka/sflow?status.svg)](https://godoc.org/github.com/PreetamJinka/sflow) [![BSD License](https://img.shields.io/pypi/l/Django.svg)](https://github.com/PreetamJinka/sflow/blob/master/LICENSE) 2 | ==== 3 | 4 | An [sFlow](http://sflow.org/) v5 encoding and decoding package for Go. 5 | 6 | Usage 7 | --- 8 | 9 | ```go 10 | // Create a new decoder that reads from an io.Reader. 11 | d := sflow.NewDecoder(r) 12 | 13 | // Attempt to decode an sFlow datagram. 14 | dgram, err := d.Decode() 15 | if err != nil { 16 | log.Println(err) 17 | return 18 | } 19 | 20 | for _, sample := range dgram.Samples { 21 | // Sample is an interface type 22 | if sample.SampleType() == sflow.TypeCounterSample { 23 | counterSample := sample.(sflow.CounterSample) 24 | 25 | for _, record := range counterSample.Records { 26 | // While there is a record.RecordType() method, 27 | // you can always check types directly. 28 | switch record.(type) { 29 | case sflow.HostDiskCounters: 30 | fmt.Printf("Max used percent of disk space is %d.\n", 31 | record.MaxUsedPercent) 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | API guarantees 39 | --- 40 | API stability is *not guaranteed*. Vendoring or using a dependency manager is suggested. 41 | 42 | Reporting issues 43 | --- 44 | Bug reports are greatly appreciated. Please provide raw datagram dumps when possible. 45 | 46 | License 47 | --- 48 | BSD (see LICENSE) 49 | -------------------------------------------------------------------------------- /flow_record_encode_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestEncodeDecodeRawPacketFlowRecord(t *testing.T) { 10 | rec := RawPacketFlow{ 11 | Protocol: 1, 12 | FrameLength: 318, 13 | Stripped: 4, 14 | HeaderSize: 128, 15 | Header: []byte{0x00, 0xD0, 0x01, 0xFF, 0x58, 16 | 0x00, 0x00, 0x16, 0x3C, 0xC2, 0xA9, 0xAB, 17 | 0x08, 0x00, 0x45, 0x00, 0x01, 0x2C, 0x00, 18 | 0x00, 0x40, 0x00, 0x40, 0x11, 0xD1, 0x58, 19 | 0xC7, 0x3A, 0xA1, 0x96, 0xC5, 0xA1, 0x39, 20 | 0xF6, 0xC8, 0xD5, 0x26, 0x00, 0x01, 0x18, 21 | 0xA6, 0x17, 0x64, 0x31, 0x3A, 0x72, 0x64, 22 | 0x32, 0x3A, 0x69, 0x64, 0x32, 0x30, 0x3A, 23 | 0x6B, 0x96, 0x8B, 0xCA, 0x4A, 0xC0, 0xB5, 24 | 0xCF, 0x10, 0x3A, 0xD6, 0xBF, 0x8D, 0xD7, 25 | 0x34, 0x01, 0x46, 0x51, 0xB7, 0xFA, 0x35, 26 | 0x3A, 0x6E, 0x6F, 0x64, 0x65, 0x73, 0x32, 27 | 0x30, 0x38, 0x3A, 0x61, 0x4A, 0xB8, 0x64, 28 | 0x54, 0xEE, 0x85, 0x5F, 0x13, 0x9A, 0x20, 29 | 0x96, 0xE9, 0x83, 0xFF, 0xCF, 0xF4, 0xD0, 30 | 0xC5, 0xA5, 0xDE, 0x67, 0x0A, 0x8F, 0xDB, 31 | 0x1D, 0x61, 0x4A, 0x78, 0x12, 0x83, 0x31, 32 | 0xA3, 0x77, 0x86, 0x68, 0x5E, 0x1C, 0x24, 33 | 0xCE, 0x33, 0x19, 0xDE, 34 | }, 35 | } 36 | 37 | b := &bytes.Buffer{} 38 | 39 | err := rec.encode(b) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | // Skip the header section. It's 8 bytes. 45 | var headerBytes [8]byte 46 | 47 | _, err = b.Read(headerBytes[:]) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | decoded, err := decodeRawPacketFlow(b) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | if !reflect.DeepEqual(rec, decoded) { 58 | t.Errorf("expected\n%+#v\n, got\n%+#v", rec, decoded) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /encoder.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "net" 8 | ) 9 | 10 | var ( 11 | ErrNoSamplesProvided = errors.New("sflow: no samples provided for encoding") 12 | ) 13 | 14 | type Encoder struct { 15 | ip net.IP 16 | subAgentId uint32 17 | sequenceNum uint32 18 | Uptime uint32 19 | } 20 | 21 | // NewEncoder returns a new sFlow encoder. 22 | func NewEncoder(source net.IP, subAgentId uint32, initialSequenceNumber uint32) *Encoder { 23 | return &Encoder{ 24 | ip: source, 25 | subAgentId: subAgentId, 26 | sequenceNum: initialSequenceNumber, 27 | } 28 | } 29 | 30 | // Encode encodes an sFlow v5 datagram with the given samples and 31 | // writes the packet to w. 32 | func (e *Encoder) Encode(w io.Writer, samples []Sample) error { 33 | if len(samples) == 0 { 34 | return ErrNoSamplesProvided 35 | } 36 | 37 | var err error 38 | 39 | // sFlow v5 40 | err = binary.Write(w, binary.BigEndian, uint32(5)) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // Check IP version 46 | ipVersion := uint32(1) 47 | ipBytes := []byte(e.ip.To4()) 48 | if ipBytes == nil { 49 | ipVersion = 2 50 | ipBytes = []byte(e.ip.To16()) 51 | } 52 | 53 | err = binary.Write(w, binary.BigEndian, ipVersion) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | _, err = w.Write(ipBytes) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | err = binary.Write(w, binary.BigEndian, e.subAgentId) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | err = binary.Write(w, binary.BigEndian, e.sequenceNum) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | err = binary.Write(w, binary.BigEndian, e.Uptime) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | err = binary.Write(w, binary.BigEndian, uint32(len(samples))) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | for _, sample := range samples { 84 | err = sample.encode(w) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | 90 | e.sequenceNum++ 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /binary.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "math" 7 | ) 8 | 9 | var ( 10 | ErrInvalidSliceLength = errors.New("sflow: invalid slice length") 11 | ErrInvalidFieldType = errors.New("sflow: field type") 12 | ) 13 | 14 | // readFields reads big-endian encoded numbers from b into 15 | // elements of fields, which should be pointers to numbers, 16 | // e.g. *uint32 or *float32. 17 | func readFields(b []byte, fields []interface{}) error { 18 | for len(b) > 0 && len(fields) > 0 { 19 | field := fields[0] 20 | size := 0 21 | 22 | switch field.(type) { 23 | case *int8, *uint8: 24 | // 1 byte 25 | if len(b) < 1 { 26 | return ErrInvalidSliceLength 27 | } 28 | size = 1 29 | 30 | switch field := field.(type) { 31 | case *int8: 32 | *field = int8(b[0]) 33 | case *uint8: 34 | *field = b[0] 35 | } 36 | 37 | case *int16, *uint16: 38 | // 2 bytes 39 | if len(b) < 2 { 40 | return ErrInvalidSliceLength 41 | } 42 | size = 2 43 | 44 | n := binary.BigEndian.Uint16(b[:size]) 45 | 46 | switch field := field.(type) { 47 | case *int16: 48 | *field = int16(n) 49 | case *uint16: 50 | *field = n 51 | } 52 | 53 | case *float32, *int32, *uint32: 54 | // 4 bytes 55 | if len(b) < 4 { 56 | return ErrInvalidSliceLength 57 | } 58 | size = 4 59 | 60 | n := binary.BigEndian.Uint32(b[:size]) 61 | 62 | switch field := field.(type) { 63 | case *float32: 64 | *field = math.Float32frombits(n) 65 | case *int32: 66 | *field = int32(n) 67 | case *uint32: 68 | *field = n 69 | } 70 | 71 | case *float64, *int64, *uint64: 72 | // 8 bytes 73 | if len(b) < 8 { 74 | return ErrInvalidSliceLength 75 | } 76 | size = 8 77 | 78 | n := binary.BigEndian.Uint64(b[:size]) 79 | 80 | switch field := field.(type) { 81 | case *float64: 82 | *field = math.Float64frombits(n) 83 | case *int64: 84 | *field = int64(n) 85 | case *uint64: 86 | *field = n 87 | } 88 | 89 | default: 90 | return ErrInvalidFieldType 91 | } 92 | 93 | b = b[size:] 94 | fields = fields[1:] 95 | } 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /flow_sample_encode_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestDecodeEncodeAndDecodeFlowSample(t *testing.T) { 10 | f, err := os.Open("_test/flow_sample.dump") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | d := NewDecoder(f) 16 | 17 | dgram, err := d.Decode() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | if dgram.Version != 5 { 23 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 24 | } 25 | 26 | if int(dgram.NumSamples) != len(dgram.Samples) { 27 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 28 | } 29 | 30 | if len(dgram.Samples) != 1 { 31 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 32 | } 33 | 34 | sample, ok := dgram.Samples[0].(*FlowSample) 35 | if !ok { 36 | t.Fatalf("expected a FlowSample, got %T", dgram.Samples[0]) 37 | } 38 | 39 | buf := &bytes.Buffer{} 40 | 41 | err = sample.encode(buf) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | // We need to skip the first 8 bytes. That's the header. 47 | var skip [8]byte 48 | buf.Read(skip[:]) 49 | 50 | // bytes.Buffer is not an io.ReadSeeker. bytes.Reader is. 51 | decodedSample, err := decodeFlowSample(bytes.NewReader(buf.Bytes())) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | sample, ok = decodedSample.(*FlowSample) 57 | if !ok { 58 | t.Fatalf("expected a FlowSample, got %T", decodedSample) 59 | } 60 | 61 | if len(sample.Records) != 2 { 62 | t.Fatalf("expected 2 records, got %d", len(sample.Records)) 63 | } 64 | 65 | rec, ok := sample.Records[0].(RawPacketFlow) 66 | if !ok { 67 | t.Fatalf("expected a RawPacketFlowRecords, got %T", sample.Records[0]) 68 | } 69 | 70 | if rec.Protocol != 1 { 71 | t.Errorf("expected Protocol to be 1, got %d", rec.Protocol) 72 | } 73 | 74 | if rec.FrameLength != 318 { 75 | t.Errorf("expected FrameLength to be 318, got %d", rec.FrameLength) 76 | } 77 | 78 | if rec.Stripped != 4 { 79 | t.Errorf("expected FrameLength to be 4, got %d", rec.Stripped) 80 | } 81 | 82 | if rec.HeaderSize != 128 { 83 | t.Errorf("expected FrameLength to be 128, got %d", rec.HeaderSize) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /counter_record_encode_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestEncodeDecodeGenericInterfaceCountersRecord(t *testing.T) { 9 | rec := GenericInterfaceCounters{ 10 | Index: 9, 11 | Type: 6, 12 | Speed: 100000000, 13 | Direction: 1, 14 | Status: 3, 15 | InOctets: 79282473, 16 | InUnicastPackets: 329128, 17 | InMulticastPackets: 0, 18 | InBroadcastPackets: 1493, 19 | InDiscards: 0, 20 | InErrors: 0, 21 | InUnknownProtocols: 0, 22 | OutOctets: 764247430, 23 | OutUnicastPackets: 9470970, 24 | OutMulticastPackets: 780342, 25 | OutBroadcastPackets: 877721, 26 | OutDiscards: 0, 27 | OutErrors: 0, 28 | PromiscuousMode: 1, 29 | } 30 | 31 | b := &bytes.Buffer{} 32 | 33 | err := rec.encode(b) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | // Skip the header section. It's 8 bytes. 39 | var headerBytes [8]byte 40 | 41 | _, err = b.Read(headerBytes[:]) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | decoded, err := decodeGenericInterfaceCountersRecord(b, uint32(b.Len())) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if decoded != rec { 52 | t.Errorf("expected\n%+#v\n, got\n%+#v", rec, decoded) 53 | } 54 | } 55 | 56 | func TestEncodeDecodeHostCPUCountersRecord(t *testing.T) { 57 | rec := HostCPUCounters{ 58 | Load1m: 0.1, 59 | Load5m: 0.2, 60 | Load15m: 0.3, 61 | ProcessesRunning: 4, 62 | ProcessesTotal: 5, 63 | NumCPU: 6, 64 | SpeedCPU: 7, 65 | Uptime: 8, 66 | 67 | CPUUser: 9, 68 | CPUNice: 10, 69 | CPUSys: 11, 70 | CPUIdle: 12, 71 | CPUWio: 13, 72 | CPUIntr: 14, 73 | CPUSoftIntr: 15, 74 | Interrupts: 16, 75 | ContextSwitches: 17, 76 | 77 | CPUSteal: 18, 78 | CPUGuest: 19, 79 | CPUGuestNice: 20, 80 | } 81 | 82 | b := &bytes.Buffer{} 83 | 84 | err := rec.encode(b) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | // Skip the header section. It's 8 bytes. 90 | var headerBytes [8]byte 91 | 92 | _, err = b.Read(headerBytes[:]) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | 97 | decoded, err := decodeHostCPUCountersRecord(b, uint32(b.Len())) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | if decoded != rec { 103 | t.Errorf("expected\n%+#v\n, got\n%+#v", rec, decoded) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /decoder.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | const ( 10 | // MaximumRecordLength defines the maximum length acceptable for decoded records. 11 | // This maximum prevents from excessive memory allocation. 12 | // The value is derived from MAX_PKT_SIZ 65536 in the reference sFlow implementation 13 | // https://github.com/sflow/sflowtool/blob/bd3df6e11bdf/src/sflowtool.c#L4313. 14 | MaximumRecordLength = 65536 15 | 16 | // MaximumHeaderLength defines the maximum length acceptable for decoded flow samples. 17 | // This maximum prevents from excessive memory allocation. 18 | // The value is set to maximum transmission unit (MTU), as the header of a network packet 19 | // may not exceed the MTU. 20 | MaximumHeaderLength = 1500 21 | ) 22 | 23 | var ErrUnsupportedDatagramVersion = errors.New("sflow: unsupported datagram version") 24 | 25 | type Decoder struct { 26 | reader io.ReadSeeker 27 | } 28 | 29 | func NewDecoder(r io.ReadSeeker) *Decoder { 30 | return &Decoder{ 31 | reader: r, 32 | } 33 | } 34 | 35 | func (d *Decoder) Use(r io.ReadSeeker) { 36 | d.reader = r 37 | } 38 | 39 | func (d *Decoder) Decode() (*Datagram, error) { 40 | // Decode headers first 41 | dgram := &Datagram{} 42 | var err error 43 | 44 | err = binary.Read(d.reader, binary.BigEndian, &dgram.Version) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | if dgram.Version != 5 { 50 | return nil, ErrUnsupportedDatagramVersion 51 | } 52 | 53 | err = binary.Read(d.reader, binary.BigEndian, &dgram.IpVersion) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | ipLen := 4 59 | if dgram.IpVersion == 2 { 60 | ipLen = 16 61 | } 62 | 63 | ipBuf := make([]byte, ipLen) 64 | _, err = d.reader.Read(ipBuf) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | dgram.IpAddress = ipBuf 70 | 71 | err = binary.Read(d.reader, binary.BigEndian, &dgram.SubAgentId) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | err = binary.Read(d.reader, binary.BigEndian, &dgram.SequenceNumber) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | err = binary.Read(d.reader, binary.BigEndian, &dgram.Uptime) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | err = binary.Read(d.reader, binary.BigEndian, &dgram.NumSamples) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | for i := dgram.NumSamples; i > 0; i-- { 92 | sample, err := decodeSample(d.reader) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | dgram.Samples = append(dgram.Samples, sample) 98 | } 99 | 100 | return dgram, nil 101 | } 102 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestDecodeAndEncodeGenericEthernetCounterDatagram(t *testing.T) { 10 | f, err := os.Open("_test/counter_sample.dump") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | d := NewDecoder(f) 16 | 17 | dgram, err := d.Decode() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | buf := &bytes.Buffer{} 23 | enc := NewEncoder(dgram.IpAddress, dgram.SubAgentId, dgram.SequenceNumber) 24 | enc.Encode(buf, dgram.Samples) 25 | 26 | d.Use(bytes.NewReader(buf.Bytes())) 27 | 28 | dgram, err = d.Decode() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | if dgram.Version != 5 { 34 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 35 | } 36 | 37 | if int(dgram.NumSamples) != len(dgram.Samples) { 38 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 39 | } 40 | 41 | if len(dgram.Samples) != 1 { 42 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 43 | } 44 | 45 | sample, ok := dgram.Samples[0].(*CounterSample) 46 | if !ok { 47 | t.Fatalf("expected a CounterSample, got %T", dgram.Samples[0]) 48 | } 49 | 50 | if len(sample.Records) != 2 { 51 | t.Fatalf("expected 2 records, got %d", len(sample.Records)) 52 | } 53 | 54 | ethCounters, ok := sample.Records[0].(EthernetCounters) 55 | if !ok { 56 | t.Fatalf("expected a EthernetCounters record, got %T", sample.Records[0]) 57 | } 58 | 59 | expectedEthCountersRec := EthernetCounters{} 60 | if ethCounters != expectedEthCountersRec { 61 | t.Errorf("expected\n%#v, got\n%#v", expectedEthCountersRec, ethCounters) 62 | } 63 | 64 | genericInterfaceCounters, ok := sample.Records[1].(GenericInterfaceCounters) 65 | if !ok { 66 | t.Fatalf("expected a GenericInterfaceCounters record, got %T", sample.Records[1]) 67 | } 68 | 69 | expectedGenericInterfaceCounters := GenericInterfaceCounters{ 70 | Index: 9, 71 | Type: 6, 72 | Speed: 100000000, 73 | Direction: 1, 74 | Status: 3, 75 | InOctets: 79282473, 76 | InUnicastPackets: 329128, 77 | InMulticastPackets: 0, 78 | InBroadcastPackets: 1493, 79 | InDiscards: 0, 80 | InErrors: 0, 81 | InUnknownProtocols: 0, 82 | OutOctets: 764247430, 83 | OutUnicastPackets: 9470970, 84 | OutMulticastPackets: 780342, 85 | OutBroadcastPackets: 877721, 86 | OutDiscards: 0, 87 | OutErrors: 0, 88 | PromiscuousMode: 1, 89 | } 90 | 91 | if genericInterfaceCounters != expectedGenericInterfaceCounters { 92 | t.Errorf("expected\n%#v, got\n%#v", expectedGenericInterfaceCounters, genericInterfaceCounters) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /counter_sample_encode_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestDecodeEncodeAndDecodeCounterSample(t *testing.T) { 10 | f, err := os.Open("_test/counter_sample.dump") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | d := NewDecoder(f) 16 | 17 | dgram, err := d.Decode() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | if dgram.Version != 5 { 23 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 24 | } 25 | 26 | if int(dgram.NumSamples) != len(dgram.Samples) { 27 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 28 | } 29 | 30 | if len(dgram.Samples) != 1 { 31 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 32 | } 33 | 34 | sample, ok := dgram.Samples[0].(*CounterSample) 35 | if !ok { 36 | t.Fatalf("expected a CounterSample, got %T", dgram.Samples[0]) 37 | } 38 | 39 | buf := &bytes.Buffer{} 40 | 41 | err = sample.encode(buf) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | // We need to skip the first 8 bytes. That's the header. 47 | var skip [8]byte 48 | buf.Read(skip[:]) 49 | 50 | // bytes.Buffer is not an io.ReadSeeker. bytes.Reader is. 51 | decodedSample, err := decodeCounterSample(bytes.NewReader(buf.Bytes())) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | sample, ok = decodedSample.(*CounterSample) 57 | if !ok { 58 | t.Fatalf("expected a CounterSample, got %T", decodedSample) 59 | } 60 | 61 | if len(sample.Records) != 2 { 62 | t.Fatalf("expected 2 records, got %d", len(sample.Records)) 63 | } 64 | 65 | ethCounters, ok := sample.Records[0].(EthernetCounters) 66 | if !ok { 67 | t.Fatalf("expected a EthernetCounters record, got %T", sample.Records[0]) 68 | } 69 | 70 | expectedEthCountersRec := EthernetCounters{} 71 | if ethCounters != expectedEthCountersRec { 72 | t.Errorf("expected\n%#v, got\n%#v", expectedEthCountersRec, ethCounters) 73 | } 74 | 75 | genericInterfaceCounters, ok := sample.Records[1].(GenericInterfaceCounters) 76 | if !ok { 77 | t.Fatalf("expected a GenericInterfaceCounters record, got %T", sample.Records[1]) 78 | } 79 | 80 | expectedGenericInterfaceCounters := GenericInterfaceCounters{ 81 | Index: 9, 82 | Type: 6, 83 | Speed: 100000000, 84 | Direction: 1, 85 | Status: 3, 86 | InOctets: 79282473, 87 | InUnicastPackets: 329128, 88 | InMulticastPackets: 0, 89 | InBroadcastPackets: 1493, 90 | InDiscards: 0, 91 | InErrors: 0, 92 | InUnknownProtocols: 0, 93 | OutOctets: 764247430, 94 | OutUnicastPackets: 9470970, 95 | OutMulticastPackets: 780342, 96 | OutBroadcastPackets: 877721, 97 | OutDiscards: 0, 98 | OutErrors: 0, 99 | PromiscuousMode: 1, 100 | } 101 | 102 | if genericInterfaceCounters != expectedGenericInterfaceCounters { 103 | t.Errorf("expected\n%#v, got\n%#v", expectedGenericInterfaceCounters, genericInterfaceCounters) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /flow_record.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | // RawPacketFlow is a raw Ethernet header flow record. 10 | type RawPacketFlow struct { 11 | Protocol uint32 12 | FrameLength uint32 13 | Stripped uint32 14 | HeaderSize uint32 15 | Header []byte 16 | } 17 | 18 | func (f RawPacketFlow) String() string { 19 | type X RawPacketFlow 20 | x := X(f) 21 | return fmt.Sprintf("RawPacketFlow: %+v", x) 22 | } 23 | 24 | // ExtendedSwitchFlow is an extended switch flow record. 25 | type ExtendedSwitchFlow struct { 26 | SourceVlan uint32 27 | SourcePriority uint32 28 | DestinationVlan uint32 29 | DestinationPriority uint32 30 | } 31 | 32 | func (f ExtendedSwitchFlow) String() string { 33 | type X ExtendedSwitchFlow 34 | x := X(f) 35 | return fmt.Sprintf("ExtendedSwitchFlow: %+v", x) 36 | } 37 | 38 | // RecordType returns the type of flow record. 39 | func (f RawPacketFlow) RecordType() int { 40 | return TypeRawPacketFlowRecord 41 | } 42 | 43 | func decodeRawPacketFlow(r io.Reader) (RawPacketFlow, error) { 44 | f := RawPacketFlow{} 45 | 46 | var err error 47 | 48 | err = binary.Read(r, binary.BigEndian, &f.Protocol) 49 | if err != nil { 50 | return f, err 51 | } 52 | 53 | err = binary.Read(r, binary.BigEndian, &f.FrameLength) 54 | if err != nil { 55 | return f, err 56 | } 57 | 58 | err = binary.Read(r, binary.BigEndian, &f.Stripped) 59 | if err != nil { 60 | return f, err 61 | } 62 | 63 | err = binary.Read(r, binary.BigEndian, &f.HeaderSize) 64 | if err != nil { 65 | return f, err 66 | } 67 | if f.HeaderSize > MaximumHeaderLength { 68 | return f, fmt.Errorf("sflow: header length more than %d: %d", 69 | MaximumHeaderLength, f.HeaderSize) 70 | } 71 | 72 | padding := (4 - int(f.HeaderSize)) % 4 73 | if padding < 0 { 74 | padding += 4 75 | } 76 | 77 | f.Header = make([]byte, f.HeaderSize+uint32(padding)) 78 | 79 | _, err = r.Read(f.Header) 80 | if err != nil { 81 | return f, err 82 | } 83 | 84 | // We need to consume the padded length, 85 | // but len(Header) should still be HeaderSize. 86 | f.Header = f.Header[:f.HeaderSize] 87 | 88 | return f, err 89 | } 90 | 91 | func (f RawPacketFlow) encode(w io.Writer) error { 92 | var err error 93 | 94 | err = binary.Write(w, binary.BigEndian, uint32(f.RecordType())) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | // We need to calculate encoded size of the record. 100 | encodedRecordLength := uint32(4 * 4) // 4 32-bit records 101 | 102 | // Add the length of the header padded to a multiple of 4 bytes. 103 | padding := (4 - int(f.HeaderSize)) % 4 104 | if padding < 0 { 105 | padding += 4 106 | } 107 | 108 | encodedRecordLength += f.HeaderSize + uint32(padding) 109 | 110 | err = binary.Write(w, binary.BigEndian, encodedRecordLength) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | err = binary.Write(w, binary.BigEndian, f.Protocol) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | err = binary.Write(w, binary.BigEndian, f.FrameLength) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | err = binary.Write(w, binary.BigEndian, f.Stripped) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | err = binary.Write(w, binary.BigEndian, f.HeaderSize) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | _, err = w.Write(append(f.Header, make([]byte, padding)...)) 136 | 137 | return err 138 | } 139 | 140 | // RecordType returns the type of flow record. 141 | func (f ExtendedSwitchFlow) RecordType() int { 142 | return TypeExtendedSwitchFlowRecord 143 | } 144 | 145 | func decodedExtendedSwitchFlow(r io.Reader) (ExtendedSwitchFlow, error) { 146 | f := ExtendedSwitchFlow{} 147 | 148 | err := binary.Read(r, binary.BigEndian, &f) 149 | 150 | return f, err 151 | } 152 | 153 | func (f ExtendedSwitchFlow) encode(w io.Writer) error { 154 | var err error 155 | 156 | err = binary.Write(w, binary.BigEndian, uint32(f.RecordType())) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | encodedRecordLength := uint32(4 * 4) // 4 32-bit records 162 | 163 | err = binary.Write(w, binary.BigEndian, encodedRecordLength) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | return binary.Write(w, binary.BigEndian, f) 169 | } 170 | -------------------------------------------------------------------------------- /event_discarded_packet.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type EventDiscardedPacket struct { 11 | SequenceNum uint32 12 | DsClass uint32 13 | DsIndex uint32 14 | Drops uint32 15 | Input uint32 16 | Output uint32 17 | Reason uint32 18 | numRecords uint32 19 | Records []Record 20 | } 21 | 22 | func (s EventDiscardedPacket) String() string { 23 | type X EventDiscardedPacket 24 | x := X(s) 25 | return fmt.Sprintf("EventDiscardedPacket: %+v", x) 26 | } 27 | 28 | // SampleType returns the type of sFlow sample. 29 | func (s *EventDiscardedPacket) SampleType() int { 30 | return TypeEventDiscardedPacket 31 | } 32 | 33 | func (s *EventDiscardedPacket) GetRecords() []Record { 34 | return s.Records 35 | } 36 | 37 | func decodEventDiscardedPacket(r io.ReadSeeker) (Sample, error) { 38 | s := &EventDiscardedPacket{} 39 | 40 | var err error 41 | 42 | err = binary.Read(r, binary.BigEndian, &s.SequenceNum) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | err = binary.Read(r, binary.BigEndian, &s.DsClass) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | err = binary.Read(r, binary.BigEndian, &s.DsIndex) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | err = binary.Read(r, binary.BigEndian, &s.Drops) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | err = binary.Read(r, binary.BigEndian, &s.Input) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | err = binary.Read(r, binary.BigEndian, &s.Output) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | err = binary.Read(r, binary.BigEndian, &s.Reason) 73 | if err != nil { 74 | return nil, fmt.Errorf("read reson %v", err) 75 | } 76 | 77 | err = binary.Read(r, binary.BigEndian, &s.numRecords) 78 | if err != nil { 79 | return nil, fmt.Errorf("read numRecords %v", err) 80 | } 81 | 82 | for i := uint32(0); i < s.numRecords; i++ { 83 | format, length := uint32(0), uint32(0) 84 | 85 | err = binary.Read(r, binary.BigEndian, &format) 86 | if err != nil { 87 | return nil, fmt.Errorf("read record format %d %v", i, err) 88 | } 89 | 90 | err = binary.Read(r, binary.BigEndian, &length) 91 | if err != nil { 92 | return nil, fmt.Errorf("read record length %d %v", i, err) 93 | } 94 | 95 | var rec Record 96 | 97 | switch format { 98 | case TypeRawPacketFlowRecord: 99 | rec, err = decodeRawPacketFlow(r) 100 | if err != nil { 101 | return nil, fmt.Errorf("read record flow %d %v", i, err) 102 | } 103 | case TypeExtendedSwitchFlowRecord: 104 | rec, err = decodedExtendedSwitchFlow(r) 105 | if err != nil { 106 | return nil, fmt.Errorf("read record switch flow %d %v", i, err) 107 | } 108 | 109 | default: 110 | _, err := r.Seek(int64(length), 1) 111 | if err != nil { 112 | return nil, fmt.Errorf("read record seek %d %v", i, err) 113 | } 114 | 115 | continue 116 | } 117 | 118 | s.Records = append(s.Records, rec) 119 | } 120 | 121 | return s, nil 122 | } 123 | 124 | func (s *EventDiscardedPacket) encode(w io.Writer) error { 125 | var err error 126 | 127 | // We first need to encode the records. 128 | buf := &bytes.Buffer{} 129 | 130 | for _, rec := range s.Records { 131 | err = rec.encode(buf) 132 | if err != nil { 133 | return ErrEncodingRecord 134 | } 135 | } 136 | 137 | // Fields 138 | encodedSampleSize := uint32(4 * 8) 139 | 140 | // Encoded records 141 | encodedSampleSize += uint32(buf.Len()) 142 | 143 | err = binary.Write(w, binary.BigEndian, uint32(s.SampleType())) 144 | if err != nil { 145 | return err 146 | } 147 | err = binary.Write(w, binary.BigEndian, encodedSampleSize) 148 | if err != nil { 149 | return err 150 | } 151 | err = binary.Write(w, binary.BigEndian, s.SequenceNum) 152 | if err != nil { 153 | return err 154 | } 155 | err = binary.Write(w, binary.BigEndian, s.DsClass) 156 | if err != nil { 157 | return err 158 | } 159 | err = binary.Write(w, binary.BigEndian, s.DsIndex) 160 | if err != nil { 161 | return err 162 | } 163 | err = binary.Write(w, binary.BigEndian, s.Drops) 164 | if err != nil { 165 | return err 166 | } 167 | err = binary.Write(w, binary.BigEndian, s.Input) 168 | if err != nil { 169 | return err 170 | } 171 | err = binary.Write(w, binary.BigEndian, s.Output) 172 | if err != nil { 173 | return err 174 | } 175 | err = binary.Write(w, binary.BigEndian, s.Reason) 176 | if err != nil { 177 | return err 178 | } 179 | err = binary.Write(w, binary.BigEndian, uint32(len(s.Records))) 180 | if err != nil { 181 | return err 182 | } 183 | _, err = io.Copy(w, buf) 184 | return err 185 | } 186 | -------------------------------------------------------------------------------- /counter_sample.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | const ( 12 | TypeGenericInterfaceCountersRecord = 1 13 | TypeEthernetCountersRecord = 2 14 | TypeTokenRingCountersRecord = 3 15 | TypeVgCountersRecord = 4 16 | TypeVlanCountersRecord = 5 17 | 18 | TypeProcessorCountersRecord = 1001 19 | TypeHostCPUCountersRecord = 2003 20 | TypeHostMemoryCountersRecord = 2004 21 | TypeHostDiskCountersRecord = 2005 22 | TypeHostNetCountersRecord = 2006 23 | 24 | // Custom (Enterprise) types 25 | TypeApplicationCountersRecord = (1)<<12 + 1 26 | ) 27 | 28 | type CounterSample struct { 29 | SequenceNum uint32 30 | SourceIdType byte 31 | SourceIdIndexVal uint32 // NOTE: this is 3 bytes in the datagram 32 | numRecords uint32 33 | Records []Record 34 | } 35 | 36 | func (s CounterSample) String() string { 37 | type X CounterSample 38 | x := X(s) 39 | return fmt.Sprintf("CounterSample: %+v", x) 40 | } 41 | 42 | // SampleType returns the type of sFlow sample. 43 | func (s *CounterSample) SampleType() int { 44 | return TypeCounterSample 45 | } 46 | 47 | func (s *CounterSample) GetRecords() []Record { 48 | return s.Records 49 | } 50 | 51 | func decodeCounterSample(r io.ReadSeeker) (Sample, error) { 52 | s := &CounterSample{} 53 | 54 | var err error 55 | 56 | err = binary.Read(r, binary.BigEndian, &s.SequenceNum) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | err = binary.Read(r, binary.BigEndian, &s.SourceIdType) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | var srcIdIndexVal [3]byte 67 | n, err := r.Read(srcIdIndexVal[:]) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | if n != 3 { 73 | return nil, errors.New("sflow: counter sample decoding error") 74 | } 75 | 76 | s.SourceIdIndexVal = uint32(srcIdIndexVal[2]) | 77 | uint32(srcIdIndexVal[1])<<8 | 78 | uint32(srcIdIndexVal[0])<<16 79 | 80 | err = binary.Read(r, binary.BigEndian, &s.numRecords) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | for i := uint32(0); i < s.numRecords; i++ { 86 | format, length := uint32(0), uint32(0) 87 | 88 | err = binary.Read(r, binary.BigEndian, &format) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | err = binary.Read(r, binary.BigEndian, &length) 94 | if err != nil { 95 | return nil, err 96 | } 97 | if length > MaximumRecordLength { 98 | return nil, fmt.Errorf("sflow: record length more than %d: %d", 99 | MaximumRecordLength, length) 100 | } 101 | 102 | var rec Record 103 | 104 | switch format { 105 | case TypeGenericInterfaceCountersRecord: 106 | rec, err = decodeGenericInterfaceCountersRecord(r, length) 107 | if err != nil { 108 | return nil, err 109 | } 110 | case TypeEthernetCountersRecord: 111 | rec, err = decodeEthernetCountersRecord(r, length) 112 | if err != nil { 113 | return nil, err 114 | } 115 | case TypeTokenRingCountersRecord: 116 | rec, err = decodeTokenRingCountersRecord(r, length) 117 | if err != nil { 118 | return nil, err 119 | } 120 | case TypeVgCountersRecord: 121 | rec, err = decodeVgCountersRecord(r, length) 122 | if err != nil { 123 | return nil, err 124 | } 125 | case TypeVlanCountersRecord: 126 | rec, err = decodeVlanCountersRecord(r, length) 127 | if err != nil { 128 | return nil, err 129 | } 130 | case TypeProcessorCountersRecord: 131 | rec, err = decodeProcessorCountersRecord(r, length) 132 | if err != nil { 133 | return nil, err 134 | } 135 | case TypeHostCPUCountersRecord: 136 | rec, err = decodeHostCPUCountersRecord(r, length) 137 | if err != nil { 138 | return nil, err 139 | } 140 | case TypeHostMemoryCountersRecord: 141 | rec, err = decodeHostMemoryCountersRecord(r, length) 142 | if err != nil { 143 | return nil, err 144 | } 145 | case TypeHostDiskCountersRecord: 146 | rec, err = decodeHostDiskCountersRecord(r, length) 147 | if err != nil { 148 | return nil, err 149 | } 150 | case TypeHostNetCountersRecord: 151 | rec, err = decodeHostNetCountersRecord(r, length) 152 | if err != nil { 153 | return nil, err 154 | } 155 | default: 156 | _, err := r.Seek(int64(length), 1) 157 | if err != nil { 158 | return nil, err 159 | } 160 | continue 161 | } 162 | s.Records = append(s.Records, rec) 163 | } 164 | return s, nil 165 | } 166 | 167 | func (s *CounterSample) encode(w io.Writer) error { 168 | var err error 169 | 170 | // We first need to encode the records. 171 | buf := &bytes.Buffer{} 172 | 173 | for _, rec := range s.Records { 174 | err = rec.encode(buf) 175 | if err != nil { 176 | return ErrEncodingRecord 177 | } 178 | } 179 | 180 | // Fields 181 | encodedSampleSize := uint32(4 + 1 + 3 + 4) 182 | 183 | // Encoded records 184 | encodedSampleSize += uint32(buf.Len()) 185 | 186 | err = binary.Write(w, binary.BigEndian, uint32(s.SampleType())) 187 | if err != nil { 188 | return err 189 | } 190 | err = binary.Write(w, binary.BigEndian, encodedSampleSize) 191 | if err != nil { 192 | return err 193 | } 194 | err = binary.Write(w, binary.BigEndian, s.SequenceNum) 195 | if err != nil { 196 | return err 197 | } 198 | err = binary.Write(w, binary.BigEndian, 199 | uint32(s.SourceIdType)|s.SourceIdIndexVal<<24) 200 | if err != nil { 201 | return err 202 | } 203 | err = binary.Write(w, binary.BigEndian, 204 | uint32(len(s.Records))) 205 | if err != nil { 206 | return err 207 | } 208 | 209 | _, err = io.Copy(w, buf) 210 | return err 211 | } 212 | -------------------------------------------------------------------------------- /flow_sample.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | const ( 12 | TypeRawPacketFlowRecord = 1 13 | TypeEthernetFrameFlowRecord = 2 14 | TypeIpv4FlowRecord = 3 15 | TypeIpv6FlowRecord = 4 16 | 17 | TypeExtendedSwitchFlowRecord = 1001 18 | TypeExtendedRouterFlowRecord = 1002 19 | TypeExtendedGatewayFlowRecord = 1003 20 | TypeExtendedUserFlowRecord = 1004 21 | TypeExtendedUrlFlowRecord = 1005 22 | TypeExtendedMlpsFlowRecord = 1006 23 | TypeExtendedNatFlowRecord = 1007 24 | TypeExtendedMlpsTunnelFlowRecord = 1008 25 | TypeExtendedMlpsVcFlowRecord = 1009 26 | TypeExtendedMlpsFecFlowRecord = 1010 27 | TypeExtendedMlpsLvpFecFlowRecord = 1011 28 | TypeExtendedVlanFlowRecord = 1012 29 | ) 30 | 31 | type FlowSample struct { 32 | SequenceNum uint32 33 | SourceIdType byte 34 | SourceIdIndexVal uint32 // NOTE: this is 3 bytes in the datagram 35 | SamplingRate uint32 36 | SamplePool uint32 37 | Drops uint32 38 | Input uint32 39 | Output uint32 40 | numRecords uint32 41 | Records []Record 42 | } 43 | 44 | func (s FlowSample) String() string { 45 | type X FlowSample 46 | x := X(s) 47 | return fmt.Sprintf("FlowSample: %+v", x) 48 | } 49 | 50 | // SampleType returns the type of sFlow sample. 51 | func (s *FlowSample) SampleType() int { 52 | return TypeFlowSample 53 | } 54 | 55 | func (s *FlowSample) GetRecords() []Record { 56 | return s.Records 57 | } 58 | 59 | func decodeFlowSample(r io.ReadSeeker) (Sample, error) { 60 | s := &FlowSample{} 61 | 62 | var err error 63 | 64 | err = binary.Read(r, binary.BigEndian, &s.SequenceNum) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | err = binary.Read(r, binary.BigEndian, &s.SourceIdType) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | var srcIdIndexVal [3]byte 75 | n, err := r.Read(srcIdIndexVal[:]) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | if n != 3 { 81 | return nil, errors.New("sflow: counter sample decoding error") 82 | } 83 | 84 | s.SourceIdIndexVal = uint32(srcIdIndexVal[2]) | 85 | uint32(srcIdIndexVal[1])<<8 | 86 | uint32(srcIdIndexVal[0])<<16 87 | 88 | err = binary.Read(r, binary.BigEndian, &s.SamplingRate) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | err = binary.Read(r, binary.BigEndian, &s.SamplePool) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | err = binary.Read(r, binary.BigEndian, &s.Drops) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | err = binary.Read(r, binary.BigEndian, &s.Input) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | err = binary.Read(r, binary.BigEndian, &s.Output) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | err = binary.Read(r, binary.BigEndian, &s.numRecords) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | for i := uint32(0); i < s.numRecords; i++ { 119 | format, length := uint32(0), uint32(0) 120 | 121 | err = binary.Read(r, binary.BigEndian, &format) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | err = binary.Read(r, binary.BigEndian, &length) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | var rec Record 132 | 133 | switch format { 134 | case TypeRawPacketFlowRecord: 135 | rec, err = decodeRawPacketFlow(r) 136 | if err != nil { 137 | return nil, err 138 | } 139 | case TypeExtendedSwitchFlowRecord: 140 | rec, err = decodedExtendedSwitchFlow(r) 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | default: 146 | _, err := r.Seek(int64(length), 1) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | continue 152 | } 153 | 154 | s.Records = append(s.Records, rec) 155 | } 156 | 157 | return s, nil 158 | } 159 | 160 | func (s *FlowSample) encode(w io.Writer) error { 161 | var err error 162 | 163 | // We first need to encode the records. 164 | buf := &bytes.Buffer{} 165 | 166 | for _, rec := range s.Records { 167 | err = rec.encode(buf) 168 | if err != nil { 169 | return ErrEncodingRecord 170 | } 171 | } 172 | 173 | // Fields 174 | encodedSampleSize := uint32(4 + 1 + 3 + 4 + 4 + 4 + 4 + 4 + 4) 175 | 176 | // Encoded records 177 | encodedSampleSize += uint32(buf.Len()) 178 | 179 | err = binary.Write(w, binary.BigEndian, uint32(s.SampleType())) 180 | if err != nil { 181 | return err 182 | } 183 | err = binary.Write(w, binary.BigEndian, encodedSampleSize) 184 | if err != nil { 185 | return err 186 | } 187 | err = binary.Write(w, binary.BigEndian, s.SequenceNum) 188 | if err != nil { 189 | return err 190 | } 191 | err = binary.Write(w, binary.BigEndian, 192 | uint32(s.SourceIdType)|s.SourceIdIndexVal<<24) 193 | if err != nil { 194 | return err 195 | } 196 | err = binary.Write(w, binary.BigEndian, s.SamplingRate) 197 | if err != nil { 198 | return err 199 | } 200 | err = binary.Write(w, binary.BigEndian, s.SamplePool) 201 | if err != nil { 202 | return err 203 | } 204 | err = binary.Write(w, binary.BigEndian, s.Drops) 205 | if err != nil { 206 | return err 207 | } 208 | err = binary.Write(w, binary.BigEndian, s.Input) 209 | if err != nil { 210 | return err 211 | } 212 | err = binary.Write(w, binary.BigEndian, s.Output) 213 | if err != nil { 214 | return err 215 | } 216 | err = binary.Write(w, binary.BigEndian, uint32(len(s.Records))) 217 | if err != nil { 218 | return err 219 | } 220 | 221 | _, err = io.Copy(w, buf) 222 | return err 223 | } 224 | -------------------------------------------------------------------------------- /decode_test.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestDecodeGenericEthernetCounterSample(t *testing.T) { 9 | f, err := os.Open("_test/counter_sample.dump") 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | d := NewDecoder(f) 15 | 16 | dgram, err := d.Decode() 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | if dgram.Version != 5 { 22 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 23 | } 24 | 25 | if int(dgram.NumSamples) != len(dgram.Samples) { 26 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 27 | } 28 | 29 | if len(dgram.Samples) != 1 { 30 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 31 | } 32 | 33 | sample, ok := dgram.Samples[0].(*CounterSample) 34 | if !ok { 35 | t.Fatalf("expected a CounterSample, got %T", dgram.Samples[0]) 36 | } 37 | 38 | if len(sample.Records) != 2 { 39 | t.Fatalf("expected 2 records, got %d", len(sample.Records)) 40 | } 41 | 42 | ethCounters, ok := sample.Records[0].(EthernetCounters) 43 | if !ok { 44 | t.Fatalf("expected a EthernetCounters record, got %T", sample.Records[0]) 45 | } 46 | 47 | expectedEthCountersRec := EthernetCounters{} 48 | if ethCounters != expectedEthCountersRec { 49 | t.Errorf("expected\n%#v, got\n%#v", expectedEthCountersRec, ethCounters) 50 | } 51 | 52 | genericInterfaceCounters, ok := sample.Records[1].(GenericInterfaceCounters) 53 | if !ok { 54 | t.Fatalf("expected a GenericInterfaceCounters record, got %T", sample.Records[1]) 55 | } 56 | 57 | expectedGenericInterfaceCounters := GenericInterfaceCounters{ 58 | Index: 9, 59 | Type: 6, 60 | Speed: 100000000, 61 | Direction: 1, 62 | Status: 3, 63 | InOctets: 79282473, 64 | InUnicastPackets: 329128, 65 | InMulticastPackets: 0, 66 | InBroadcastPackets: 1493, 67 | InDiscards: 0, 68 | InErrors: 0, 69 | InUnknownProtocols: 0, 70 | OutOctets: 764247430, 71 | OutUnicastPackets: 9470970, 72 | OutMulticastPackets: 780342, 73 | OutBroadcastPackets: 877721, 74 | OutDiscards: 0, 75 | OutErrors: 0, 76 | PromiscuousMode: 1, 77 | } 78 | 79 | if genericInterfaceCounters != expectedGenericInterfaceCounters { 80 | t.Errorf("expected\n%#v, got\n%#v", expectedGenericInterfaceCounters, genericInterfaceCounters) 81 | } 82 | } 83 | 84 | func TestDecodeHostCounters(t *testing.T) { 85 | f, err := os.Open("_test/host_sample.dump") 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | 90 | d := NewDecoder(f) 91 | 92 | dgram, err := d.Decode() 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | 97 | if dgram.Version != 5 { 98 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 99 | } 100 | 101 | if int(dgram.NumSamples) != len(dgram.Samples) { 102 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 103 | } 104 | 105 | if len(dgram.Samples) != 1 { 106 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 107 | } 108 | 109 | sample, ok := dgram.Samples[0].(*CounterSample) 110 | if !ok { 111 | t.Fatalf("expected a CounterSample, got %T", dgram.Samples[0]) 112 | } 113 | 114 | if len(sample.Records) != 4 { 115 | t.Fatalf("expected 4 records, got %d", len(sample.Records)) 116 | } 117 | 118 | // TODO: check values 119 | } 120 | 121 | func TestDecodeFlow1(t *testing.T) { 122 | f, err := os.Open("_test/flow_sample.dump") 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | 127 | d := NewDecoder(f) 128 | 129 | dgram, err := d.Decode() 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | 134 | if dgram.Version != 5 { 135 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 136 | } 137 | 138 | if int(dgram.NumSamples) != len(dgram.Samples) { 139 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 140 | } 141 | 142 | if len(dgram.Samples) != 1 { 143 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 144 | } 145 | 146 | sample, ok := dgram.Samples[0].(*FlowSample) 147 | if !ok { 148 | t.Fatalf("expected a FlowSample, got %T", dgram.Samples[0]) 149 | } 150 | 151 | if len(sample.Records) != 2 { 152 | t.Fatalf("expected 2 records, got %d", len(sample.Records)) 153 | } 154 | 155 | rec, ok := sample.Records[0].(RawPacketFlow) 156 | if !ok { 157 | t.Fatalf("expected a RawPacketFlowRecords, got %T", sample.Records[0]) 158 | } 159 | 160 | if rec.Protocol != 1 { 161 | t.Errorf("expected Protocol to be 1, got %d", rec.Protocol) 162 | } 163 | 164 | if rec.FrameLength != 318 { 165 | t.Errorf("expected FrameLength to be 318, got %d", rec.FrameLength) 166 | } 167 | 168 | if rec.Stripped != 4 { 169 | t.Errorf("expected FrameLength to be 4, got %d", rec.Stripped) 170 | } 171 | 172 | if rec.HeaderSize != 128 { 173 | t.Errorf("expected FrameLength to be 128, got %d", rec.HeaderSize) 174 | } 175 | } 176 | 177 | func TestDecodeEventDiscardedPacket(t *testing.T) { 178 | f, err := os.Open("_test/event_discarded_packet.dump") 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | 183 | d := NewDecoder(f) 184 | 185 | dgram, err := d.Decode() 186 | if err != nil { 187 | t.Fatal(err) 188 | } 189 | 190 | if dgram.Version != 5 { 191 | t.Errorf("Expected datagram version %v, got %v", 5, dgram.Version) 192 | } 193 | 194 | if int(dgram.NumSamples) != len(dgram.Samples) { 195 | t.Fatalf("expected NumSamples to be %d, but len(Samples) is %d", dgram.NumSamples, len(dgram.Samples)) 196 | } 197 | 198 | if len(dgram.Samples) != 1 { 199 | t.Fatalf("expected 1 sample, got %d", len(dgram.Samples)) 200 | } 201 | 202 | _, ok := dgram.Samples[0].(*EventDiscardedPacket) 203 | if !ok { 204 | t.Fatalf("expected a EventDiscardedPacket, got %T", dgram.Samples[0]) 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /counter_record.go: -------------------------------------------------------------------------------- 1 | package sflow 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "unsafe" 8 | ) 9 | 10 | // GenericInterfaceCounters is a generic switch counters record. 11 | type GenericInterfaceCounters struct { 12 | Index uint32 13 | Type uint32 14 | Speed uint64 15 | Direction uint32 16 | Status uint32 17 | InOctets uint64 18 | InUnicastPackets uint32 19 | InMulticastPackets uint32 20 | InBroadcastPackets uint32 21 | InDiscards uint32 22 | InErrors uint32 23 | InUnknownProtocols uint32 24 | OutOctets uint64 25 | OutUnicastPackets uint32 26 | OutMulticastPackets uint32 27 | OutBroadcastPackets uint32 28 | OutDiscards uint32 29 | OutErrors uint32 30 | PromiscuousMode uint32 31 | } 32 | 33 | func (c GenericInterfaceCounters) String() string { 34 | type X GenericInterfaceCounters 35 | x := X(c) 36 | return fmt.Sprintf("GenericInterfaceCounters: %+v", x) 37 | } 38 | 39 | // EthernetCounters is an Ethernet interface counters record. 40 | type EthernetCounters struct { 41 | AlignmentErrors uint32 42 | FCSErrors uint32 43 | SingleCollisionFrames uint32 44 | MultipleCollisionFrames uint32 45 | SQETestErrors uint32 46 | DeferredTransmissions uint32 47 | LateCollisions uint32 48 | ExcessiveCollisions uint32 49 | InternalMACTransmitErrors uint32 50 | CarrierSenseErrors uint32 51 | FrameTooLongs uint32 52 | InternalMACReceiveErrors uint32 53 | SymbolErrors uint32 54 | } 55 | 56 | func (c EthernetCounters) String() string { 57 | type X EthernetCounters 58 | x := X(c) 59 | return fmt.Sprintf("EthernetCounters: %+v", x) 60 | } 61 | 62 | // TokenRingCounters is a token ring interface counters record. 63 | type TokenRingCounters struct { 64 | LineErrors uint32 65 | BurstErrors uint32 66 | ACErrors uint32 67 | AbortTransErrors uint32 68 | InternalErrors uint32 69 | LostFrameErrors uint32 70 | ReceiveCongestions uint32 71 | FrameCopiedErrors uint32 72 | TokenErrors uint32 73 | SoftErrors uint32 74 | HardErrors uint32 75 | SignalLoss uint32 76 | TransmitBeacons uint32 77 | Recoverys uint32 78 | LobeWires uint32 79 | Removes uint32 80 | Singles uint32 81 | FreqErrors uint32 82 | } 83 | 84 | func (c TokenRingCounters) String() string { 85 | type X TokenRingCounters 86 | x := X(c) 87 | return fmt.Sprintf("TokenRingCounters: %+v", x) 88 | } 89 | 90 | // VgCounters is a BaseVG interface counters record. 91 | type VgCounters struct { 92 | InHighPriorityFrames uint32 93 | InHighPriorityOctets uint64 94 | InNormPriorityFrames uint32 95 | InNormPriorityOctets uint64 96 | InIPMErrors uint32 97 | InOversizeFrameErrors uint32 98 | InDataErrors uint32 99 | InNullAddressedFrames uint32 100 | OutHighPriorityFrames uint32 101 | OutHighPriorityOctets uint64 102 | TransitionIntoTrainings uint32 103 | HCInHighPriorityOctets uint64 104 | HCInNormPriorityOctets uint64 105 | HCOutHighPriorityOctets uint64 106 | } 107 | 108 | func (c VgCounters) String() string { 109 | type X VgCounters 110 | x := X(c) 111 | return fmt.Sprintf("VgCounters: %+v", x) 112 | } 113 | 114 | // VlanCounters is a VLAN counters record. 115 | type VlanCounters struct { 116 | ID uint32 117 | Octets uint64 118 | UnicastPackets uint32 119 | MulticastPackets uint32 120 | BroadcastPackets uint32 121 | Discards uint32 122 | } 123 | 124 | func (c VlanCounters) String() string { 125 | type X VlanCounters 126 | x := X(c) 127 | return fmt.Sprintf("VlanCounters: %+v", x) 128 | } 129 | 130 | // ProcessorCounters is a switch processor counters record. 131 | type ProcessorCounters struct { 132 | CPU5s uint32 133 | CPU1m uint32 134 | CPU5m uint32 135 | TotalMemory uint64 136 | FreeMemory uint64 137 | } 138 | 139 | func (c ProcessorCounters) String() string { 140 | type X ProcessorCounters 141 | x := X(c) 142 | return fmt.Sprintf("ProcessorCounters: %+v", x) 143 | } 144 | 145 | // HostCPUCounters is a host CPU counters record. 146 | type HostCPUCounters struct { 147 | Load1m float32 148 | Load5m float32 149 | Load15m float32 150 | ProcessesRunning uint32 151 | ProcessesTotal uint32 152 | NumCPU uint32 153 | SpeedCPU uint32 154 | Uptime uint32 155 | 156 | CPUUser uint32 157 | CPUNice uint32 158 | CPUSys uint32 159 | CPUIdle uint32 160 | CPUWio uint32 161 | CPUIntr uint32 162 | CPUSoftIntr uint32 163 | Interrupts uint32 164 | ContextSwitches uint32 165 | 166 | CPUSteal uint32 167 | CPUGuest uint32 168 | CPUGuestNice uint32 169 | } 170 | 171 | func (c HostCPUCounters) String() string { 172 | type X HostCPUCounters 173 | x := X(c) 174 | return fmt.Sprintf("HostCPUCounters: %+v", x) 175 | } 176 | 177 | // HostMemoryCounters is a host memory counters record. 178 | type HostMemoryCounters struct { 179 | Total uint64 180 | Free uint64 181 | Shared uint64 182 | Buffers uint64 183 | Cached uint64 184 | SwapTotal uint64 185 | SwapFree uint64 186 | 187 | PageIn uint32 188 | PageOut uint32 189 | SwapIn uint32 190 | SwapOut uint32 191 | } 192 | 193 | func (c HostMemoryCounters) String() string { 194 | type X HostMemoryCounters 195 | x := X(c) 196 | return fmt.Sprintf("HostMemoryCounters: %+v", x) 197 | } 198 | 199 | // HostDiskCounters is a host disk counters record. 200 | type HostDiskCounters struct { 201 | Total uint64 202 | Free uint64 203 | MaxUsedPercent float32 204 | Reads uint32 205 | BytesRead uint64 206 | ReadTime uint32 207 | Writes uint32 208 | BytesWritten uint64 209 | WriteTime uint32 210 | } 211 | 212 | func (c HostDiskCounters) String() string { 213 | type X HostDiskCounters 214 | x := X(c) 215 | return fmt.Sprintf("HostDiskCounters: %+v", x) 216 | } 217 | 218 | // HostNetCounters is a host network counters record. 219 | type HostNetCounters struct { 220 | BytesIn uint64 221 | PacketsIn uint32 222 | ErrorsIn uint32 223 | DropsIn uint32 224 | 225 | BytesOut uint64 226 | PacketsOut uint32 227 | ErrorsOut uint32 228 | DropsOut uint32 229 | } 230 | 231 | func (c HostNetCounters) String() string { 232 | type X HostNetCounters 233 | x := X(c) 234 | return fmt.Sprintf("HostNetCounters: %+v", x) 235 | } 236 | 237 | var ( 238 | genericInterfaceCountersSize = uint32(unsafe.Sizeof(GenericInterfaceCounters{})) 239 | ethernetCountersSize = uint32(unsafe.Sizeof(EthernetCounters{})) 240 | tokenRingCountersSize = uint32(unsafe.Sizeof(TokenRingCounters{})) 241 | vgCountersSize = uint32(unsafe.Sizeof(VgCounters{})) 242 | vlanCountersSize = uint32(unsafe.Sizeof(VlanCounters{})) 243 | processorCountersSize = uint32(unsafe.Sizeof(ProcessorCounters{})) 244 | hostCPUCountersSize = uint32(unsafe.Sizeof(HostCPUCounters{})) 245 | hostMemoryCountersSize = uint32(unsafe.Sizeof(HostMemoryCounters{})) 246 | hostDiskCountersSize = uint32(unsafe.Sizeof(HostDiskCounters{})) 247 | hostNetCountersSize = uint32(unsafe.Sizeof(HostNetCounters{})) 248 | ) 249 | 250 | // RecordType returns the type of counter record. 251 | func (c GenericInterfaceCounters) RecordType() int { 252 | return TypeGenericInterfaceCountersRecord 253 | } 254 | 255 | func decodeGenericInterfaceCountersRecord(r io.Reader, length uint32) (GenericInterfaceCounters, error) { 256 | c := GenericInterfaceCounters{} 257 | b := make([]byte, int(length)) 258 | n, _ := r.Read(b) 259 | if n != int(length) { 260 | return c, ErrDecodingRecord 261 | } 262 | 263 | fields := []interface{}{ 264 | &c.Index, 265 | &c.Type, 266 | &c.Speed, 267 | &c.Direction, 268 | &c.Status, 269 | &c.InOctets, 270 | &c.InUnicastPackets, 271 | &c.InMulticastPackets, 272 | &c.InBroadcastPackets, 273 | &c.InDiscards, 274 | &c.InErrors, 275 | &c.InUnknownProtocols, 276 | &c.OutOctets, 277 | &c.OutUnicastPackets, 278 | &c.OutMulticastPackets, 279 | &c.OutBroadcastPackets, 280 | &c.OutDiscards, 281 | &c.OutErrors, 282 | &c.PromiscuousMode, 283 | } 284 | 285 | return c, readFields(b, fields) 286 | } 287 | 288 | func (c GenericInterfaceCounters) encode(w io.Writer) error { 289 | var err error 290 | 291 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 292 | if err != nil { 293 | return err 294 | } 295 | 296 | err = binary.Write(w, binary.BigEndian, genericInterfaceCountersSize) 297 | if err != nil { 298 | return err 299 | } 300 | 301 | err = binary.Write(w, binary.BigEndian, c) 302 | return err 303 | } 304 | 305 | // RecordType returns the type of counter record. 306 | func (c EthernetCounters) RecordType() int { 307 | return TypeEthernetCountersRecord 308 | } 309 | 310 | func decodeEthernetCountersRecord(r io.Reader, length uint32) (EthernetCounters, error) { 311 | c := EthernetCounters{} 312 | b := make([]byte, int(length)) 313 | n, _ := r.Read(b) 314 | if n != int(length) { 315 | return c, ErrDecodingRecord 316 | } 317 | 318 | fields := []interface{}{ 319 | &c.AlignmentErrors, 320 | &c.FCSErrors, 321 | &c.SingleCollisionFrames, 322 | &c.MultipleCollisionFrames, 323 | &c.SQETestErrors, 324 | &c.DeferredTransmissions, 325 | &c.LateCollisions, 326 | &c.ExcessiveCollisions, 327 | &c.InternalMACTransmitErrors, 328 | &c.CarrierSenseErrors, 329 | &c.FrameTooLongs, 330 | &c.InternalMACReceiveErrors, 331 | &c.SymbolErrors, 332 | } 333 | 334 | return c, readFields(b, fields) 335 | } 336 | 337 | func (c EthernetCounters) encode(w io.Writer) error { 338 | var err error 339 | 340 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 341 | if err != nil { 342 | return err 343 | } 344 | 345 | err = binary.Write(w, binary.BigEndian, ethernetCountersSize) 346 | if err != nil { 347 | return err 348 | } 349 | 350 | err = binary.Write(w, binary.BigEndian, c) 351 | return err 352 | } 353 | 354 | // RecordType returns the type of counter record. 355 | func (c TokenRingCounters) RecordType() int { 356 | return TypeTokenRingCountersRecord 357 | } 358 | 359 | func decodeTokenRingCountersRecord(r io.Reader, length uint32) (TokenRingCounters, error) { 360 | c := TokenRingCounters{} 361 | b := make([]byte, int(length)) 362 | n, _ := r.Read(b) 363 | if n != int(length) { 364 | return c, ErrDecodingRecord 365 | } 366 | 367 | fields := []interface{}{ 368 | &c.LineErrors, 369 | &c.BurstErrors, 370 | &c.ACErrors, 371 | &c.AbortTransErrors, 372 | &c.InternalErrors, 373 | &c.LostFrameErrors, 374 | &c.ReceiveCongestions, 375 | &c.FrameCopiedErrors, 376 | &c.TokenErrors, 377 | &c.SoftErrors, 378 | &c.HardErrors, 379 | &c.SignalLoss, 380 | &c.TransmitBeacons, 381 | &c.Recoverys, 382 | &c.LobeWires, 383 | &c.Removes, 384 | &c.Singles, 385 | &c.FreqErrors, 386 | } 387 | 388 | return c, readFields(b, fields) 389 | } 390 | 391 | func (c TokenRingCounters) encode(w io.Writer) error { 392 | var err error 393 | 394 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 395 | if err != nil { 396 | return err 397 | } 398 | 399 | err = binary.Write(w, binary.BigEndian, tokenRingCountersSize) 400 | if err != nil { 401 | return err 402 | } 403 | 404 | err = binary.Write(w, binary.BigEndian, c) 405 | return err 406 | } 407 | 408 | // RecordType returns the type of counter record. 409 | func (c VgCounters) RecordType() int { 410 | return TypeVgCountersRecord 411 | } 412 | 413 | func decodeVgCountersRecord(r io.Reader, length uint32) (VgCounters, error) { 414 | c := VgCounters{} 415 | b := make([]byte, int(length)) 416 | n, _ := r.Read(b) 417 | if n != int(length) { 418 | return c, ErrDecodingRecord 419 | } 420 | 421 | fields := []interface{}{ 422 | &c.InHighPriorityFrames, 423 | &c.InHighPriorityOctets, 424 | &c.InNormPriorityFrames, 425 | &c.InNormPriorityOctets, 426 | &c.InIPMErrors, 427 | &c.InOversizeFrameErrors, 428 | &c.InDataErrors, 429 | &c.InNullAddressedFrames, 430 | &c.OutHighPriorityFrames, 431 | &c.OutHighPriorityOctets, 432 | &c.TransitionIntoTrainings, 433 | &c.HCInHighPriorityOctets, 434 | &c.HCInNormPriorityOctets, 435 | &c.HCOutHighPriorityOctets, 436 | } 437 | 438 | return c, readFields(b, fields) 439 | } 440 | 441 | func (c VgCounters) encode(w io.Writer) error { 442 | var err error 443 | 444 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 445 | if err != nil { 446 | return err 447 | } 448 | 449 | err = binary.Write(w, binary.BigEndian, vgCountersSize) 450 | if err != nil { 451 | return err 452 | } 453 | 454 | err = binary.Write(w, binary.BigEndian, c) 455 | return err 456 | } 457 | 458 | // RecordType returns the type of counter record. 459 | func (c VlanCounters) RecordType() int { 460 | return TypeVlanCountersRecord 461 | } 462 | 463 | func decodeVlanCountersRecord(r io.Reader, length uint32) (VlanCounters, error) { 464 | c := VlanCounters{} 465 | b := make([]byte, int(length)) 466 | n, _ := r.Read(b) 467 | if n != int(length) { 468 | return c, ErrDecodingRecord 469 | } 470 | 471 | fields := []interface{}{ 472 | &c.ID, 473 | &c.Octets, 474 | &c.UnicastPackets, 475 | &c.MulticastPackets, 476 | &c.BroadcastPackets, 477 | &c.Discards, 478 | } 479 | 480 | return c, readFields(b, fields) 481 | } 482 | 483 | func (c VlanCounters) encode(w io.Writer) error { 484 | var err error 485 | 486 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 487 | if err != nil { 488 | return err 489 | } 490 | 491 | err = binary.Write(w, binary.BigEndian, vlanCountersSize) 492 | if err != nil { 493 | return err 494 | } 495 | 496 | err = binary.Write(w, binary.BigEndian, c) 497 | return err 498 | } 499 | 500 | // RecordType returns the type of counter record. 501 | func (c ProcessorCounters) RecordType() int { 502 | return TypeProcessorCountersRecord 503 | } 504 | 505 | func decodeProcessorCountersRecord(r io.Reader, length uint32) (ProcessorCounters, error) { 506 | c := ProcessorCounters{} 507 | b := make([]byte, int(length)) 508 | n, _ := r.Read(b) 509 | if n != int(length) { 510 | return c, ErrDecodingRecord 511 | } 512 | 513 | fields := []interface{}{ 514 | &c.CPU5s, 515 | &c.CPU1m, 516 | &c.CPU5m, 517 | &c.TotalMemory, 518 | &c.FreeMemory, 519 | } 520 | 521 | return c, readFields(b, fields) 522 | } 523 | 524 | func (c ProcessorCounters) encode(w io.Writer) error { 525 | var err error 526 | 527 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 528 | if err != nil { 529 | return err 530 | } 531 | 532 | err = binary.Write(w, binary.BigEndian, processorCountersSize) 533 | if err != nil { 534 | return err 535 | } 536 | 537 | err = binary.Write(w, binary.BigEndian, c) 538 | return err 539 | } 540 | 541 | // RecordType returns the type of counter record. 542 | func (c HostCPUCounters) RecordType() int { 543 | return TypeHostCPUCountersRecord 544 | } 545 | 546 | func decodeHostCPUCountersRecord(r io.Reader, length uint32) (HostCPUCounters, error) { 547 | c := HostCPUCounters{} 548 | b := make([]byte, int(length)) 549 | n, _ := r.Read(b) 550 | if n != int(length) { 551 | return c, ErrDecodingRecord 552 | } 553 | 554 | fields := []interface{}{ 555 | &c.Load1m, 556 | &c.Load5m, 557 | &c.Load15m, 558 | &c.ProcessesRunning, 559 | &c.ProcessesTotal, 560 | &c.NumCPU, 561 | &c.SpeedCPU, 562 | &c.Uptime, 563 | &c.CPUUser, 564 | &c.CPUNice, 565 | &c.CPUSys, 566 | &c.CPUIdle, 567 | &c.CPUWio, 568 | &c.CPUIntr, 569 | &c.CPUSoftIntr, 570 | &c.Interrupts, 571 | &c.ContextSwitches, 572 | &c.CPUSteal, 573 | &c.CPUGuest, 574 | &c.CPUGuestNice, 575 | } 576 | 577 | return c, readFields(b, fields) 578 | } 579 | 580 | func (c HostCPUCounters) encode(w io.Writer) error { 581 | var err error 582 | 583 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 584 | if err != nil { 585 | return err 586 | } 587 | 588 | err = binary.Write(w, binary.BigEndian, hostCPUCountersSize) 589 | if err != nil { 590 | return err 591 | } 592 | 593 | err = binary.Write(w, binary.BigEndian, c) 594 | return err 595 | } 596 | 597 | // RecordType returns the type of counter record. 598 | func (c HostMemoryCounters) RecordType() int { 599 | return TypeHostMemoryCountersRecord 600 | } 601 | 602 | func decodeHostMemoryCountersRecord(r io.Reader, length uint32) (HostMemoryCounters, error) { 603 | c := HostMemoryCounters{} 604 | b := make([]byte, int(length)) 605 | n, _ := r.Read(b) 606 | if n != int(length) { 607 | return c, ErrDecodingRecord 608 | } 609 | 610 | fields := []interface{}{ 611 | &c.Total, 612 | &c.Free, 613 | &c.Shared, 614 | &c.Buffers, 615 | &c.Cached, 616 | &c.SwapTotal, 617 | &c.SwapFree, 618 | &c.PageIn, 619 | &c.PageOut, 620 | &c.SwapIn, 621 | &c.SwapOut, 622 | } 623 | 624 | return c, readFields(b, fields) 625 | } 626 | 627 | func (c HostMemoryCounters) encode(w io.Writer) error { 628 | var err error 629 | 630 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 631 | if err != nil { 632 | return err 633 | } 634 | 635 | err = binary.Write(w, binary.BigEndian, hostMemoryCountersSize) 636 | if err != nil { 637 | return err 638 | } 639 | 640 | err = binary.Write(w, binary.BigEndian, c) 641 | return err 642 | } 643 | 644 | // RecordType returns the type of counter record. 645 | func (c HostDiskCounters) RecordType() int { 646 | return TypeHostDiskCountersRecord 647 | } 648 | 649 | func decodeHostDiskCountersRecord(r io.Reader, length uint32) (HostDiskCounters, error) { 650 | c := HostDiskCounters{} 651 | b := make([]byte, int(length)) 652 | n, _ := r.Read(b) 653 | if n != int(length) { 654 | return c, ErrDecodingRecord 655 | } 656 | 657 | fields := []interface{}{ 658 | &c.Total, 659 | &c.Free, 660 | &c.MaxUsedPercent, 661 | &c.Reads, 662 | &c.BytesRead, 663 | &c.ReadTime, 664 | &c.Writes, 665 | &c.BytesWritten, 666 | &c.WriteTime, 667 | } 668 | 669 | return c, readFields(b, fields) 670 | } 671 | 672 | func (c HostDiskCounters) encode(w io.Writer) error { 673 | var err error 674 | 675 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 676 | if err != nil { 677 | return err 678 | } 679 | 680 | err = binary.Write(w, binary.BigEndian, hostDiskCountersSize) 681 | if err != nil { 682 | return err 683 | } 684 | 685 | err = binary.Write(w, binary.BigEndian, c) 686 | return err 687 | } 688 | 689 | // RecordType returns the type of counter record. 690 | func (c HostNetCounters) RecordType() int { 691 | return TypeHostNetCountersRecord 692 | } 693 | 694 | func decodeHostNetCountersRecord(r io.Reader, length uint32) (HostNetCounters, error) { 695 | c := HostNetCounters{} 696 | b := make([]byte, int(length)) 697 | n, _ := r.Read(b) 698 | if n != int(length) { 699 | return c, ErrDecodingRecord 700 | } 701 | 702 | fields := []interface{}{ 703 | &c.BytesIn, 704 | &c.PacketsIn, 705 | &c.ErrorsIn, 706 | &c.DropsIn, 707 | &c.BytesOut, 708 | &c.PacketsOut, 709 | &c.ErrorsOut, 710 | &c.DropsOut, 711 | } 712 | 713 | return c, readFields(b, fields) 714 | } 715 | 716 | func (c HostNetCounters) encode(w io.Writer) error { 717 | var err error 718 | 719 | err = binary.Write(w, binary.BigEndian, uint32(c.RecordType())) 720 | if err != nil { 721 | return err 722 | } 723 | 724 | err = binary.Write(w, binary.BigEndian, hostNetCountersSize) 725 | if err != nil { 726 | return err 727 | } 728 | 729 | err = binary.Write(w, binary.BigEndian, c) 730 | return err 731 | } 732 | --------------------------------------------------------------------------------