Skip to content

Commit 78af2fb

Browse files
committed
process_collector: add a proof-of-concept for collecting RSS and VSIZE on macOS
This unfortunately uses cgo, which I think everyone agrees is not great. I dislike using it because it breaks cross-compiling, but this code allows opting out (at the cost of not publishing `process_resident_memory_bytes` and `process_virtual_memory_bytes`), by setting `CGO_ENABLED=0` in the environment. That's not ideal, but good enough for me where I can cross-compile locally most of the time without `cgo`, and enable it always on the CI server. But that may not be reasonable for other projects. Sadly, building my exporter on Linux with this complained about the presence of the *.c file without explicitly setting `CGO_ENABLED=0`, so the build tags aren't quite enough to do the right thing on the non-darwin platforms. Not sure if there's something that can be added to the *.c file to make that get ignored on non-darwin platforms. Otherwise, every client is on the hook for disabling cgo. I'm still looking into 3rd party modules to call this without cgo, but this code will help test out those implementations, and hopefully highlights the problems getting at these native APIs. The non-cgo implementation will get much more verbose because the structs in play each have a handful of fields, each of which with a train of typedefs that are platform specific. That said, even though the members may be typed slightly differently on amd64 vs arm64, it looks like the field widths are the same, with the minimal testing I did so far.
1 parent 055a68c commit 78af2fb

File tree

5 files changed

+106
-1
lines changed

5 files changed

+106
-1
lines changed

prometheus/native.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include <mach/mach_init.h>
2+
#include <mach/task.h>
3+
// Compiler warns shared_memory_server.h is deprecated, use this instead.
4+
// But this doesn't define SHARED_XXX_REGION_SIZE.
5+
//#include <mach/shared_region.h>
6+
#include <mach/shared_memory_server.h> // SHARED_DATA_REGION_SIZE, SHARED_TEXT_REGION_SIZE
7+
#include <mach/mach_vm.h>
8+
#include <stdio.h>
9+
10+
int getmem (unsigned long long *rss, unsigned long long *vs)
11+
{
12+
// https://github.com/apple-oss-distributions/adv_cmds/blob/8744084ea0ff41ca4bb96b0f9c22407d0e48e9b7/ps/tasks.c#L109
13+
14+
kern_return_t error;
15+
task_t task = MACH_PORT_NULL;
16+
struct task_basic_info t_info;
17+
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
18+
19+
error = task_info(mach_task_self(),
20+
TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
21+
22+
if( error != KERN_SUCCESS )
23+
{
24+
return error;
25+
}
26+
27+
*rss = t_info.resident_size;
28+
*vs = t_info.virtual_size;
29+
30+
{
31+
vm_region_basic_info_data_64_t b_info;
32+
mach_vm_address_t address = GLOBAL_SHARED_TEXT_SEGMENT;
33+
mach_vm_size_t size;
34+
mach_port_t object_name;
35+
36+
/*
37+
* try to determine if this task has the split libraries
38+
* mapped in... if so, adjust its virtual size down by
39+
* the 2 segments that are used for split libraries
40+
*/
41+
t_info_count = VM_REGION_BASIC_INFO_COUNT_64;
42+
error = mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO,
43+
(vm_region_info_t)&b_info, &t_info_count, &object_name);
44+
45+
if (error == KERN_SUCCESS) {
46+
if (b_info.reserved && size == (SHARED_TEXT_REGION_SIZE) &&
47+
*vs > (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE)) {
48+
*vs -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE);
49+
}
50+
}
51+
}
52+
53+
// `ps -o rss,vsize,command` prints values in KB
54+
printf("rss is %qd (%qd KB)\n", *rss, (unsigned long long) (*rss / 1024));
55+
printf("vs is %qd (%qd KB)\n", *vs, (unsigned long long) (*vs / 1024));
56+
57+
return 0;
58+
}

prometheus/process_collector.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ type processCollector struct {
3333
inBytes, outBytes *Desc
3434
}
3535

36+
func init() {
37+
// Debugging to show the correct implementation is used based on CGO_ENABLED=0.
38+
if rss, vs, err := getMemory(); err == nil {
39+
fmt.Printf("GO: rss -> %d\n", rss)
40+
fmt.Printf("GO: vs -> %d\n", vs)
41+
}
42+
}
43+
3644
// ProcessCollectorOpts defines the behavior of a process metrics collector
3745
// created with NewProcessCollector.
3846
type ProcessCollectorOpts struct {

prometheus/process_collector_darwin.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,18 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
8585
c.reportError(ch, c.cpuTotal, err)
8686
}
8787

88-
// TODO: publish c.vsize and c.rss values
88+
if rss, vs, err := getMemory(); err == nil {
89+
if rss != 0 {
90+
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(rss))
91+
}
92+
93+
if vs != 0 {
94+
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(vs))
95+
}
96+
} else {
97+
c.reportError(ch, c.rss, err)
98+
c.reportError(ch, c.vsize, err)
99+
}
89100

90101
if fds, err := getOpenFileCount(); err == nil {
91102
ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//go:build darwin && cgo
2+
3+
package prometheus
4+
5+
/*
6+
int getmem(unsigned long long *rss, unsigned long long *vs);
7+
*/
8+
import "C"
9+
import "fmt"
10+
11+
func getMemory() (uint64, uint64, error) {
12+
var (
13+
rss, vs C.ulonglong
14+
)
15+
16+
if err := C.getmem(&rss, &vs); err != 0 {
17+
return 0, 0, fmt.Errorf("task_info() failed with 0x%x", int(err))
18+
}
19+
20+
return uint64(rss), uint64(vs), nil
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !darwin || !cgo
2+
3+
package prometheus
4+
5+
func getMemory() (uint64, uint64, error) {
6+
return 0, 0, nil
7+
}

0 commit comments

Comments
 (0)