Skip to content

Commit eca836e

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. The build-tag comment in the C file avoids the usual "C source files not allowed when not using cgo or SWIG" error on the non-darwin platforms that aren't using cgo, without any changes needing to be made to the client project. Otherwise, the file is compiled on darwin by default, unless `CGO_ENABLED=0`, in which case the dummy function is used and the metrics aren't exported. Therefore, the only potential hardships for client programs with this change are limited to darwin builds. 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. Signed-off-by: Matt Harbison <[email protected]>
1 parent ac114f3 commit eca836e

5 files changed

+134
-1
lines changed

prometheus/native.c

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

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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2024 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
//go:build darwin && cgo
15+
16+
package prometheus
17+
18+
/*
19+
int getmem(unsigned long long *rss, unsigned long long *vs);
20+
*/
21+
import "C"
22+
import "fmt"
23+
24+
func getMemory() (uint64, uint64, error) {
25+
var (
26+
rss, vs C.ulonglong
27+
)
28+
29+
if err := C.getmem(&rss, &vs); err != 0 {
30+
return 0, 0, fmt.Errorf("task_info() failed with 0x%x", int(err))
31+
}
32+
33+
return uint64(rss), uint64(vs), nil
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2024 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
//go:build !darwin || !cgo
15+
16+
package prometheus
17+
18+
func getMemory() (uint64, uint64, error) {
19+
return 0, 0, nil
20+
}

0 commit comments

Comments
 (0)