process_collector: fill in virtual and resident memory values on macOS using optional cgo (#1616)
Unfortunately, these values aren't available from getrusage(2), or any other builtin Go API. Go itself doesn't provide a mechanism (like on Windows) to call into system libraries. Using a 3rd party package[1] to dynamically call system libraries was proposed and rejected, to avoid adding to the number of dependencies. That leaves using cgo, which is used here when available. When not available (either because of cross compiling or explicitly disabling it), a stub function is linked instead, and the metrics are not exported. That way, cross compiling of other platforms is unaffected (and can also still be done with Darwin too, but at the cost of not exporting these metrics). Note that building an amd64 image on an arm64 mac or vice-versa is cross compiling, and will use the stub method by default. This can be avoided by setting `CGO_ENABLED=1` in the environment to force the use of cgo for both architectures. I'm unsure of the usefulness of the potential adjustment made to the virtual memory value after calling `mach_vm_region()`. I've not seen that code get run with a native amd64 or arm64 image, or with an amd64 image running under Rosetta. But that's what the `ps(1)` command does, and I think we should report what the system tools do. When I was testing this on a beta of macOS 15 with Go 1.21.13 (the current minimum support for this module), the amd64 image ran fine under Rosetta, but the arm64 image immediately printed a message that it was killed, even prior to the cgo call. This seems to be a recurring issue on macOS[2][3], and passing `-ldflags -s` to `go build` avoided the issue. Go 1.23.1 worked out of the box, without fiddling with linker flags, so I don't think this is an issue- Go 1.21 is simply too old to support macOS 15, but I thought it was worth noting. I supposed we could gate the cgo code with an additional build flag, if anyone is concerned about this. [1] https://github.com/ebitengine/purego [2] https://github.com/golang/go/issues/19841#issuecomment-293334802 [3] https://github.com/golang/go/issues/11887#issuecomment-125694604 Signed-off-by: Matt Harbison <mharbison72@gmail.com>
This commit is contained in:
parent
86f3496868
commit
25bda7ceb5
|
@ -0,0 +1,80 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#include <mach/mach_init.h>
|
||||||
|
#include <mach/task.h>
|
||||||
|
// Compiler warns that shared_memory_server.h is deprecated, use this instead.
|
||||||
|
// But this doesn't define SHARED_DATA_REGION_SIZE or SHARED_TEXT_REGION_SIZE.
|
||||||
|
//#include <mach/shared_region.h>
|
||||||
|
#include <mach/shared_memory_server.h>
|
||||||
|
#include <mach/mach_vm.h>
|
||||||
|
|
||||||
|
|
||||||
|
int get_memory_info(unsigned long long *rss, unsigned long long *vsize)
|
||||||
|
{
|
||||||
|
// This is lightly adapted from how ps(1) obtains its memory info.
|
||||||
|
// https://github.com/apple-oss-distributions/adv_cmds/blob/8744084ea0ff41ca4bb96b0f9c22407d0e48e9b7/ps/tasks.c#L109
|
||||||
|
|
||||||
|
kern_return_t error;
|
||||||
|
task_t task = MACH_PORT_NULL;
|
||||||
|
mach_task_basic_info_data_t info;
|
||||||
|
mach_msg_type_number_t info_count = MACH_TASK_BASIC_INFO_COUNT;
|
||||||
|
|
||||||
|
error = task_info(
|
||||||
|
mach_task_self(),
|
||||||
|
MACH_TASK_BASIC_INFO,
|
||||||
|
(task_info_t) &info,
|
||||||
|
&info_count );
|
||||||
|
|
||||||
|
if( error != KERN_SUCCESS )
|
||||||
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
*rss = info.resident_size;
|
||||||
|
*vsize = 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
|
||||||
|
*/
|
||||||
|
info_count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||||
|
|
||||||
|
error = mach_vm_region(
|
||||||
|
mach_task_self(),
|
||||||
|
&address,
|
||||||
|
&size,
|
||||||
|
VM_REGION_BASIC_INFO_64,
|
||||||
|
(vm_region_info_t) &b_info,
|
||||||
|
&info_count,
|
||||||
|
&object_name);
|
||||||
|
|
||||||
|
if (error == KERN_SUCCESS) {
|
||||||
|
if (b_info.reserved && size == (SHARED_TEXT_REGION_SIZE) &&
|
||||||
|
*vsize > (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE)) {
|
||||||
|
*vsize -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -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 get_memory_info(unsigned long long *rss, unsigned long long *vs);
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func getMemory() (*memoryInfo, error) {
|
||||||
|
var (
|
||||||
|
rss, vsize C.ulonglong
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := C.get_memory_info(&rss, &vsize); err != 0 {
|
||||||
|
return nil, fmt.Errorf("task_info() failed with 0x%x", int(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &memoryInfo{vsize: uint64(vsize), rss: uint64(rss)}, nil
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"os"
|
"os"
|
||||||
|
@ -21,6 +22,15 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// notImplementedErr is returned by stub functions that replace cgo functions, when cgo
|
||||||
|
// isn't available.
|
||||||
|
var notImplementedErr = fmt.Errorf("not implemented")
|
||||||
|
|
||||||
|
type memoryInfo struct {
|
||||||
|
vsize uint64 // Virtual memory size in bytes
|
||||||
|
rss uint64 // Resident memory size in bytes
|
||||||
|
}
|
||||||
|
|
||||||
func canCollectProcess() bool {
|
func canCollectProcess() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -85,7 +95,14 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||||
c.reportError(ch, c.cpuTotal, err)
|
c.reportError(ch, c.cpuTotal, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: publish c.vsize and c.rss values
|
if memInfo, err := getMemory(); err == nil {
|
||||||
|
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(memInfo.rss))
|
||||||
|
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(memInfo.vsize))
|
||||||
|
} else if !errors.Is(err, notImplementedErr) {
|
||||||
|
// Don't report an error when support is not compiled in.
|
||||||
|
c.reportError(ch, c.rss, err)
|
||||||
|
c.reportError(ch, c.vsize, err)
|
||||||
|
}
|
||||||
|
|
||||||
if fds, err := getOpenFileCount(); err == nil {
|
if fds, err := getOpenFileCount(); err == nil {
|
||||||
ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds)
|
ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds)
|
||||||
|
|
|
@ -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() (*memoryInfo, error) {
|
||||||
|
return nil, notImplementedErr
|
||||||
|
}
|
Loading…
Reference in New Issue