diff --git a/go.mod b/go.mod index 5893ac562db..3a25f19e3b6 100644 --- a/go.mod +++ b/go.mod @@ -128,7 +128,7 @@ require ( github.com/prometheus/sigv4 v0.4.1 github.com/richardartoul/molecule v1.0.0 github.com/schollz/progressbar/v3 v3.19.0 - github.com/shirou/gopsutil/v4 v4.26.3 + github.com/shirou/gopsutil/v4 v4.26.4 github.com/thanos-io/objstore v0.0.0-20250115091151-a54d0f04b42a github.com/tjhop/slog-gokit v0.2.0 github.com/twmb/franz-go v1.20.7 diff --git a/go.sum b/go.sum index a5c51229330..73d85af3784 100644 --- a/go.sum +++ b/go.sum @@ -906,8 +906,8 @@ github.com/sercand/kuberesolver/v6 v6.0.1 h1:XZUTA0gy/lgDYp/UhEwv7Js24F1j8NJ833Q github.com/sercand/kuberesolver/v6 v6.0.1/go.mod h1:C0tsTuRMONSY+Xf7pv7RMW1/JlewY1+wS8SZE+1lf1s= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= -github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= -github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY= +github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/vendor/github.com/shirou/gopsutil/v4/cpu/cpu_windows.go b/vendor/github.com/shirou/gopsutil/v4/cpu/cpu_windows.go index a6000a3c50e..c15a085b75c 100644 --- a/vendor/github.com/shirou/gopsutil/v4/cpu/cpu_windows.go +++ b/vendor/github.com/shirou/gopsutil/v4/cpu/cpu_windows.go @@ -25,6 +25,7 @@ var ( procGetLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx") procGetSystemFirmwareTable = common.Modkernel32.NewProc("GetSystemFirmwareTable") procCallNtPowerInformation = common.ModPowrProf.NewProc("CallNtPowerInformation") + procGetActiveProcessorGroupCount = common.Modkernel32.NewProc("GetActiveProcessorGroupCount") ) type win32_Processor struct { //nolint:revive //FIXME @@ -263,6 +264,21 @@ func perCPUTimes() ([]TimesStat, error) { // makes call to Windows API function to retrieve performance information for each core func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) { + // On hosts with more than 64 logical CPUs Windows splits CPUs into Processor Groups + // (up to 64 logical CPUs per group). The non-Ex NtQuerySystemInformation only returns + // data for the calling thread's group, so whenever the Ex variant is available we + // iterate every active group and concatenate the results. See issue #887. + if common.ProcNtQuerySystemInformationEx.Find() == nil { + return perfInfoAllGroups() + } + return perfInfoSingleGroup() +} + +// perfInfoSingleGroup queries SystemProcessorPerformanceInformation via the non-Ex +// NtQuerySystemInformation call. This is the legacy fallback for environments where +// NtQuerySystemInformationEx cannot be resolved; it only returns data for the calling +// thread's processor group. +func perfInfoSingleGroup() ([]win32_SystemProcessorPerformanceInformation, error) { // Make maxResults large for safety. // We can't invoke the api call with a results array that's too small. // If we have more than 2056 cores on a single host, then it's probably the future. @@ -286,16 +302,61 @@ func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) { // check return code for errors if retCode != 0 { - return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error()) + return nil, fmt.Errorf("call to NtQuerySystemInformation returned 0x%x: %w", retCode, err) } // calculate the number of returned elements based on the returned size numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize // trim results to the number of returned elements - resultBuffer = resultBuffer[:numReturnedElements] + return resultBuffer[:numReturnedElements], nil +} - return resultBuffer, nil +// perfInfoAllGroups queries SystemProcessorPerformanceInformation for every active +// processor group via NtQuerySystemInformationEx and concatenates the results. The +// group index is passed as the InputBuffer per the Ex calling convention documented at +// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/queryex.htm +func perfInfoAllGroups() ([]win32_SystemProcessorPerformanceInformation, error) { + // GetActiveProcessorGroupCount returns 0 only on failure; propagate the error + // rather than silently defaulting to a single group and returning partial data. + r, _, callErr := procGetActiveProcessorGroupCount.Call() + if r == 0 { + return nil, fmt.Errorf("GetActiveProcessorGroupCount returned 0: %w", callErr) + } + groupCount := uint16(r) + + var result []win32_SystemProcessorPerformanceInformation + for g := uint16(0); g < groupCount; g++ { + numLP := windows.GetActiveProcessorCount(g) + if numLP == 0 { + return nil, fmt.Errorf("GetActiveProcessorCount returned 0 for processor group %d", g) + } + // buffer sized exactly for this group's logical CPU count + buf := make([]win32_SystemProcessorPerformanceInformation, numLP) + bufSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(numLP) + var retSize uint32 + // InputBuffer is a USHORT (2 bytes) holding the target processor group index. + group := g + retCode, _, err := common.ProcNtQuerySystemInformationEx.Call( + win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation + uintptr(unsafe.Pointer(&group)), // InputBuffer: pointer to USHORT group index + unsafe.Sizeof(group), // InputBufferLength: sizeof(USHORT) = 2 + uintptr(unsafe.Pointer(&buf[0])), // pointer to first element in result buffer + bufSize, // size of the buffer in memory + uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this + ) + if retCode != 0 { + return nil, fmt.Errorf("call to NtQuerySystemInformationEx(group=%d) returned 0x%x: %w", g, retCode, err) + } + // Guard against a retSize that is not a whole number of entries or exceeds + // the allocated buffer (e.g. CPU hot-add racing with GetActiveProcessorCount). + if retSize%win32_SystemProcessorPerformanceInfoSize != 0 || uintptr(retSize) > bufSize { + return nil, fmt.Errorf("NtQuerySystemInformationEx(group=%d) returned unexpected retSize=%d (bufSize=%d)", g, retSize, bufSize) + } + n := retSize / win32_SystemProcessorPerformanceInfoSize + result = append(result, buf[:n]...) + } + return result, nil } // SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API. diff --git a/vendor/github.com/shirou/gopsutil/v4/internal/common/common_windows.go b/vendor/github.com/shirou/gopsutil/v4/internal/common/common_windows.go index 31df6efe9bf..3778e4d95eb 100644 --- a/vendor/github.com/shirou/gopsutil/v4/internal/common/common_windows.go +++ b/vendor/github.com/shirou/gopsutil/v4/internal/common/common_windows.go @@ -73,6 +73,7 @@ var ( ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes") ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation") + ProcNtQuerySystemInformationEx = ModNt.NewProc("NtQuerySystemInformationEx") ProcRtlGetNativeSystemInformation = ModNt.NewProc("RtlGetNativeSystemInformation") ProcRtlNtStatusToDosError = ModNt.NewProc("RtlNtStatusToDosError") ProcNtQueryInformationProcess = ModNt.NewProc("NtQueryInformationProcess") diff --git a/vendor/github.com/shirou/gopsutil/v4/mem/mem_netbsd.go b/vendor/github.com/shirou/gopsutil/v4/mem/mem_netbsd.go index 8ef539ca321..2efa1199cc8 100644 --- a/vendor/github.com/shirou/gopsutil/v4/mem/mem_netbsd.go +++ b/vendor/github.com/shirou/gopsutil/v4/mem/mem_netbsd.go @@ -61,6 +61,7 @@ func SwapMemory() (*SwapMemoryStat, error) { return SwapMemoryWithContext(context.Background()) } +// Reference: https://man.netbsd.org/swapctl.8 func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { out, err := invoke.CommandWithContext(ctx, "swapctl", "-sk") if err != nil { @@ -71,7 +72,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { var total, used, free uint64 _, err = fmt.Sscanf(line, - "total: %d 1K-blocks allocated, %d used, %d available", + "total: %d KBytes allocated, %d KBytes used, %d KBytes available", &total, &used, &free) if err != nil { return nil, errors.New("failed to parse swapctl output") diff --git a/vendor/github.com/shirou/gopsutil/v4/net/net_aix.go b/vendor/github.com/shirou/gopsutil/v4/net/net_aix.go index 4531dd4449b..9f2e145f078 100644 --- a/vendor/github.com/shirou/gopsutil/v4/net/net_aix.go +++ b/vendor/github.com/shirou/gopsutil/v4/net/net_aix.go @@ -31,8 +31,240 @@ func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, erro return nil, common.ErrNotImplementedError } -func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) { - return nil, common.ErrNotImplementedError +func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) { + out, err := invoke.CommandWithContext(ctx, "netstat", "-s") + if err != nil { + return nil, err + } + return parseNetstatS(string(out), protocols) +} + +// parseNetstatS parses "netstat -s" output on AIX. +// +// Format: +// +// : +// \t +// \t\t +// +// Descriptions containing parenthetical sub-counts (e.g. "(6302116893 bytes)") +// are normalised by stripping the parenthetical before matching. +func parseNetstatS(output string, protocols []string) ([]ProtoCountersStat, error) { + var stats []ProtoCountersStat + var currentProto string + var currentStats map[string]int64 + + for _, line := range strings.Split(output, "\n") { + // Protocol header: no leading whitespace, ends with ":" + if line != "" && line[0] != '\t' { + if currentProto != "" && len(currentStats) > 0 { + if len(protocols) == 0 || common.StringsHas(protocols, currentProto) { + stats = append(stats, ProtoCountersStat{ + Protocol: currentProto, + Stats: currentStats, + }) + } + } + currentProto = strings.TrimSuffix(strings.TrimSpace(line), ":") + currentStats = make(map[string]int64) + continue + } + + if currentProto == "" { + continue + } + + // Count leading tabs to track indentation depth (1 = top-level metric). + depth := 0 + rest := line + for rest != "" && rest[0] == '\t' { + depth++ + rest = rest[1:] + } + if depth == 0 || rest == "" { + continue + } + + // Split " ". + spaceIdx := strings.IndexByte(rest, ' ') + if spaceIdx <= 0 { + continue + } + val, err := strconv.ParseInt(rest[:spaceIdx], 10, 64) + if err != nil { + continue + } + // Normalise: remove parenthetical sub-counts like "(6302116893 bytes)". + desc := normaliseNetstatDesc(strings.TrimSpace(rest[spaceIdx+1:])) + + if key := aixProtoKey(currentProto, depth, desc); key != "" { + currentStats[key] += val + } + } + + if currentProto != "" && len(currentStats) > 0 { + if len(protocols) == 0 || common.StringsHas(protocols, currentProto) { + stats = append(stats, ProtoCountersStat{ + Protocol: currentProto, + Stats: currentStats, + }) + } + } + + return stats, nil +} + +// normaliseNetstatDesc strips a single parenthetical from a netstat -s description, +// e.g. "data packets (6302116893 bytes) retransmitted" → "data packets retransmitted". +func normaliseNetstatDesc(s string) string { + start := strings.Index(s, "(") + if start == -1 { + return s + } + end := strings.LastIndex(s, ")") + if end < start { + return s + } + return strings.Join(strings.Fields(s[:start]+s[end+1:]), " ") +} + +// aixProtoKey maps a normalised AIX netstat -s description to a ProtoCountersStat key. +// depth is the tab-indentation level (1 = top-level line under the protocol header). +// Returns "" for lines that should be ignored. +func aixProtoKey(proto string, depth int, desc string) string { + switch proto { + case "tcp": + return aixTCPKey(depth, desc) + case "udp": + return aixUDPKey(desc) + case "ip": + return aixIPKey(depth, desc) + case "ipv6": + return aixIPv6Key(depth, desc) + } + return "" +} + +func aixTCPKey(depth int, desc string) string { + switch { + // Top-level totals — depth check avoids matching sub-lines like "N data packets sent". + case depth == 1 && desc == "packets sent": + return "OutSegs" + case depth == 1 && desc == "packets received": + return "InSegs" + // Sub-line: "data packets NNN bytes retransmitted" → normalised "data packets retransmitted" + case strings.Contains(desc, "retransmitted"): + return "RetransSegs" + case desc == "connection requests": + return "ActiveOpens" + case desc == "connection accepts": + return "PassiveOpens" + case desc == "embryonic connections dropped": + return "AttemptFails" + case strings.HasPrefix(desc, "discarded for bad checksums"): + return "InCsumErrors" + // Other input errors accumulate into InErrs. + case strings.HasPrefix(desc, "discarded for bad header") || + strings.HasPrefix(desc, "discarded because packet too short"): + return "InErrs" + } + return "" +} + +func aixUDPKey(desc string) string { + switch { + case desc == "delivered": + return "InDatagrams" + // Both unicast and broadcast "dropped due to no socket" map to NoPorts. + case strings.Contains(desc, "dropped due to no socket"): + return "NoPorts" + case desc == "bad checksums": + return "InCsumErrors" + case desc == "socket buffer overflows": + return "RcvbufErrors" + case desc == "datagrams output": + return "OutDatagrams" + case desc == "incomplete headers" || desc == "bad data length fields": + return "InErrors" + } + return "" +} + +func aixIPKey(depth int, desc string) string { + switch { + case desc == "total packets received": + return "InReceives" + // All header-error variants accumulate into InHdrErrors. + case desc == "bad header checksums" || + desc == "with size smaller than minimum" || + desc == "with data size < data length" || + desc == "with header length < data size" || + desc == "with data length < header length" || + desc == "with bad options" || + desc == "with incorrect version number": + return "InHdrErrors" + case strings.Contains(desc, "unknown/unsupported protocol"): + return "InUnknownProtos" + case desc == "packets for this host": + return "InDelivers" + case depth == 1 && desc == "packets forwarded": + return "ForwDatagrams" + case desc == "packets sent from this host": + return "OutRequests" + case desc == "packets reassembled ok": + return "ReasmOKs" + case strings.HasPrefix(desc, "fragments dropped after timeout"): + return "ReasmFails" + case desc == "fragments received": + return "ReasmReqds" + case strings.HasPrefix(desc, "fragments dropped"): + return "InDiscards" + case desc == "fragments created": + return "FragCreates" + case desc == "output packets discarded due to no route": + return "OutNoRoutes" + case strings.HasPrefix(desc, "output packets dropped due to no bufs"): + return "OutDiscards" + } + return "" +} + +func aixIPv6Key(depth int, desc string) string { + switch { + case desc == "total packets received": + return "InReceives" + case desc == "with size smaller than minimum" || + desc == "with data size < data length" || + desc == "with incorrect version number" || + desc == "with illegal source": + return "InHdrErrors" + case strings.Contains(desc, "unknown/unsupported protocol"): + return "InUnknownProtos" + case desc == "input packets without enough memory": + return "InDiscards" + case desc == "packets for this host": + return "InDelivers" + case depth == 1 && desc == "packets forwarded": + return "ForwDatagrams" + case desc == "packets sent from this host": + return "OutRequests" + case desc == "packets reassembled ok": + return "ReasmOKs" + case strings.HasPrefix(desc, "fragments dropped after timeout"): + return "ReasmFails" + case desc == "fragments received": + return "ReasmReqds" + case strings.HasPrefix(desc, "fragments dropped"): + return "InDiscards" + case desc == "fragments created": + return "FragCreates" + case desc == "output packets discarded due to no route": + return "OutNoRoutes" + case strings.HasPrefix(desc, "output packets dropped due to no bufs") || + desc == "output packets without enough memory": + return "OutDiscards" + } + return "" } func parseNetstatNetLine(line string) (ConnectionStat, error) { diff --git a/vendor/github.com/shirou/gopsutil/v4/net/net_aix_nocgo.go b/vendor/github.com/shirou/gopsutil/v4/net/net_aix_nocgo.go index 834534d34c4..27d37a99c8b 100644 --- a/vendor/github.com/shirou/gopsutil/v4/net/net_aix_nocgo.go +++ b/vendor/github.com/shirou/gopsutil/v4/net/net_aix_nocgo.go @@ -78,6 +78,35 @@ func parseNetstatI(output string) ([]IOCountersStat, error) { return ret, nil } +// parseEntstat extracts BytesSent and BytesRecv from entstat output. +// The entstat two-column Transmit/Receive format (including the "Bytes:" +// line) has been stable across AIX 4.3 through 7.3 (over 25 years). +// The output has a two-column layout with Transmit on the left and Receive +// on the right, e.g.: +// +// Bytes: 3509236040 Bytes: 4547812126 +func parseEntstat(output string) (bytesSent, bytesRecv uint64) { + for _, line := range strings.Split(output, "\n") { + if !strings.Contains(line, "Bytes:") { + continue + } + // Split on "Bytes:" to get: ["", " 3509236040 ", " 4547812126"] + parts := strings.Split(line, "Bytes:") + if len(parts) >= 2 { + if v, err := strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 64); err == nil { + bytesSent = v + } + } + if len(parts) >= 3 { + if v, err := strconv.ParseUint(strings.TrimSpace(parts[2]), 10, 64); err == nil { + bytesRecv = v + } + } + return + } + return +} + func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) { out, err := invoke.CommandWithContext(ctx, "netstat", "-idn") if err != nil { @@ -88,6 +117,24 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, if err != nil { return nil, err } + + // Populate BytesSent/BytesRecv via entstat for each interface. + // entstat only works on hardware/virtual ethernet adapters; it fails + // on loopback (lo0) with errno 19, which is silently skipped. Errors + // on other interfaces are propagated since they indicate a real problem. + for i := range iocounters { + entOut, err := invoke.CommandWithContext(ctx, "entstat", iocounters[i].Name) + if err != nil { + // entstat fails on loopback (lo0) with errno 19 — this is expected. + // For other interfaces, propagate the error. + if iocounters[i].Name == "lo0" { + continue + } + return nil, err + } + iocounters[i].BytesSent, iocounters[i].BytesRecv = parseEntstat(string(entOut)) + } + if !pernic { return getIOCountersAll(iocounters), nil } diff --git a/vendor/github.com/shirou/gopsutil/v4/net/net_fallback.go b/vendor/github.com/shirou/gopsutil/v4/net/net_fallback.go index 29c2a148ef5..0c0e57da3a4 100644 --- a/vendor/github.com/shirou/gopsutil/v4/net/net_fallback.go +++ b/vendor/github.com/shirou/gopsutil/v4/net/net_fallback.go @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows && !solaris +//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows && !solaris && !netbsd package net diff --git a/vendor/github.com/shirou/gopsutil/v4/net/net_linux.go b/vendor/github.com/shirou/gopsutil/v4/net/net_linux.go index a3dd17aac1a..96a78ff8053 100644 --- a/vendor/github.com/shirou/gopsutil/v4/net/net_linux.go +++ b/vendor/github.com/shirou/gopsutil/v4/net/net_linux.go @@ -69,6 +69,9 @@ func IOCountersByFileWithContext(_ context.Context, pernic bool, filename string } fields := strings.Fields(strings.TrimSpace(statsPart)) + if len(fields) < 13 { + continue + } bytesRecv, err := strconv.ParseUint(fields[0], 10, 64) if err != nil { return ret, err diff --git a/vendor/github.com/shirou/gopsutil/v4/net/net_netbsd.go b/vendor/github.com/shirou/gopsutil/v4/net/net_netbsd.go new file mode 100644 index 00000000000..d0e44fbf2b2 --- /dev/null +++ b/vendor/github.com/shirou/gopsutil/v4/net/net_netbsd.go @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build netbsd + +package net + +import ( + "context" + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" + "syscall" + + "github.com/shirou/gopsutil/v4/internal/common" +) + +var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`) + +// parseNetstat parses the output of "netstat -inb" (mode "inb") or +// "netstat -ind" (mode "ind") and merges results into iocs. +// +// NetBSD netstat column layout (0-indexed fields after strings.Fields): +// +// -inb with Address (6 fields): Name Mtu Network Address Ibytes Obytes +// -inb without Address (5 fields): Name Mtu Network Ibytes Obytes +// +// -ind with Address (11 fields): Name Mtu Network Address Ipkts Ierrs Idrops Opkts Oerrs Colls Odrops +// -ind without Address (10 fields): Name Mtu Network Ipkts Ierrs Idrops Opkts Oerrs Colls Odrops +// +// The Address field is present for non-loopback interfaces and absent for +// loopback (lo0). We detect this via field count and set base accordingly. +// +// Reference: https://man.netbsd.org/netstat.1 +func parseNetstat(output, mode string, iocs map[string]IOCountersStat) error { + // Minimum field counts when Address is absent. + minFields := map[string]int{ + "inb": 5, + "ind": 10, + } + // Field count when Address is present (base = 1). + addrFields := map[string]int{ + "inb": 6, + "ind": 11, + } + + seen := make([]string, 0) + + for _, line := range strings.Split(output, "\n") { + values := strings.Fields(line) + if len(values) < 1 || values[0] == "Name" { + continue + } + if common.StringsHas(seen, values[0]) { + continue + } + if len(values) < minFields[mode] { + continue + } + + base := 0 + if len(values) >= addrFields[mode] { + base = 1 + } + + seen = append(seen, values[0]) + + n, present := iocs[values[0]] + if !present { + n = IOCountersStat{Name: values[0]} + } + + switch mode { + case "inb": + recv, err := parseUint(values[base+3]) + if err != nil { + return err + } + sent, err := parseUint(values[base+4]) + if err != nil { + return err + } + n.BytesRecv = recv + n.BytesSent = sent + + case "ind": + // Ipkts Ierrs Idrops Opkts Oerrs Colls Odrops + pktsRecv, err := parseUint(values[base+3]) + if err != nil { + return err + } + errin, err := parseUint(values[base+4]) + if err != nil { + return err + } + dropin, err := parseUint(values[base+5]) + if err != nil { + return err + } + pktsSent, err := parseUint(values[base+6]) + if err != nil { + return err + } + errout, err := parseUint(values[base+7]) + if err != nil { + return err + } + // values[base+8] = Colls (not mapped to IOCountersStat) + dropout, err := parseUint(values[base+9]) + if err != nil { + return err + } + n.PacketsRecv = pktsRecv + n.Errin = errin + n.Dropin = dropin + n.PacketsSent = pktsSent + n.Errout = errout + n.Dropout = dropout + } + + iocs[n.Name] = n + } + return nil +} + +func parseUint(s string) (uint64, error) { + if s == "-" { + return 0, nil + } + return strconv.ParseUint(s, 10, 64) +} + +// Deprecated: use process.PidsWithContext instead +func PidsWithContext(_ context.Context) ([]int32, error) { + return nil, common.ErrNotImplementedError +} + +func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) { + netstat, err := exec.LookPath("netstat") + if err != nil { + return nil, err + } + + outBytes, err := invoke.CommandWithContext(ctx, netstat, "-inb") + if err != nil { + return nil, err + } + outPackets, err := invoke.CommandWithContext(ctx, netstat, "-ind") + if err != nil { + return nil, err + } + + iocs := make(map[string]IOCountersStat) + + if err := parseNetstat(string(outBytes), "inb", iocs); err != nil { + return nil, err + } + if err := parseNetstat(string(outPackets), "ind", iocs); err != nil { + return nil, err + } + + ret := make([]IOCountersStat, 0, len(iocs)) + for _, ioc := range iocs { + ret = append(ret, ioc) + } + + if !pernic { + return getIOCountersAll(ret), nil + } + + return ret, nil +} + +func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) { + return IOCountersWithContext(ctx, pernic) +} + +func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) { + return nil, common.ErrNotImplementedError +} + +func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) { + return nil, common.ErrNotImplementedError +} + +func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) { + return nil, common.ErrNotImplementedError +} + +func parseNetstatLine(line string) (ConnectionStat, error) { + f := strings.Fields(line) + if len(f) < 5 { + return ConnectionStat{}, fmt.Errorf("wrong line,%s", line) + } + + var netType, netFamily uint32 + switch f[0] { + case "tcp": + netType = syscall.SOCK_STREAM + netFamily = syscall.AF_INET + case "udp": + netType = syscall.SOCK_DGRAM + netFamily = syscall.AF_INET + case "tcp6": + netType = syscall.SOCK_STREAM + netFamily = syscall.AF_INET6 + case "udp6": + netType = syscall.SOCK_DGRAM + netFamily = syscall.AF_INET6 + default: + return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0]) + } + + laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily) + if err != nil { + return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4]) + } + + n := ConnectionStat{ + Fd: uint32(0), // not supported + Family: uint32(netFamily), + Type: uint32(netType), + Laddr: laddr, + Raddr: raddr, + Pid: int32(0), // not supported + } + if len(f) == 6 { + n.Status = f[5] + } + + return n, nil +} + +func parseAddr(l string, family uint32) (Addr, error) { + matches := portMatch.FindStringSubmatch(l) + if matches == nil { + return Addr{}, fmt.Errorf("wrong addr, %s", l) + } + host := matches[1] + port := matches[2] + if host == "*" { + switch family { + case syscall.AF_INET: + host = "0.0.0.0" + case syscall.AF_INET6: + host = "::" + default: + return Addr{}, fmt.Errorf("unknown family, %d", family) + } + } + lport, err := strconv.ParseInt(port, 10, 32) + if err != nil { + return Addr{}, err + } + return Addr{IP: host, Port: uint32(lport)}, nil +} + +func parseNetstatAddr(local, remote string, family uint32) (laddr, raddr Addr, err error) { + laddr, err = parseAddr(local, family) + if err != nil { + return laddr, raddr, err + } + if remote != "*.*" { + raddr, err = parseAddr(remote, family) + if err != nil { + return laddr, raddr, err + } + } + return laddr, raddr, err +} + +func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) { + var ret []ConnectionStat + + args := []string{"-na"} + switch strings.ToLower(kind) { + default: + fallthrough + case "", "all", "inet": + // nothing to add + case "inet4": + args = append(args, "-finet") + case "inet6": + args = append(args, "-finet6") + case "tcp": + args = append(args, "-ptcp") + case "tcp4": + args = append(args, "-ptcp", "-finet") + case "tcp6": + args = append(args, "-ptcp", "-finet6") + case "udp": + args = append(args, "-pudp") + case "udp4": + args = append(args, "-pudp", "-finet") + case "udp6": + args = append(args, "-pudp", "-finet6") + case "unix": + return ret, common.ErrNotImplementedError + } + + netstat, err := exec.LookPath("netstat") + if err != nil { + return nil, err + } + out, err := invoke.CommandWithContext(ctx, netstat, args...) + if err != nil { + return nil, err + } + for _, line := range strings.Split(string(out), "\n") { + if !strings.HasPrefix(line, "tcp") && !strings.HasPrefix(line, "udp") { + continue + } + n, err := parseNetstatLine(line) + if err != nil { + continue + } + ret = append(ret, n) + } + + return ret, nil +} + +func ConnectionsPidWithContext(_ context.Context, _ string, _ int32) ([]ConnectionStat, error) { + return nil, common.ErrNotImplementedError +} + +func ConnectionsMaxWithContext(_ context.Context, _ string, _ int) ([]ConnectionStat, error) { + return nil, common.ErrNotImplementedError +} + +func ConnectionsPidMaxWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) { + return nil, common.ErrNotImplementedError +} + +func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) { + return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0) +} + +func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) { + return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn) +} + +func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) { + return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0) +} + +func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) { + return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn) +} + +func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) { + return nil, common.ErrNotImplementedError +} diff --git a/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go b/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go index 764523dcf7d..dc2f03bb5f4 100644 --- a/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go +++ b/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go @@ -417,8 +417,7 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M if len(field) < 2 { continue } - v := strings.Trim(field[1], "kB") // remove last "kB" - v = strings.TrimSpace(v) + v := strings.TrimSpace(strings.TrimSuffix(field[1], " kB")) t, err := strconv.ParseUint(v, 10, 64) if err != nil { return m, err @@ -924,49 +923,49 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error { } p.numCtxSwitches.Involuntary = v case "VmRSS": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.RSS = v * 1024 case "VmSize": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.VMS = v * 1024 case "VmSwap": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Swap = v * 1024 case "VmHWM": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.HWM = v * 1024 case "VmData": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Data = v * 1024 case "VmStk": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Stack = v * 1024 case "VmLck": - value = strings.Trim(value, " kB") // remove last "kB" + value = strings.TrimSpace(strings.TrimSuffix(value, " kB")) v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err @@ -1046,6 +1045,9 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui } // Indexing from one, as described in `man proc` about the file /proc/[pid]/stat fields := splitProcStat(contents) + if len(fields) < 23 { + return 0, 0, nil, 0, 0, 0, nil, fmt.Errorf("malformed stat file: expected at least 23 fields, got %d", len(fields)) + } terminal, err := strconv.ParseUint(fields[7], 10, 64) if err != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index 7b43842a6f8..5fd18fe1d45 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1633,7 +1633,7 @@ github.com/sercand/kuberesolver/v6 # github.com/sethvargo/go-retry v0.3.0 ## explicit; go 1.21 github.com/sethvargo/go-retry -# github.com/shirou/gopsutil/v4 v4.26.3 +# github.com/shirou/gopsutil/v4 v4.26.4 ## explicit; go 1.24.0 github.com/shirou/gopsutil/v4/common github.com/shirou/gopsutil/v4/cpu