Skip to content

Commit

Permalink
process_collector: add a proof-of-concept for collecting RSS and VSIZ…
Browse files Browse the repository at this point in the history
…E 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 <mharbison72@gmail.com>
  • Loading branch information
mharbison72 committed Sep 3, 2024
1 parent ac114f3 commit eca836e
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 1 deletion.
60 changes: 60 additions & 0 deletions prometheus/native.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//go:build ignore || (darwin && cgo)

#include <mach/mach_init.h>
#include <mach/task.h>
// Compiler warns shared_memory_server.h is deprecated, use this instead.
// But this doesn't define SHARED_XXX_REGION_SIZE.
//#include <mach/shared_region.h>
#include <mach/shared_memory_server.h> // SHARED_DATA_REGION_SIZE, SHARED_TEXT_REGION_SIZE
#include <mach/mach_vm.h>
#include <stdio.h>

int getmem (unsigned long long *rss, unsigned long long *vs)
{
// https://github.com/apple-oss-distributions/adv_cmds/blob/8744084ea0ff41ca4bb96b0f9c22407d0e48e9b7/ps/tasks.c#L109

kern_return_t error;
task_t task = MACH_PORT_NULL;
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;

error = task_info(mach_task_self(),
TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);

if( error != KERN_SUCCESS )
{
return error;
}

*rss = t_info.resident_size;
*vs = t_info.virtual_size;

{
vm_region_basic_info_data_64_t b_info;
mach_vm_address_t address = GLOBAL_SHARED_TEXT_SEGMENT;
mach_vm_size_t size;
mach_port_t object_name;

/*
* try to determine if this task has the split libraries
* mapped in... if so, adjust its virtual size down by
* the 2 segments that are used for split libraries
*/
t_info_count = VM_REGION_BASIC_INFO_COUNT_64;
error = mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO,
(vm_region_info_t)&b_info, &t_info_count, &object_name);

if (error == KERN_SUCCESS) {
if (b_info.reserved && size == (SHARED_TEXT_REGION_SIZE) &&
*vs > (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE)) {
*vs -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE);
}
}
}

// `ps -o rss,vsize,command` prints values in KB
printf("rss is %qd (%qd KB)\n", *rss, (unsigned long long) (*rss / 1024));
printf("vs is %qd (%qd KB)\n", *vs, (unsigned long long) (*vs / 1024));

return 0;
}
8 changes: 8 additions & 0 deletions prometheus/process_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ type processCollector struct {
inBytes, outBytes *Desc
}

func init() {
// Debugging to show the correct implementation is used based on CGO_ENABLED=0.
if rss, vs, err := getMemory(); err == nil {
fmt.Printf("GO: rss -> %d\n", rss)
fmt.Printf("GO: vs -> %d\n", vs)
}
}

// ProcessCollectorOpts defines the behavior of a process metrics collector
// created with NewProcessCollector.
type ProcessCollectorOpts struct {
Expand Down
13 changes: 12 additions & 1 deletion prometheus/process_collector_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,18 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
c.reportError(ch, c.cpuTotal, err)
}

// TODO: publish c.vsize and c.rss values
if rss, vs, err := getMemory(); err == nil {
if rss != 0 {
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(rss))
}

if vs != 0 {
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(vs))
}
} else {
c.reportError(ch, c.rss, err)
c.reportError(ch, c.vsize, err)
}

if fds, err := getOpenFileCount(); err == nil {
ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds)
Expand Down
34 changes: 34 additions & 0 deletions prometheus/process_collector_memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build darwin && cgo

package prometheus

/*
int getmem(unsigned long long *rss, unsigned long long *vs);
*/
import "C"
import "fmt"

func getMemory() (uint64, uint64, error) {
var (
rss, vs C.ulonglong
)

if err := C.getmem(&rss, &vs); err != 0 {
return 0, 0, fmt.Errorf("task_info() failed with 0x%x", int(err))
}

return uint64(rss), uint64(vs), nil
}
20 changes: 20 additions & 0 deletions prometheus/prometheus_collector_memory_noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !darwin || !cgo

package prometheus

func getMemory() (uint64, uint64, error) {
return 0, 0, nil
}

0 comments on commit eca836e

Please sign in to comment.