diff --git a/utils/networkutils/networkutils.go b/utils/networkutils/networkutils.go index 8ed5484..c6bd842 100644 --- a/utils/networkutils/networkutils.go +++ b/utils/networkutils/networkutils.go @@ -498,3 +498,86 @@ func ParseEthtoolLOutput(output string) (*EthtoolQueueCounts, error) { CurrentCombinedQueues: currentCombinedQueues, }, nil } + +// EthtoolRingSizes contains ring parameters for an interface as returned by `ethtool -g`. +// Fields are set to -1 if the value is "n/a" or omitted. +type EthtoolRingSizes struct { + MaxRX int + MaxRXMini int + MaxRXJumbo int + MaxTX int + CurrentRX int + CurrentRXMini int + CurrentRXJumbo int + CurrentTX int +} + +// ParseEthtoolGOutput parses the output of `ethtool -g` and returns an EthtoolRingSizes +// object. Populates fields with -1 if the value is "n/a" or omitted, returns an error if +// the output cannot be parsed. +func ParseEthtoolGOutput(output string) (*EthtoolRingSizes, error) { + idx := strings.Index(output, "Current hardware settings:") + if idx == -1 { + return nil, fmt.Errorf("parsing ethtool -g output: missing 'Current hardware settings:'") + } + preSetSection := output[:idx] + currentSection := output[idx:] + + rxRe := regexp.MustCompile(`(?m)^\s*RX:\s*(\d+|n/a)`) + rxMiniRe := regexp.MustCompile(`(?m)^\s*RX Mini:\s*(\d+|n/a)`) + rxJumboRe := regexp.MustCompile(`(?m)^\s*RX Jumbo:\s*(\d+|n/a)`) + txRe := regexp.MustCompile(`(?m)^\s*TX:\s*(\d+|n/a)`) + + parseVal := func(re *regexp.Regexp, s string) (int, error) { + m := re.FindStringSubmatch(s) + if len(m) < 2 || m[1] == "n/a" { + return -1, nil + } + return strconv.Atoi(m[1]) + } + + maxRX, err := parseVal(rxRe, preSetSection) + if err != nil { + return nil, fmt.Errorf("parsing max RX: %w", err) + } + maxRXMini, err := parseVal(rxMiniRe, preSetSection) + if err != nil { + return nil, fmt.Errorf("parsing max RX Mini: %w", err) + } + maxRXJumbo, err := parseVal(rxJumboRe, preSetSection) + if err != nil { + return nil, fmt.Errorf("parsing max RX Jumbo: %w", err) + } + maxTX, err := parseVal(txRe, preSetSection) + if err != nil { + return nil, fmt.Errorf("parsing max TX: %w", err) + } + + currentRX, err := parseVal(rxRe, currentSection) + if err != nil { + return nil, fmt.Errorf("parsing current RX: %w", err) + } + currentRXMini, err := parseVal(rxMiniRe, currentSection) + if err != nil { + return nil, fmt.Errorf("parsing current RX Mini: %w", err) + } + currentRXJumbo, err := parseVal(rxJumboRe, currentSection) + if err != nil { + return nil, fmt.Errorf("parsing current RX Jumbo: %w", err) + } + currentTX, err := parseVal(txRe, currentSection) + if err != nil { + return nil, fmt.Errorf("parsing current TX: %w", err) + } + + return &EthtoolRingSizes{ + MaxRX: maxRX, + MaxRXMini: maxRXMini, + MaxRXJumbo: maxRXJumbo, + MaxTX: maxTX, + CurrentRX: currentRX, + CurrentRXMini: currentRXMini, + CurrentRXJumbo: currentRXJumbo, + CurrentTX: currentTX, + }, nil +} diff --git a/utils/networkutils/networkutils_test.go b/utils/networkutils/networkutils_test.go index 142c745..ac9c500 100644 --- a/utils/networkutils/networkutils_test.go +++ b/utils/networkutils/networkutils_test.go @@ -660,91 +660,60 @@ Combined: 16`, }) } } - -func TestEthtoolQueueCountsEffectiveQueues(t *testing.T) { +func TestParseEthtoolGOutput(t *testing.T) { cases := []struct { - name string - counts *EthtoolQueueCounts - wantEffectiveCurrentRX int - wantEffectiveCurrentTX int - wantEffectiveMaxRX int - wantEffectiveMaxTX int + name string + input string + want *EthtoolRingSizes + wantErr bool }{ { - name: "separate rx and tx queues set", - counts: &EthtoolQueueCounts{ - CurrentRXQueues: 2, - CurrentTXQueues: 4, - CurrentCombinedQueues: -1, - MaxRXQueues: 16, - MaxTXQueues: 32, - MaxCombinedQueues: -1, - }, - wantEffectiveCurrentRX: 2, - wantEffectiveCurrentTX: 4, - wantEffectiveMaxRX: 16, - wantEffectiveMaxTX: 32, - }, - { - name: "combined queues set", - counts: &EthtoolQueueCounts{ - CurrentRXQueues: -1, - CurrentTXQueues: -1, - CurrentCombinedQueues: 8, - MaxRXQueues: -1, - MaxTXQueues: -1, - MaxCombinedQueues: 64, + name: "valid idpf output", + input: `Ring parameters for eth0: +Pre-set maximums: +RX: 8128 +RX Mini: n/a +RX Jumbo: n/a +TX: 8160 +TX push buff len: n/a +Current hardware settings: +RX: 512 +RX Mini: n/a +RX Jumbo: n/a +TX: 512 +RX Buf Len: n/a +CQE Size: n/a +TX Push: off +RX Push: off +TX push buff len: n/a +TCP data split: on`, + want: &EthtoolRingSizes{ + MaxRX: 8128, + MaxRXMini: -1, + MaxRXJumbo: -1, + MaxTX: 8160, + CurrentRX: 512, + CurrentRXMini: -1, + CurrentRXJumbo: -1, + CurrentTX: 512, }, - wantEffectiveCurrentRX: 8, - wantEffectiveCurrentTX: 8, - wantEffectiveMaxRX: 64, - wantEffectiveMaxTX: 64, }, { - name: "neither separate nor combined queues set", - counts: &EthtoolQueueCounts{ - CurrentRXQueues: -1, - CurrentTXQueues: -1, - CurrentCombinedQueues: -1, - MaxRXQueues: -1, - MaxTXQueues: -1, - MaxCombinedQueues: -1, - }, - wantEffectiveCurrentRX: -1, - wantEffectiveCurrentTX: -1, - wantEffectiveMaxRX: -1, - wantEffectiveMaxTX: -1, - }, - { - name: "separate queues take precedence over combined queues", - counts: &EthtoolQueueCounts{ - CurrentRXQueues: 3, - CurrentTXQueues: 5, - CurrentCombinedQueues: 12, - MaxRXQueues: 15, - MaxTXQueues: 25, - MaxCombinedQueues: 48, - }, - wantEffectiveCurrentRX: 3, - wantEffectiveCurrentTX: 5, - wantEffectiveMaxRX: 15, - wantEffectiveMaxTX: 25, + name: "invalid output missing section", + input: "Invalid output", + wantErr: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - if got := tc.counts.EffectiveCurrentRXQueues(); got != tc.wantEffectiveCurrentRX { - t.Errorf("EffectiveCurrentRXQueues() = %d, want %d", got, tc.wantEffectiveCurrentRX) - } - if got := tc.counts.EffectiveCurrentTXQueues(); got != tc.wantEffectiveCurrentTX { - t.Errorf("EffectiveCurrentTXQueues() = %d, want %d", got, tc.wantEffectiveCurrentTX) - } - if got := tc.counts.EffectiveMaxRXQueues(); got != tc.wantEffectiveMaxRX { - t.Errorf("EffectiveMaxRXQueues() = %d, want %d", got, tc.wantEffectiveMaxRX) + got, err := ParseEthtoolGOutput(tc.input) + if (err != nil) != tc.wantErr { + t.Errorf("ParseEthtoolGOutput(%q) returned error %v, wantErr %t", tc.input, err, tc.wantErr) + return } - if got := tc.counts.EffectiveMaxTXQueues(); got != tc.wantEffectiveMaxTX { - t.Errorf("EffectiveMaxTXQueues() = %d, want %d", got, tc.wantEffectiveMaxTX) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Errorf("ParseEthtoolGOutput(%q) mismatch (-got +want):\n%s", tc.input, diff) } }) }