Skip to content

Commit b693c36

Browse files
authored
Merge pull request #2077 from fraggerfox/fix-netbsd-stats
Fix NetBSD mem / net stats
2 parents 20b5fc6 + 25fe401 commit b693c36

6 files changed

Lines changed: 437 additions & 2 deletions

File tree

mem/mem_netbsd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func SwapMemory() (*SwapMemoryStat, error) {
6161
return SwapMemoryWithContext(context.Background())
6262
}
6363

64+
// Reference: https://man.netbsd.org/swapctl.8
6465
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
6566
out, err := invoke.CommandWithContext(ctx, "swapctl", "-sk")
6667
if err != nil {
@@ -71,7 +72,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
7172
var total, used, free uint64
7273

7374
_, err = fmt.Sscanf(line,
74-
"total: %d 1K-blocks allocated, %d used, %d available",
75+
"total: %d KBytes allocated, %d KBytes used, %d KBytes available",
7576
&total, &used, &free)
7677
if err != nil {
7778
return nil, errors.New("failed to parse swapctl output")

net/net_fallback.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BSD-3-Clause
2-
//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows && !solaris
2+
//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows && !solaris && !netbsd
33

44
package net
55

net/net_netbsd.go

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//go:build netbsd
3+
4+
package net
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os/exec"
10+
"regexp"
11+
"strconv"
12+
"strings"
13+
"syscall"
14+
15+
"github.com/shirou/gopsutil/v4/internal/common"
16+
)
17+
18+
var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`)
19+
20+
// parseNetstat parses the output of "netstat -inb" (mode "inb") or
21+
// "netstat -ind" (mode "ind") and merges results into iocs.
22+
//
23+
// NetBSD netstat column layout (0-indexed fields after strings.Fields):
24+
//
25+
// -inb with Address (6 fields): Name Mtu Network Address Ibytes Obytes
26+
// -inb without Address (5 fields): Name Mtu Network Ibytes Obytes
27+
//
28+
// -ind with Address (11 fields): Name Mtu Network Address Ipkts Ierrs Idrops Opkts Oerrs Colls Odrops
29+
// -ind without Address (10 fields): Name Mtu Network Ipkts Ierrs Idrops Opkts Oerrs Colls Odrops
30+
//
31+
// The Address field is present for non-loopback interfaces and absent for
32+
// loopback (lo0). We detect this via field count and set base accordingly.
33+
//
34+
// Reference: https://man.netbsd.org/netstat.1
35+
func parseNetstat(output, mode string, iocs map[string]IOCountersStat) error {
36+
// Minimum field counts when Address is absent.
37+
minFields := map[string]int{
38+
"inb": 5,
39+
"ind": 10,
40+
}
41+
// Field count when Address is present (base = 1).
42+
addrFields := map[string]int{
43+
"inb": 6,
44+
"ind": 11,
45+
}
46+
47+
seen := make([]string, 0)
48+
49+
for _, line := range strings.Split(output, "\n") {
50+
values := strings.Fields(line)
51+
if len(values) < 1 || values[0] == "Name" {
52+
continue
53+
}
54+
if common.StringsHas(seen, values[0]) {
55+
continue
56+
}
57+
if len(values) < minFields[mode] {
58+
continue
59+
}
60+
61+
base := 0
62+
if len(values) >= addrFields[mode] {
63+
base = 1
64+
}
65+
66+
seen = append(seen, values[0])
67+
68+
n, present := iocs[values[0]]
69+
if !present {
70+
n = IOCountersStat{Name: values[0]}
71+
}
72+
73+
switch mode {
74+
case "inb":
75+
recv, err := parseUint(values[base+3])
76+
if err != nil {
77+
return err
78+
}
79+
sent, err := parseUint(values[base+4])
80+
if err != nil {
81+
return err
82+
}
83+
n.BytesRecv = recv
84+
n.BytesSent = sent
85+
86+
case "ind":
87+
// Ipkts Ierrs Idrops Opkts Oerrs Colls Odrops
88+
pktsRecv, err := parseUint(values[base+3])
89+
if err != nil {
90+
return err
91+
}
92+
errin, err := parseUint(values[base+4])
93+
if err != nil {
94+
return err
95+
}
96+
dropin, err := parseUint(values[base+5])
97+
if err != nil {
98+
return err
99+
}
100+
pktsSent, err := parseUint(values[base+6])
101+
if err != nil {
102+
return err
103+
}
104+
errout, err := parseUint(values[base+7])
105+
if err != nil {
106+
return err
107+
}
108+
// values[base+8] = Colls (not mapped to IOCountersStat)
109+
dropout, err := parseUint(values[base+9])
110+
if err != nil {
111+
return err
112+
}
113+
n.PacketsRecv = pktsRecv
114+
n.Errin = errin
115+
n.Dropin = dropin
116+
n.PacketsSent = pktsSent
117+
n.Errout = errout
118+
n.Dropout = dropout
119+
}
120+
121+
iocs[n.Name] = n
122+
}
123+
return nil
124+
}
125+
126+
func parseUint(s string) (uint64, error) {
127+
if s == "-" {
128+
return 0, nil
129+
}
130+
return strconv.ParseUint(s, 10, 64)
131+
}
132+
133+
// Deprecated: use process.PidsWithContext instead
134+
func PidsWithContext(_ context.Context) ([]int32, error) {
135+
return nil, common.ErrNotImplementedError
136+
}
137+
138+
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
139+
netstat, err := exec.LookPath("netstat")
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
outBytes, err := invoke.CommandWithContext(ctx, netstat, "-inb")
145+
if err != nil {
146+
return nil, err
147+
}
148+
outPackets, err := invoke.CommandWithContext(ctx, netstat, "-ind")
149+
if err != nil {
150+
return nil, err
151+
}
152+
153+
iocs := make(map[string]IOCountersStat)
154+
155+
if err := parseNetstat(string(outBytes), "inb", iocs); err != nil {
156+
return nil, err
157+
}
158+
if err := parseNetstat(string(outPackets), "ind", iocs); err != nil {
159+
return nil, err
160+
}
161+
162+
ret := make([]IOCountersStat, 0, len(iocs))
163+
for _, ioc := range iocs {
164+
ret = append(ret, ioc)
165+
}
166+
167+
if !pernic {
168+
return getIOCountersAll(ret), nil
169+
}
170+
171+
return ret, nil
172+
}
173+
174+
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
175+
return IOCountersWithContext(ctx, pernic)
176+
}
177+
178+
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
179+
return nil, common.ErrNotImplementedError
180+
}
181+
182+
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
183+
return nil, common.ErrNotImplementedError
184+
}
185+
186+
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
187+
return nil, common.ErrNotImplementedError
188+
}
189+
190+
func parseNetstatLine(line string) (ConnectionStat, error) {
191+
f := strings.Fields(line)
192+
if len(f) < 5 {
193+
return ConnectionStat{}, fmt.Errorf("wrong line,%s", line)
194+
}
195+
196+
var netType, netFamily uint32
197+
switch f[0] {
198+
case "tcp":
199+
netType = syscall.SOCK_STREAM
200+
netFamily = syscall.AF_INET
201+
case "udp":
202+
netType = syscall.SOCK_DGRAM
203+
netFamily = syscall.AF_INET
204+
case "tcp6":
205+
netType = syscall.SOCK_STREAM
206+
netFamily = syscall.AF_INET6
207+
case "udp6":
208+
netType = syscall.SOCK_DGRAM
209+
netFamily = syscall.AF_INET6
210+
default:
211+
return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0])
212+
}
213+
214+
laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily)
215+
if err != nil {
216+
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4])
217+
}
218+
219+
n := ConnectionStat{
220+
Fd: uint32(0), // not supported
221+
Family: uint32(netFamily),
222+
Type: uint32(netType),
223+
Laddr: laddr,
224+
Raddr: raddr,
225+
Pid: int32(0), // not supported
226+
}
227+
if len(f) == 6 {
228+
n.Status = f[5]
229+
}
230+
231+
return n, nil
232+
}
233+
234+
func parseAddr(l string, family uint32) (Addr, error) {
235+
matches := portMatch.FindStringSubmatch(l)
236+
if matches == nil {
237+
return Addr{}, fmt.Errorf("wrong addr, %s", l)
238+
}
239+
host := matches[1]
240+
port := matches[2]
241+
if host == "*" {
242+
switch family {
243+
case syscall.AF_INET:
244+
host = "0.0.0.0"
245+
case syscall.AF_INET6:
246+
host = "::"
247+
default:
248+
return Addr{}, fmt.Errorf("unknown family, %d", family)
249+
}
250+
}
251+
lport, err := strconv.ParseInt(port, 10, 32)
252+
if err != nil {
253+
return Addr{}, err
254+
}
255+
return Addr{IP: host, Port: uint32(lport)}, nil
256+
}
257+
258+
func parseNetstatAddr(local, remote string, family uint32) (laddr, raddr Addr, err error) {
259+
laddr, err = parseAddr(local, family)
260+
if err != nil {
261+
return laddr, raddr, err
262+
}
263+
if remote != "*.*" {
264+
raddr, err = parseAddr(remote, family)
265+
if err != nil {
266+
return laddr, raddr, err
267+
}
268+
}
269+
return laddr, raddr, err
270+
}
271+
272+
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
273+
var ret []ConnectionStat
274+
275+
args := []string{"-na"}
276+
switch strings.ToLower(kind) {
277+
default:
278+
fallthrough
279+
case "", "all", "inet":
280+
// nothing to add
281+
case "inet4":
282+
args = append(args, "-finet")
283+
case "inet6":
284+
args = append(args, "-finet6")
285+
case "tcp":
286+
args = append(args, "-ptcp")
287+
case "tcp4":
288+
args = append(args, "-ptcp", "-finet")
289+
case "tcp6":
290+
args = append(args, "-ptcp", "-finet6")
291+
case "udp":
292+
args = append(args, "-pudp")
293+
case "udp4":
294+
args = append(args, "-pudp", "-finet")
295+
case "udp6":
296+
args = append(args, "-pudp", "-finet6")
297+
case "unix":
298+
return ret, common.ErrNotImplementedError
299+
}
300+
301+
netstat, err := exec.LookPath("netstat")
302+
if err != nil {
303+
return nil, err
304+
}
305+
out, err := invoke.CommandWithContext(ctx, netstat, args...)
306+
if err != nil {
307+
return nil, err
308+
}
309+
for _, line := range strings.Split(string(out), "\n") {
310+
if !strings.HasPrefix(line, "tcp") && !strings.HasPrefix(line, "udp") {
311+
continue
312+
}
313+
n, err := parseNetstatLine(line)
314+
if err != nil {
315+
continue
316+
}
317+
ret = append(ret, n)
318+
}
319+
320+
return ret, nil
321+
}
322+
323+
func ConnectionsPidWithContext(_ context.Context, _ string, _ int32) ([]ConnectionStat, error) {
324+
return nil, common.ErrNotImplementedError
325+
}
326+
327+
func ConnectionsMaxWithContext(_ context.Context, _ string, _ int) ([]ConnectionStat, error) {
328+
return nil, common.ErrNotImplementedError
329+
}
330+
331+
func ConnectionsPidMaxWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) {
332+
return nil, common.ErrNotImplementedError
333+
}
334+
335+
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
336+
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
337+
}
338+
339+
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
340+
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
341+
}
342+
343+
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
344+
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
345+
}
346+
347+
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
348+
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn)
349+
}
350+
351+
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) {
352+
return nil, common.ErrNotImplementedError
353+
}

0 commit comments

Comments
 (0)