Skip to content

Commit 77607f6

Browse files
derekmarcotteSuperQ
authored andcommitted
Correct buffer_bytes > INT_MAX on BSD/amd64. (#712)
* Correct buffer_bytes > INT_MAX on BSD/amd64. The sysctl vfs.bufspace returns either an int or a long, depending on the value. Large values of vfs.bufspace will result in error messages like: couldn't get meminfo: cannot allocate memory This will detect the returned data type, and cast appropriately. * Added explicit length checks per feedback. * Flatten Value() to make it easier to read. * Simplify per feedback. * Fix style. * Doc updates.
1 parent c0e20dc commit 77607f6

File tree

2 files changed

+73
-38
lines changed

2 files changed

+73
-38
lines changed

collector/meminfo_bsd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (c *meminfoCollector) getMemInfo() (map[string]float64, error) {
3939
{name: "inactive_bytes", mib: "vm.stats.vm.v_inactive_count", conversion: fromPage},
4040
{name: "wired_bytes", mib: "vm.stats.vm.v_wire_count", conversion: fromPage},
4141
{name: "cache_bytes", mib: "vm.stats.vm.v_cache_count", conversion: fromPage},
42-
{name: "buffer_bytes", mib: "vfs.bufspace"},
42+
{name: "buffer_bytes", mib: "vfs.bufspace", dataType: bsdSysctlTypeCLong},
4343
{name: "free_bytes", mib: "vm.stats.vm.v_free_count", conversion: fromPage},
4444
{name: "size_bytes", mib: "vm.stats.vm.v_page_count", conversion: fromPage},
4545
{name: "swap_in_bytes_total", mib: "vm.stats.vm.v_swappgsin", conversion: fromPage},

collector/sysctl_bsd.go

+72-37
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ const (
3535
bsdSysctlTypeUint32 bsdSysctlType = iota
3636
bsdSysctlTypeUint64
3737
bsdSysctlTypeStructTimeval
38+
bsdSysctlTypeCLong
3839
)
3940

40-
// Contains all the info needed to map a single bsd-sysctl to a prometheus value.
41+
// Contains all the info needed to map a single bsd-sysctl to a prometheus
42+
// value.
4143
type bsdSysctl struct {
4244
// Prometheus name
4345
name string
@@ -72,42 +74,9 @@ func (b bsdSysctl) Value() (float64, error) {
7274
tmp64, err = unix.SysctlUint64(b.mib)
7375
tmpf64 = float64(tmp64)
7476
case bsdSysctlTypeStructTimeval:
75-
raw, err := unix.SysctlRaw(b.mib)
76-
if err != nil {
77-
return 0, err
78-
}
79-
80-
/*
81-
* From 10.3-RELEASE sources:
82-
*
83-
* /usr/include/sys/_timeval.h:47
84-
* time_t tv_sec
85-
* suseconds_t tv_usec
86-
*
87-
* /usr/include/sys/_types.h:60
88-
* long __suseconds_t
89-
*
90-
* ... architecture dependent, via #ifdef:
91-
* typedef __int64_t __time_t;
92-
* typedef __int32_t __time_t;
93-
*/
94-
if len(raw) != (C.sizeof_time_t + C.sizeof_suseconds_t) {
95-
// Shouldn't get here, unless the ABI changes...
96-
return 0, fmt.Errorf(
97-
"length of bytes received from sysctl (%d) does not match expected bytes (%d)",
98-
len(raw),
99-
C.sizeof_time_t+C.sizeof_suseconds_t,
100-
)
101-
}
102-
103-
secondsUp := unsafe.Pointer(&raw[0])
104-
susecondsUp := uintptr(secondsUp) + C.sizeof_time_t
105-
unix := float64(*(*C.time_t)(secondsUp))
106-
usec := float64(*(*C.suseconds_t)(unsafe.Pointer(susecondsUp)))
107-
108-
// This conversion maintains the usec precision. Using
109-
// the time package did not.
110-
tmpf64 = unix + (usec / float64(1000*1000))
77+
tmpf64, err = b.getStructTimeval()
78+
case bsdSysctlTypeCLong:
79+
tmpf64, err = b.getCLong()
11180
}
11281

11382
if err != nil {
@@ -120,3 +89,69 @@ func (b bsdSysctl) Value() (float64, error) {
12089

12190
return tmpf64, nil
12291
}
92+
93+
func (b bsdSysctl) getStructTimeval() (float64, error) {
94+
raw, err := unix.SysctlRaw(b.mib)
95+
if err != nil {
96+
return 0, err
97+
}
98+
99+
/*
100+
* From 10.3-RELEASE sources:
101+
*
102+
* /usr/include/sys/_timeval.h:47
103+
* time_t tv_sec
104+
* suseconds_t tv_usec
105+
*
106+
* /usr/include/sys/_types.h:60
107+
* long __suseconds_t
108+
*
109+
* ... architecture dependent, via #ifdef:
110+
* typedef __int64_t __time_t;
111+
* typedef __int32_t __time_t;
112+
*/
113+
if len(raw) != (C.sizeof_time_t + C.sizeof_suseconds_t) {
114+
// Shouldn't get here, unless the ABI changes...
115+
return 0, fmt.Errorf(
116+
"length of bytes received from sysctl (%d) does not match expected bytes (%d)",
117+
len(raw),
118+
C.sizeof_time_t+C.sizeof_suseconds_t,
119+
)
120+
}
121+
122+
secondsUp := unsafe.Pointer(&raw[0])
123+
susecondsUp := uintptr(secondsUp) + C.sizeof_time_t
124+
unix := float64(*(*C.time_t)(secondsUp))
125+
usec := float64(*(*C.suseconds_t)(unsafe.Pointer(susecondsUp)))
126+
127+
// This conversion maintains the usec precision. Using the time
128+
// package did not.
129+
return (unix + (usec / float64(1000*1000))), nil
130+
}
131+
132+
func (b bsdSysctl) getCLong() (float64, error) {
133+
raw, err := unix.SysctlRaw(b.mib)
134+
if err != nil {
135+
return 0, err
136+
}
137+
138+
if len(raw) == C.sizeof_long {
139+
return float64(*(*C.long)(unsafe.Pointer(&raw[0]))), nil
140+
}
141+
142+
if len(raw) == C.sizeof_int {
143+
// This is valid for at least vfs.bufspace, and the default
144+
// long handler - which can clamp longs to 32-bits:
145+
// https://github.com/freebsd/freebsd/blob/releng/10.3/sys/kern/vfs_bio.c#L338
146+
// https://github.com/freebsd/freebsd/blob/releng/10.3/sys/kern/kern_sysctl.c#L1062
147+
return float64(*(*C.int)(unsafe.Pointer(&raw[0]))), nil
148+
}
149+
150+
return 0, fmt.Errorf(
151+
"length of bytes received from sysctl (%d) does not match expected bytes (long: %d), (int: %d)",
152+
len(raw),
153+
C.sizeof_long,
154+
C.sizeof_int,
155+
)
156+
157+
}

0 commit comments

Comments
 (0)