From fd8e61de91baff112c35d90d854cd4b83f131b13 Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Thu, 6 Aug 2026 17:35:22 +0200 Subject: [PATCH 1/3] condition and check_network: ai assisted add a new UBytesPerSec type and integrate it with the condition unit detection/expansion to be used within thresholds add a new item to snclient.Unit called UBytesPerSec add pkg/humanize functions ParseBytesPerSec, uses the existing SI/IEC byte sizes and adds SI bit sizes as well (network speeds may be written in bits/s) modify Condition.getVarValue , Condition.expandUnitByType to accept UBytesPerSec, use the pkg/humanize functions to convert them possibly breaking change: in Condition.conditionSetValue if the operand is in quoutes and the CheckAttribute is not UNone, try to parse it as is. This enables quouted operands like '1 Mbps' to work. otherwise the condition thinks Mbps is another logical operator check_network: switch 'received' 'sent' , 'total' and 'speed' to use the unit UBytesPerSec. While these will take on humanized values, new attributes 'received_bytes' , 'sent_bytes' , 'total_bytes' and 'speed_bytes' take the raw byte count. these are what the Condition.getVarValue extracts for the given keyword, by appending '_bytes' at the end check_network, breaking change: 'total' attribute now takes on humanized value for the send+receive rate. 'total_bytes' now takes the raw byte number. it was previously taking on the raw bytes number add tests in condition_test for checking mock outputs from check_network, which use the UBytesPerSec. uses the full condition checking pipeline, with expandUnitByType , parseBytesPerSec , getUnit from the CheckAttribute etc. to see if the condition parses the operand correctly also add another test in condition_test to parse the condition, and then check if its correct. this time it also checks getVarValue to get raw bytes/s written into the mock data using '_bytes' prefix. this was the convention in other checks like check_drive_io and such add test for the ParseBytesPerSec in pkg/humanize, works locally --- docs/checks/commands/check_network.md | 30 ++++--- pkg/humanize/humanize.go | 118 ++++++++++++++++++++++++++ pkg/humanize/humanize_test.go | 38 +++++++++ pkg/snclient/check_network.go | 29 +++++-- pkg/snclient/checkdata.go | 1 + pkg/snclient/condition.go | 27 ++++++ pkg/snclient/condition_test.go | 76 +++++++++++++++++ 7 files changed, 300 insertions(+), 19 deletions(-) diff --git a/docs/checks/commands/check_network.md b/docs/checks/commands/check_network.md index ab53b9a2..2f4cf052 100644 --- a/docs/checks/commands/check_network.md +++ b/docs/checks/commands/check_network.md @@ -66,16 +66,20 @@ Naemon Config these can be used in filters and thresholds (along with the default attributes): -| Attribute | Description | -| ----------------- | -------------------------------------------------------- | -| MAC | The MAC address | -| enabled | True if the network interface is enabled (true/false) | -| name | Name of the interface | -| net_connection_id | same as name | -| received | Bytes received per second (calculated over the last 30s) | -| total_received | Total bytes received | -| sent | Bytes sent per second (calculated over the last 30s) | -| total_sent | Total bytes sent | -| speed | Network interface speed (in Mbits/sec) | -| flags | Interface flags | -| total | Sum of sent and received bytes per second | +| Attribute | Description | +| ----------------- | ------------------------------------------------------------------------------------------------- | +| MAC | The MAC address | +| enabled | True if the network interface is enabled (true/false) | +| name | Name of the interface | +| net_connection_id | same as name | +| received | Bytes received per second (calculated over the last 30s). In humanized format | +| received_bytes | Bytes received per second (calculated over the last 30s). In bytes | +| total_received | Total bytes received | +| sent | Bytes sent per second (calculated over the last 30s). In humanized format | +| sent_bytes | Bytes sent per second (calculated over the last 30s). In bytes | +| total_sent | Total bytes sent | +| speed | Network interface speed. In Mbits/sec | +| speed | Network interface speed. In bytes | +| flags | Interface flags | +| total | Sum of send and receive rates in bytes per second (calculated over the last 30s). In humanized format | +| total_bytes | Sum of send and receive rates in bytes per second (calculated over the last 30s). In bytes | diff --git a/pkg/humanize/humanize.go b/pkg/humanize/humanize.go index 054f0ee7..9a378442 100644 --- a/pkg/humanize/humanize.go +++ b/pkg/humanize/humanize.go @@ -30,6 +30,20 @@ const ( EByte = PByte * 1000 ) +// SI Bit Sizes. +const ( + IBit = 1 + KBit = IBit * 1000 + MBit = KBit * 1000 + GBit = MBit * 1000 + TBit = GBit * 1000 + PBit = TBit * 1000 + EBit = PBit * 1000 +) + +// BitsPerByte is the number of bits in one byte. +const BitsPerByte = 8 + var bytesSizeTable = map[string]uint64{ "B": Byte, @@ -73,6 +87,95 @@ var bytesSizeTable = map[string]uint64{ "E": EByte, } +var bitSizeTable = map[string]uint64{ + "": IBit, + "k": KBit, + "M": MBit, + "G": GBit, + "T": TBit, + "P": PBit, + "E": EBit, + "Kb": KBit, + "Mb": MBit, + "Gb": GBit, + "Tb": TBit, +} + +// parses byte values with optional suffixes into raw bytes per second equivalents +// values without a "/s" suffix are interpreted as bytes per second. +// ParseBytesPerSec("1 KiB/s") -> 1024 +// ParseBytesPerSec("1 MB/s") -> 1000000 +// ParseBytesPerSec("80GB") -> 80000000000 +// ParseBytesPerSec("1 Mbps") -> 125000 +func ParseBytesPerSec(raw string) (float64, error) { + str := strings.TrimSpace(raw) + if str == "" { + return 0, fmt.Errorf("empty value") + } + + // bit based rates, ex. "100Mbps" -> 100*1000*1000 bits / 8 = 12500000 bytes/s + // these end with bps and have to be treated separately + if strings.HasSuffix(strings.ToLower(str), "bps") { + prefix, num, err := splitNumberPrefix(str[:len(str)-len("bps")]) + if err != nil { + return 0, err + } + factor, ok := getBitSize(prefix) + if !ok { + return 0, fmt.Errorf("unhandled bits size name with prefix: %q, raw: %q", prefix, raw) + } + + return num * float64(factor) / BitsPerByte, nil + } + + // strip "/s" suffix, ex. "1 KiB/s" -> "1 KiB" + if strings.HasSuffix(strings.ToLower(str), "/s") { + str = str[:len(str)-len("/s")] + } + + prefix, num, err := splitNumberPrefix(strings.TrimSpace(str)) + if err != nil { + return 0, err + } + if prefix == "" { + // plain number, already bytes per second + return num, nil + } + factor, ok := getByteSize(prefix) + if !ok { + return 0, fmt.Errorf("unhandled size name: %v", prefix) + } + + return num * float64(factor), nil +} + +// splitNumberPrefix splits a string like "1.5 GiB" into the number and the prefix ("GiB"). +func splitNumberPrefix(raw string) (prefix string, num float64, err error) { + lastDigit := 0 + hasComma := false + for _, r := range raw { + if !unicode.IsDigit(r) && r != '.' && r != ',' { + break + } + if r == ',' { + hasComma = true + } + lastDigit++ + } + + strNum := raw[:lastDigit] + if hasComma { + strNum = strings.ReplaceAll(strNum, ",", "") + } + + num, err = strconv.ParseFloat(strNum, 64) + if err != nil { + return "", 0, fmt.Errorf("parsefloat %s: %s", raw, err.Error()) + } + + return strings.TrimSpace(raw[lastDigit:]), num, nil +} + // ParseBytes("83 M") -> 82854982 func ParseBytes(raw string) (uint64, error) { lastDigit := 0 @@ -204,6 +307,21 @@ func roundToPrecision(val float64, precision int) float64 { return math.Round(val*factor) / factor } +// find entry in the bit size table, looks for case-insensitive cases as well +func getBitSize(name string) (uint64, bool) { + if m, ok := bitSizeTable[name]; ok { + return m, ok + } + + for key, val := range bitSizeTable { + if strings.EqualFold(key, name) { + return val, true + } + } + + return 1, false +} + // find entry in the byte size table func getByteSize(name string) (uint64, bool) { if m, ok := bytesSizeTable[name]; ok { diff --git a/pkg/humanize/humanize_test.go b/pkg/humanize/humanize_test.go index 039f6bfa..cd48ae0c 100644 --- a/pkg/humanize/humanize_test.go +++ b/pkg/humanize/humanize_test.go @@ -34,6 +34,44 @@ func TestParseBytes(t *testing.T) { } } +func TestParseBytesPerSec(t *testing.T) { + tests := []struct { + in string + res float64 + err bool + }{ + {"1 B/s", 1, false}, + {"1B/s", 1, false}, + {"1 KiB/s", 1024, false}, + {"1 KiB /s", 1024, false}, + {"1 MiB/s", 1048576, false}, + {"1.5 MiB/s", 1.5 * 1048576, false}, + {"1 MB/s", 1000000, false}, + {"1 GB/s", 1000000000, false}, + {"80GB", 80000000000, false}, + {"80 GB", 80000000000, false}, + {"4194304", 4194304, false}, + {"12345.67", 12345.67, false}, + {"1 Mbps", 125000, false}, + {"100 Mbps", 12500000, false}, + {"1 Gbps", 125000000, false}, + {"1 kb/s", 1000, false}, + {"", 0, true}, + {"xyz", 0, true}, + {"1 xyz/s", 0, true}, + } + + for _, tst := range tests { + res, err := ParseBytesPerSec(tst.in) + if tst.err { + require.Errorf(t, err, "ParseBytesPerSec: %s should error", tst.in) + } else { + require.NoErrorf(t, err, "ParseBytesPerSec: %s", tst.in) + } + assert.InDeltaf(t, tst.res, res, 0.0001, "ParseBytesPerSec: %s -> %f", tst.in, res) + } +} + func TestBytes(t *testing.T) { tests := []struct { in uint64 diff --git a/pkg/snclient/check_network.go b/pkg/snclient/check_network.go index 4b8c66f0..e56caea9 100644 --- a/pkg/snclient/check_network.go +++ b/pkg/snclient/check_network.go @@ -57,14 +57,17 @@ func (l *CheckNetwork) Build() *CheckData { {name: "enabled", description: "True if the network interface is enabled (true/false)"}, {name: "name", description: "Name of the interface"}, {name: "net_connection_id", description: "same as name"}, - // unit would be B/s but unit is only used to expand threshold inputs here - {name: "received", description: "Bytes received per second (calculated over the last " + TrafficRateDuration.String() + ")", unit: UByte}, + {name: "received", description: "Bytes received per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, + {name: "received_bytes", description: "Bytes received per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, {name: "total_received", description: "Total bytes received", unit: UByte}, - {name: "sent", description: "Bytes sent per second (calculated over the last " + TrafficRateDuration.String() + ")", unit: UByte}, // received comment applies here as well + {name: "sent", description: "Bytes sent per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, + {name: "sent_bytes", description: "Bytes sent per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, {name: "total_sent", description: "Total bytes sent", unit: UByte}, - {name: "speed", description: "Network interface speed (in Mbits/sec)"}, + {name: "speed", description: "Network interface speed. In Mbits/sec", unit: UBytesPerSec}, + {name: "speed", description: "Network interface speed. In bytes", unit: UBytesPerSec}, {name: "flags", description: "Interface flags"}, - {name: "total", description: "Sum of sent and received bytes per second"}, + {name: "total", description: "Sum of send and receive rates in bytes per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, + {name: "total_bytes", description: "Sum of send and receive rates in bytes per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, }, exampleDefault: ` check_network device=eth0 @@ -116,17 +119,27 @@ func (l *CheckNetwork) Check(_ context.Context, snc *Agent, check *CheckData, _ totalSent = IOList[intnr].BytesSent } + speedBytes := "" + if speed >= 0 { + // speed is in Mbit/s, convert to bytes/s + speedBytes = fmt.Sprintf("%.2f", float64(speed)*humanize.MBit/humanize.BitsPerByte) + } + entry := map[string]string{ "MAC": int.HardwareAddr, "enabled": strconv.FormatBool(slices.Contains(int.Flags, "up")), "name": int.Name, "net_connection_id": int.Name, "received": humanize.IBytes(uint64(recvRate)) + "/s", + "received_bytes": fmt.Sprintf("%.2f", recvRate), "total_received": fmt.Sprintf("%d", totalReceived), "sent": humanize.IBytes(uint64(sentRate)) + "/s", + "sent_bytes": fmt.Sprintf("%.2f", sentRate), "total_sent": fmt.Sprintf("%d", totalSent), - "total": fmt.Sprintf("%.2f", recvRate+sentRate), + "total": humanize.IBytes(uint64(recvRate)+uint64(sentRate)) + "/s", + "total_bytes": fmt.Sprintf("%.2f", recvRate+sentRate), "speed": fmt.Sprintf("%d", speed), + "speed_bytes": speedBytes, "flags": strings.Join(int.Flags, ","), } if speed == -1 { @@ -169,11 +182,15 @@ func (l *CheckNetwork) Check(_ context.Context, snc *Agent, check *CheckData, _ "name": deviceName, "net_connection_id": deviceName, "received": "0", + "received_bytes": "0", "total_received": "0", "sent": "0", + "sent_bytes": "0", "total_sent": "0", "total": "0", + "total_bytes": "0", "speed": "-1", + "speed_bytes": "", "flags": "", }) } diff --git a/pkg/snclient/checkdata.go b/pkg/snclient/checkdata.go index 9729acba..5b080cc2 100644 --- a/pkg/snclient/checkdata.go +++ b/pkg/snclient/checkdata.go @@ -84,6 +84,7 @@ const ( UTimestamp UPercent UBool + UBytesPerSec ) type CheckAttribute struct { diff --git a/pkg/snclient/condition.go b/pkg/snclient/condition.go index d0c178b8..e5b4e647 100644 --- a/pkg/snclient/condition.go +++ b/pkg/snclient/condition.go @@ -524,6 +524,11 @@ func (c *Condition) getVarValue(data map[string]string) (varStr string, ok bool) if ok { return varStr, ok } + case strings.EqualFold(c.unit, "B/s"): + varStr, ok = data[c.keyword+"_bytes"] + if ok { + return varStr, ok + } } varStr, ok = data[c.keyword+"_value"] @@ -805,6 +810,12 @@ func (c *Condition) conditionSetValue(str string, expand bool) error { str = strings.TrimSuffix(str, "'") c.value = str + // quoted values are treated as literal strings, except when the attribute uses a known unit unlike UNone, + // example: '1 KiB/s' of UBytesPerSec + if expand && c.getUnit(c.keyword) != UNone { + return c.expandUnitByType(str) + } + return nil case strings.HasPrefix(str, `"`): if !strings.HasSuffix(str, `"`) || len(str) == 1 { @@ -814,6 +825,10 @@ func (c *Condition) conditionSetValue(str string, expand bool) error { str = strings.TrimSuffix(str, `"`) c.value = str + if expand && c.getUnit(c.keyword) != UNone { + return c.expandUnitByType(str) + } + return nil case !expand: c.value = str @@ -881,6 +896,9 @@ func (c *Condition) expandDateKeyword(str string) bool { return false } +// this sets the Condition.value and Condition.unit according to CheckAttribute.unit +// Condition.Unit is a string, while CheckAttribute.unit is of snclient.Unit type +// //nolint:funlen // the function is long due to handling all unit types, but it is simple func (c *Condition) expandUnitByType(str string) error { // valid units might be "today", "thisweek", "thismonth", "thisyear" and ":utc" variants @@ -928,6 +946,15 @@ func (c *Condition) expandUnitByType(str string) error { c.value = strconv.FormatUint(value, 10) c.unit = "B" + return nil + case UBytesPerSec: + value, err := humanize.ParseBytesPerSec(str) + if err != nil { + return fmt.Errorf("invalid bytes per second value: %s", err.Error()) + } + c.value = strconv.FormatFloat(value, 'f', -1, 64) + c.unit = "B/s" + return nil case UDate, UTimestamp: value, err := utils.ExpandDuration(str) diff --git a/pkg/snclient/condition_test.go b/pkg/snclient/condition_test.go index 65c09fcd..7eb41cdc 100644 --- a/pkg/snclient/condition_test.go +++ b/pkg/snclient/condition_test.go @@ -144,6 +144,82 @@ func TestConditionParseErrors(t *testing.T) { } } +func TestConditionBytePerSec(t *testing.T) { + // example attributes from check_network + attr := &[]CheckAttribute{ + {name: "sent", unit: UBytesPerSec}, + {name: "received", unit: UBytesPerSec}, + {name: "total", unit: UBytesPerSec}, + {name: "speed", unit: UBytesPerSec}, + {name: "used", unit: UByte}, + {name: "version", unit: UNone}, + } + for _, check := range []struct { + threshold string + value string + unit string + }{ + {`sent > '1 KiB/s'`, "1024", "B/s"}, + {`sent > 1KiB/s`, "1024", "B/s"}, + {`sent > '1 MiB/s'`, "1048576", "B/s"}, + {`sent > '1.5 MiB/s'`, "1572864", "B/s"}, + {`speed > 100Mbps`, "12500000", "B/s"}, + {`speed > '100 Mbps'`, "12500000", "B/s"}, + {`speed > '1 Gbps'`, "125000000", "B/s"}, + {`total > 80GB`, "80000000000", "B/s"}, + {`total > '80 GB'`, "80000000000", "B/s"}, + {`received > 1024`, "1024", ""}, + } { + cond, err := NewCondition(check.threshold, attr) + require.NoErrorf(t, err, "parsed threshold %s", check.threshold) + assert.Equalf(t, check.value, cond.value, "value for %s", check.threshold) + assert.Equalf(t, check.unit, cond.unit, "unit for %s", check.threshold) + } + + cond, err := NewCondition(`used > '90GB'`, attr) + require.NoError(t, err) + assert.Equal(t, "90000000000", cond.value) + assert.Equal(t, "B", cond.unit) + + // quoted values on plain string attributes stay literal + cond, err = NewCondition(`version not like '1 2 3'`, attr) + require.NoError(t, err) + assert.Equal(t, "1 2 3", cond.value) + assert.Empty(t, cond.unit) +} + +func TestConditionBytePerSecMatch(t *testing.T) { + // example attributes from check_network + attr := &[]CheckAttribute{ + {name: "sent", unit: UBytesPerSec}, + {name: "received", unit: UBytesPerSec}, + } + data := map[string]string{ + "sent": "4 MiB/s", + "sent_bytes": "4194304", + "received": "11 MiB/s", + "received_bytes": "11534336", + } + for _, check := range []struct { + threshold string + expect bool + }{ + {"sent > '1 KiB/s'", true}, + {"sent > 1MiB/s", true}, + {"sent > '1 GiB/s'", false}, + {"sent < '1 KiB/s'", false}, + {"sent = '4 MiB/s'", true}, + {"received >= '100 KiB/s'", true}, + {"received < '1 MiB/s'", false}, + } { + cond, err := NewCondition(check.threshold, attr) + require.NoErrorf(t, err, "parsed threshold %s", check.threshold) + res, ok := cond.Match(data) + assert.Truef(t, ok, "deterministic for %s", check.threshold) + assert.Equalf(t, check.expect, res, "result for %s", check.threshold) + } +} + func TestConditionCompare(t *testing.T) { for _, check := range []struct { threshold string From ebd1f47c16bc0b7e0726698da1e71f130466ba9a Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Thu, 6 Aug 2026 18:03:44 +0200 Subject: [PATCH 2/3] check_network: fix the redefinition of speed, copy paste error, the name should have been speed_bytes the attribute was already being added as speed_bytes, just the CheckNetwork.attributes array item was wrong --- docs/checks/commands/check_network.md | 2 +- pkg/snclient/check_network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/checks/commands/check_network.md b/docs/checks/commands/check_network.md index 2f4cf052..766d44b4 100644 --- a/docs/checks/commands/check_network.md +++ b/docs/checks/commands/check_network.md @@ -79,7 +79,7 @@ these can be used in filters and thresholds (along with the default attributes): | sent_bytes | Bytes sent per second (calculated over the last 30s). In bytes | | total_sent | Total bytes sent | | speed | Network interface speed. In Mbits/sec | -| speed | Network interface speed. In bytes | +| speed_bytes | Network interface speed. In bytes | | flags | Interface flags | | total | Sum of send and receive rates in bytes per second (calculated over the last 30s). In humanized format | | total_bytes | Sum of send and receive rates in bytes per second (calculated over the last 30s). In bytes | diff --git a/pkg/snclient/check_network.go b/pkg/snclient/check_network.go index e56caea9..b4c736c1 100644 --- a/pkg/snclient/check_network.go +++ b/pkg/snclient/check_network.go @@ -64,7 +64,7 @@ func (l *CheckNetwork) Build() *CheckData { {name: "sent_bytes", description: "Bytes sent per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, {name: "total_sent", description: "Total bytes sent", unit: UByte}, {name: "speed", description: "Network interface speed. In Mbits/sec", unit: UBytesPerSec}, - {name: "speed", description: "Network interface speed. In bytes", unit: UBytesPerSec}, + {name: "speed_bytes", description: "Network interface speed. In bytes", unit: UBytesPerSec}, {name: "flags", description: "Interface flags"}, {name: "total", description: "Sum of send and receive rates in bytes per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, {name: "total_bytes", description: "Sum of send and receive rates in bytes per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, From 42f4eaf5bf694aa6d9162bb87a982b9c3faba590 Mon Sep 17 00:00:00 2001 From: Ahmet Oeztuerk Date: Fri, 7 Aug 2026 16:03:13 +0200 Subject: [PATCH 3/3] check_network: misc fixes after discussion - add 'name' as an alias to 'device' in the attributes - adjust some attribute documentation to be more readable - convert sent, sent_bytes , received, received_bytes , total , total_bytes and speed, speed_bytes to uint64 before adding them to entry - add comments in humanize.go --- docs/checks/commands/check_network.md | 13 +++++++------ pkg/humanize/humanize.go | 5 +++-- pkg/snclient/check_network.go | 27 +++++++++++++++------------ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/checks/commands/check_network.md b/docs/checks/commands/check_network.md index 766d44b4..144ada18 100644 --- a/docs/checks/commands/check_network.md +++ b/docs/checks/commands/check_network.md @@ -70,16 +70,17 @@ these can be used in filters and thresholds (along with the default attributes): | ----------------- | ------------------------------------------------------------------------------------------------- | | MAC | The MAC address | | enabled | True if the network interface is enabled (true/false) | -| name | Name of the interface | +| device | Interface device | +| name | Alias for device | | net_connection_id | same as name | | received | Bytes received per second (calculated over the last 30s). In humanized format | -| received_bytes | Bytes received per second (calculated over the last 30s). In bytes | +| received_bytes | Bytes received in the rate calculation over the last 30s | | total_received | Total bytes received | | sent | Bytes sent per second (calculated over the last 30s). In humanized format | -| sent_bytes | Bytes sent per second (calculated over the last 30s). In bytes | +| sent_bytes | Bytes sent in the rate calculation over the last 30s | | total_sent | Total bytes sent | -| speed | Network interface speed. In Mbits/sec | -| speed_bytes | Network interface speed. In bytes | +| speed | Network interface speed. | +| speed_bytes | Network interface speed. In bytes/s. | | flags | Interface flags | | total | Sum of send and receive rates in bytes per second (calculated over the last 30s). In humanized format | -| total_bytes | Sum of send and receive rates in bytes per second (calculated over the last 30s). In bytes | +| total_bytes | Sum of sent and received bytes in the rate calculation over the last 30s | diff --git a/pkg/humanize/humanize.go b/pkg/humanize/humanize.go index 9a378442..a7523d32 100644 --- a/pkg/humanize/humanize.go +++ b/pkg/humanize/humanize.go @@ -114,7 +114,8 @@ func ParseBytesPerSec(raw string) (float64, error) { } // bit based rates, ex. "100Mbps" -> 100*1000*1000 bits / 8 = 12500000 bytes/s - // these end with bps and have to be treated separately + // these end with 'bps' and have to be treated separately than ones that end with '/s' + // casing of the unit matters, ex. "Mb" is binary (mebibyte) while "MB" is decimal if strings.HasSuffix(strings.ToLower(str), "bps") { prefix, num, err := splitNumberPrefix(str[:len(str)-len("bps")]) if err != nil { @@ -122,7 +123,7 @@ func ParseBytesPerSec(raw string) (float64, error) { } factor, ok := getBitSize(prefix) if !ok { - return 0, fmt.Errorf("unhandled bits size name with prefix: %q, raw: %q", prefix, raw) + return 0, fmt.Errorf("unhandled bits size name with prefix: %q , raw: %q , str: %q", prefix, raw, str) } return num * float64(factor) / BitsPerByte, nil diff --git a/pkg/snclient/check_network.go b/pkg/snclient/check_network.go index b4c736c1..99a109c6 100644 --- a/pkg/snclient/check_network.go +++ b/pkg/snclient/check_network.go @@ -40,8 +40,8 @@ func (l *CheckNetwork) Build() *CheckData { State: CheckExitOK, }, args: map[string]CheckArgument{ - "dev": {value: &l.names, description: "Alias for device"}, "device": {value: &l.names, description: "The device to check. Default is all"}, + "dev": {value: &l.names, description: "Alias for device"}, "name": {value: &l.names, description: "Alias for device"}, "exclude": {value: &l.excludes, description: "Exclude device by name"}, }, @@ -55,19 +55,20 @@ func (l *CheckNetwork) Build() *CheckData { attributes: []CheckAttribute{ {name: "MAC", description: "The MAC address"}, {name: "enabled", description: "True if the network interface is enabled (true/false)"}, - {name: "name", description: "Name of the interface"}, + {name: "device", description: "Interface device"}, + {name: "name", description: "Alias for device"}, {name: "net_connection_id", description: "same as name"}, {name: "received", description: "Bytes received per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, - {name: "received_bytes", description: "Bytes received per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, + {name: "received_bytes", description: "Bytes received in the rate calculation over the last " + TrafficRateDuration.String(), unit: UBytesPerSec}, {name: "total_received", description: "Total bytes received", unit: UByte}, {name: "sent", description: "Bytes sent per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, - {name: "sent_bytes", description: "Bytes sent per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, + {name: "sent_bytes", description: "Bytes sent in the rate calculation over the last " + TrafficRateDuration.String(), unit: UBytesPerSec}, {name: "total_sent", description: "Total bytes sent", unit: UByte}, - {name: "speed", description: "Network interface speed. In Mbits/sec", unit: UBytesPerSec}, - {name: "speed_bytes", description: "Network interface speed. In bytes", unit: UBytesPerSec}, + {name: "speed", description: "Network interface speed.", unit: UBytesPerSec}, + {name: "speed_bytes", description: "Network interface speed. In bytes/s.", unit: UBytesPerSec}, {name: "flags", description: "Interface flags"}, {name: "total", description: "Sum of send and receive rates in bytes per second (calculated over the last " + TrafficRateDuration.String() + "). In humanized format", unit: UBytesPerSec}, - {name: "total_bytes", description: "Sum of send and receive rates in bytes per second (calculated over the last " + TrafficRateDuration.String() + "). In bytes", unit: UBytesPerSec}, + {name: "total_bytes", description: "Sum of sent and received bytes in the rate calculation over the last " + TrafficRateDuration.String(), unit: UBytesPerSec}, }, exampleDefault: ` check_network device=eth0 @@ -121,24 +122,25 @@ func (l *CheckNetwork) Check(_ context.Context, snc *Agent, check *CheckData, _ speedBytes := "" if speed >= 0 { - // speed is in Mbit/s, convert to bytes/s + // speed is in Mbps, convert to bytes/s speedBytes = fmt.Sprintf("%.2f", float64(speed)*humanize.MBit/humanize.BitsPerByte) } entry := map[string]string{ "MAC": int.HardwareAddr, "enabled": strconv.FormatBool(slices.Contains(int.Flags, "up")), + "device": int.Name, "name": int.Name, "net_connection_id": int.Name, "received": humanize.IBytes(uint64(recvRate)) + "/s", - "received_bytes": fmt.Sprintf("%.2f", recvRate), + "received_bytes": fmt.Sprintf("%d", uint64(recvRate)), "total_received": fmt.Sprintf("%d", totalReceived), "sent": humanize.IBytes(uint64(sentRate)) + "/s", - "sent_bytes": fmt.Sprintf("%.2f", sentRate), + "sent_bytes": fmt.Sprintf("%d", uint64(sentRate)), "total_sent": fmt.Sprintf("%d", totalSent), "total": humanize.IBytes(uint64(recvRate)+uint64(sentRate)) + "/s", - "total_bytes": fmt.Sprintf("%.2f", recvRate+sentRate), - "speed": fmt.Sprintf("%d", speed), + "total_bytes": fmt.Sprintf("%d", uint64(recvRate)+uint64(sentRate)), + "speed": fmt.Sprintf("%d Mb/s", speed), "speed_bytes": speedBytes, "flags": strings.Join(int.Flags, ","), } @@ -179,6 +181,7 @@ func (l *CheckNetwork) Check(_ context.Context, snc *Agent, check *CheckData, _ "_error": fmt.Sprintf("no device named %s found", deviceName), "MAC": "", "enabled": "false", + "device": deviceName, "name": deviceName, "net_connection_id": deviceName, "received": "0",