diff --git a/collector/filesystem_bsd.go b/collector/filesystem_bsd.go index d586c9ca90..2810a5a3d5 100644 --- a/collector/filesystem_bsd.go +++ b/collector/filesystem_bsd.go @@ -11,9 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build (darwin || dragonfly) && !nofilesystem -// +build darwin dragonfly -// +build !nofilesystem +//go:build dragonfly && !nofilesystem +// +build dragonfly,!nofilesystem package collector diff --git a/collector/filesystem_common.go b/collector/filesystem_common.go index 41d439c80e..272366b38b 100644 --- a/collector/filesystem_common.go +++ b/collector/filesystem_common.go @@ -75,6 +75,7 @@ type filesystemCollector struct { fsTypeFilter deviceFilter sizeDesc, freeDesc, availDesc *prometheus.Desc filesDesc, filesFreeDesc *prometheus.Desc + purgeableDesc *prometheus.Desc roDesc, deviceErrorDesc *prometheus.Desc mountInfoDesc *prometheus.Desc logger *slog.Logger @@ -88,6 +89,7 @@ type filesystemStats struct { labels filesystemLabels size, free, avail float64 files, filesFree float64 + purgeable *float64 ro, deviceError float64 } @@ -129,6 +131,12 @@ func NewFilesystemCollector(logger *slog.Logger) (Collector, error) { filesystemLabelNames, nil, ) + purgeableDesc := prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "purgeable_bytes"), + "Filesystem space available including purgeable space (MacOS specific).", + filesystemLabelNames, nil, + ) + roDesc := prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "readonly"), "Filesystem read-only status.", @@ -166,6 +174,7 @@ func NewFilesystemCollector(logger *slog.Logger) (Collector, error) { availDesc: availDesc, filesDesc: filesDesc, filesFreeDesc: filesFreeDesc, + purgeableDesc: purgeableDesc, roDesc: roDesc, deviceErrorDesc: deviceErrorDesc, mountInfoDesc: mountInfoDesc, @@ -223,6 +232,13 @@ func (c *filesystemCollector) Update(ch chan<- prometheus.Metric) error { c.mountInfoDesc, prometheus.GaugeValue, 1.0, s.labels.device, s.labels.major, s.labels.minor, s.labels.mountPoint, ) + if s.purgeable != nil { + ch <- prometheus.MustNewConstMetric( + c.purgeableDesc, prometheus.GaugeValue, + *s.purgeable, s.labels.device, s.labels.mountPoint, + s.labels.fsType, s.labels.deviceError, + ) + } } return nil } diff --git a/collector/filesystem_macos.go b/collector/filesystem_macos.go new file mode 100644 index 0000000000..0894cabc5c --- /dev/null +++ b/collector/filesystem_macos.go @@ -0,0 +1,108 @@ +// Copyright 2015 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 && !nofilesystem +// +build darwin,!nofilesystem + +package collector + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation +#import +Float64 *purgeable(char *path) { + CFNumberRef tmp; + Float64 *value; + NSError *error = nil; + NSString *str = [NSString stringWithUTF8String:path]; + NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:str]; + NSDictionary *results = [fileURL resourceValuesForKeys:@[NSURLVolumeAvailableCapacityForImportantUsageKey] error:&error]; + if (results) { + if ((tmp = CFDictionaryGetValue((CFDictionaryRef)results, NSURLVolumeAvailableCapacityForImportantUsageKey)) == NULL) + return NULL; + value = (Float64 *)malloc(sizeof(Float64)); + if (CFNumberGetValue(tmp, kCFNumberFloat64Type, value)) { + return value; + } + } + free(value); + return NULL; +} +*/ +import "C" + +import ( + "errors" + "unsafe" +) + +/* +#include +#include +#include +#include +*/ +import "C" + +const ( + defMountPointsExcluded = "^/(dev)($|/)" + defFSTypesExcluded = "^devfs$" + readOnly = 0x1 // MNT_RDONLY +) + +// Expose filesystem fullness. +func (c *filesystemCollector) GetStats() (stats []filesystemStats, err error) { + var mntbuf *C.struct_statfs + count := C.getmntinfo(&mntbuf, C.MNT_NOWAIT) + if count == 0 { + return nil, errors.New("getmntinfo() failed") + } + + mnt := (*[1 << 20]C.struct_statfs)(unsafe.Pointer(mntbuf)) + stats = []filesystemStats{} + for i := 0; i < int(count); i++ { + mountpoint := C.GoString(&mnt[i].f_mntonname[0]) + if c.mountPointFilter.ignored(mountpoint) { + c.logger.Debug("Ignoring mount point", "mountpoint", mountpoint) + continue + } + + device := C.GoString(&mnt[i].f_mntfromname[0]) + fstype := C.GoString(&mnt[i].f_fstypename[0]) + if c.fsTypeFilter.ignored(fstype) { + c.logger.Debug("Ignoring fs type", "type", fstype) + continue + } + + var ro float64 + if (mnt[i].f_flags & readOnly) != 0 { + ro = 1 + } + + stats = append(stats, filesystemStats{ + labels: filesystemLabels{ + device: device, + mountPoint: rootfsStripPrefix(mountpoint), + fsType: fstype, + }, + size: float64(mnt[i].f_blocks) * float64(mnt[i].f_bsize), + free: float64(mnt[i].f_bfree) * float64(mnt[i].f_bsize), + avail: float64(mnt[i].f_bavail) * float64(mnt[i].f_bsize), + files: float64(mnt[i].f_files), + filesFree: float64(mnt[i].f_ffree), + purgeable: (*float64)(C.purgeable(C.CString(mountpoint))), + ro: ro, + }) + } + return stats, nil +}