|
|
|
// Copyright 2019 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 !nopowersupplyclass
|
|
|
|
// +build !nopowersupplyclass
|
|
|
|
|
|
|
|
package collector
|
|
|
|
|
|
|
|
/*
|
|
|
|
#cgo LDFLAGS: -framework IOKit -framework CoreFoundation
|
|
|
|
#include <CoreFoundation/CFNumber.h>
|
|
|
|
#include <CoreFoundation/CFRunLoop.h>
|
|
|
|
#include <CoreFoundation/CFString.h>
|
|
|
|
#include <IOKit/ps/IOPowerSources.h>
|
|
|
|
#include <IOKit/ps/IOPSKeys.h>
|
|
|
|
|
|
|
|
// values collected from IOKit Power Source APIs
|
|
|
|
// Functions documentation available at
|
|
|
|
// https://developer.apple.com/documentation/iokit/iopowersources_h
|
|
|
|
// CFDictionary keys definition
|
|
|
|
// https://developer.apple.com/documentation/iokit/iopskeys_h/defines
|
|
|
|
struct macos_powersupply {
|
|
|
|
char *Name;
|
|
|
|
char *PowerSourceState;
|
|
|
|
char *Type;
|
|
|
|
char *TransportType;
|
|
|
|
char *BatteryHealth;
|
|
|
|
char *HardwareSerialNumber;
|
|
|
|
|
|
|
|
int *PowerSourceID;
|
|
|
|
int *CurrentCapacity;
|
|
|
|
int *MaxCapacity;
|
|
|
|
int *DesignCapacity;
|
|
|
|
int *NominalCapacity;
|
|
|
|
|
|
|
|
int *TimeToEmpty;
|
|
|
|
int *TimeToFullCharge;
|
|
|
|
|
|
|
|
int *Voltage;
|
|
|
|
int *Current;
|
|
|
|
|
|
|
|
int *Temperature;
|
|
|
|
|
|
|
|
// boolean values
|
|
|
|
int *IsCharged;
|
|
|
|
int *IsCharging;
|
|
|
|
int *InternalFailure;
|
|
|
|
int *IsPresent;
|
|
|
|
};
|
|
|
|
|
|
|
|
int *CFDictionaryGetInt(CFDictionaryRef theDict, const void *key) {
|
|
|
|
CFNumberRef tmp;
|
|
|
|
int *value;
|
|
|
|
|
|
|
|
tmp = CFDictionaryGetValue(theDict, key);
|
|
|
|
|
|
|
|
if (tmp == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
value = (int*)malloc(sizeof(int));
|
|
|
|
if (CFNumberGetValue(tmp, kCFNumberIntType, value)) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(value);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int *CFDictionaryGetBoolean(CFDictionaryRef theDict, const void *key) {
|
|
|
|
CFBooleanRef tmp;
|
|
|
|
int *value;
|
|
|
|
|
|
|
|
tmp = CFDictionaryGetValue(theDict, key);
|
|
|
|
|
|
|
|
if (tmp == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
value = (int*)malloc(sizeof(int));
|
|
|
|
if (CFBooleanGetValue(tmp)) {
|
|
|
|
*value = 1;
|
|
|
|
} else {
|
|
|
|
*value = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *CFDictionaryGetSring(CFDictionaryRef theDict, const void *key) {
|
|
|
|
CFStringRef tmp;
|
|
|
|
CFIndex size;
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
tmp = CFDictionaryGetValue(theDict, key);
|
|
|
|
|
|
|
|
if (tmp == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
size = CFStringGetLength(tmp) + 1;
|
|
|
|
value = (char*)malloc(size);
|
|
|
|
|
|
|
|
if(CFStringGetCString(tmp, value, size, kCFStringEncodingUTF8)) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(value);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct macos_powersupply* getPowerSupplyInfo(CFDictionaryRef powerSourceInformation) {
|
|
|
|
struct macos_powersupply *ret;
|
|
|
|
|
|
|
|
if (powerSourceInformation == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ret = (struct macos_powersupply*)malloc(sizeof(struct macos_powersupply));
|
|
|
|
|
|
|
|
ret->PowerSourceID = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSPowerSourceIDKey));
|
|
|
|
ret->CurrentCapacity = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSCurrentCapacityKey));
|
|
|
|
ret->MaxCapacity = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSMaxCapacityKey));
|
|
|
|
ret->DesignCapacity = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSDesignCapacityKey));
|
|
|
|
ret->NominalCapacity = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSNominalCapacityKey));
|
|
|
|
ret->TimeToEmpty = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTimeToEmptyKey));
|
|
|
|
ret->TimeToFullCharge = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTimeToFullChargeKey));
|
|
|
|
ret->Voltage = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSVoltageKey));
|
|
|
|
ret->Current = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSCurrentKey));
|
|
|
|
ret->Temperature = CFDictionaryGetInt(powerSourceInformation, CFSTR(kIOPSTemperatureKey));
|
|
|
|
|
|
|
|
ret->Name = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSNameKey));
|
|
|
|
ret->PowerSourceState = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSPowerSourceStateKey));
|
|
|
|
ret->Type = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSTypeKey));
|
|
|
|
ret->TransportType = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSTransportTypeKey));
|
|
|
|
ret->BatteryHealth = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSBatteryHealthKey));
|
|
|
|
ret->HardwareSerialNumber = CFDictionaryGetSring(powerSourceInformation, CFSTR(kIOPSHardwareSerialNumberKey));
|
|
|
|
|
|
|
|
ret->IsCharged = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsChargedKey));
|
|
|
|
ret->IsCharging = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsChargingKey));
|
|
|
|
ret->InternalFailure = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSInternalFailureKey));
|
|
|
|
ret->IsPresent = CFDictionaryGetBoolean(powerSourceInformation, CFSTR(kIOPSIsPresentKey));
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void releasePowerSupply(struct macos_powersupply *ps) {
|
|
|
|
free(ps->Name);
|
|
|
|
free(ps->PowerSourceState);
|
|
|
|
free(ps->Type);
|
|
|
|
free(ps->TransportType);
|
|
|
|
free(ps->BatteryHealth);
|
|
|
|
free(ps->HardwareSerialNumber);
|
|
|
|
|
|
|
|
free(ps->PowerSourceID);
|
|
|
|
free(ps->CurrentCapacity);
|
|
|
|
free(ps->MaxCapacity);
|
|
|
|
free(ps->DesignCapacity);
|
|
|
|
free(ps->NominalCapacity);
|
|
|
|
free(ps->TimeToEmpty);
|
|
|
|
free(ps->TimeToFullCharge);
|
|
|
|
free(ps->Voltage);
|
|
|
|
free(ps->Current);
|
|
|
|
free(ps->Temperature);
|
|
|
|
|
|
|
|
free(ps->IsCharged);
|
|
|
|
free(ps->IsCharging);
|
|
|
|
free(ps->InternalFailure);
|
|
|
|
free(ps->IsPresent);
|
|
|
|
|
|
|
|
free(ps);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
import "C"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error {
|
|
|
|
psList, err := getPowerSourceList()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't get IOPPowerSourcesList: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, info := range psList {
|
|
|
|
labels := getPowerSourceDescriptorLabels(info)
|
|
|
|
powerSupplyName := labels["power_supply"]
|
|
|
|
|
|
|
|
if c.ignoredPattern.MatchString(powerSupplyName) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, value := range getPowerSourceDescriptorMap(info) {
|
|
|
|
if value == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ch <- prometheus.MustNewConstMetric(
|
|
|
|
prometheus.NewDesc(
|
|
|
|
prometheus.BuildFQName(namespace, c.subsystem, name),
|
|
|
|
fmt.Sprintf("IOKit Power Source information field %s for <power_supply>.", name),
|
|
|
|
[]string{"power_supply"}, nil,
|
|
|
|
),
|
|
|
|
prometheus.GaugeValue, *value, powerSupplyName,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pushEnumMetric(
|
|
|
|
ch,
|
|
|
|
getPowerSourceDescriptorState(info),
|
|
|
|
"power_source_state",
|
|
|
|
c.subsystem,
|
|
|
|
powerSupplyName,
|
|
|
|
)
|
|
|
|
|
|
|
|
pushEnumMetric(
|
|
|
|
ch,
|
|
|
|
getPowerSourceDescriptorBatteryHealth(info),
|
|
|
|
"battery_health",
|
|
|
|
c.subsystem,
|
|
|
|
powerSupplyName,
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
keys []string
|
|
|
|
values []string
|
|
|
|
)
|
|
|
|
for name, value := range labels {
|
|
|
|
if value != "" {
|
|
|
|
keys = append(keys, name)
|
|
|
|
values = append(values, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fieldDesc := prometheus.NewDesc(
|
|
|
|
prometheus.BuildFQName(namespace, c.subsystem, "info"),
|
|
|
|
"IOKit Power Source information for <power_supply>.",
|
|
|
|
keys,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
ch <- prometheus.MustNewConstMetric(fieldDesc, prometheus.GaugeValue, 1.0, values...)
|
|
|
|
|
|
|
|
C.releasePowerSupply(info)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getPowerSourceList fetches information from IOKit APIs
|
|
|
|
//
|
|
|
|
// Data is provided as opaque CoreFoundation references
|
|
|
|
// C.getPowerSupplyInfo will convert those objects in something
|
|
|
|
// easily manageable in Go.
|
|
|
|
// https://developer.apple.com/documentation/iokit/iopowersources_h
|
|
|
|
func getPowerSourceList() ([]*C.struct_macos_powersupply, error) {
|
|
|
|
infos, err := C.IOPSCopyPowerSourcesInfo()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer C.CFRelease(infos)
|
|
|
|
|
|
|
|
psList, err := C.IOPSCopyPowerSourcesList(infos)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if psList == C.CFArrayRef(0) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
defer C.CFRelease(C.CFTypeRef(psList))
|
|
|
|
|
|
|
|
size, err := C.CFArrayGetCount(psList)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := make([]*C.struct_macos_powersupply, size)
|
|
|
|
for i := C.CFIndex(0); i < size; i++ {
|
|
|
|
ps, err := C.CFArrayGetValueAtIndex(psList, i)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dict, err := C.IOPSGetPowerSourceDescription(infos, (C.CFTypeRef)(ps))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := C.getPowerSupplyInfo(dict)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ret[int(i)] = info
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPowerSourceDescriptorMap(info *C.struct_macos_powersupply) map[string]*float64 {
|
|
|
|
return map[string]*float64{
|
|
|
|
"current_capacity": convertValue(info.CurrentCapacity),
|
|
|
|
"max_capacity": convertValue(info.MaxCapacity),
|
|
|
|
"design_capacity": convertValue(info.DesignCapacity),
|
|
|
|
"nominal_capacity": convertValue(info.NominalCapacity),
|
|
|
|
"time_to_empty_seconds": minutesToSeconds(info.TimeToEmpty),
|
|
|
|
"time_to_full_seconds": minutesToSeconds(info.TimeToFullCharge),
|
|
|
|
"voltage_volt": scaleValue(info.Voltage, 1e3),
|
|
|
|
"current_ampere": scaleValue(info.Current, 1e3),
|
|
|
|
"temp_celsius": convertValue(info.Temperature),
|
|
|
|
"present": convertValue(info.IsPresent),
|
|
|
|
"charging": convertValue(info.IsCharging),
|
|
|
|
"charged": convertValue(info.IsCharged),
|
|
|
|
"internal_failure": convertValue(info.InternalFailure),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPowerSourceDescriptorLabels(info *C.struct_macos_powersupply) map[string]string {
|
|
|
|
return map[string]string{
|
|
|
|
"id": strconv.FormatInt(int64(*info.PowerSourceID), 10),
|
|
|
|
"power_supply": C.GoString(info.Name),
|
|
|
|
"type": C.GoString(info.Type),
|
|
|
|
"transport_type": C.GoString(info.TransportType),
|
|
|
|
"serial_number": C.GoString(info.HardwareSerialNumber),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPowerSourceDescriptorState(info *C.struct_macos_powersupply) map[string]float64 {
|
|
|
|
stateMap := map[string]float64{
|
|
|
|
"Off Line": 0,
|
|
|
|
"AC Power": 0,
|
|
|
|
"Battery Power": 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
// This field is always present
|
|
|
|
// https://developer.apple.com/documentation/iokit/kiopspowersourcestatekey
|
|
|
|
stateMap[C.GoString(info.PowerSourceState)] = 1
|
|
|
|
|
|
|
|
return stateMap
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPowerSourceDescriptorBatteryHealth(info *C.struct_macos_powersupply) map[string]float64 {
|
|
|
|
// This field is optional
|
|
|
|
// https://developer.apple.com/documentation/iokit/kiopsBatteryHealthkey
|
|
|
|
if info.BatteryHealth == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMap := map[string]float64{
|
|
|
|
"Good": 0,
|
|
|
|
"Fair": 0,
|
|
|
|
"Poor": 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMap[C.GoString(info.BatteryHealth)] = 1
|
|
|
|
|
|
|
|
return stateMap
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertValue(value *C.int) *float64 {
|
|
|
|
if value == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := new(float64)
|
|
|
|
*ret = (float64)(*value)
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func scaleValue(value *C.int, scale float64) *float64 {
|
|
|
|
ret := convertValue(value)
|
|
|
|
if ret == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
*ret /= scale
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// minutesToSeconds converts *C.int minutes into *float64 seconds.
|
|
|
|
//
|
|
|
|
// Only positive values will be scaled to seconds, because negative ones
|
|
|
|
// have special meanings. I.e. -1 indicates "Still Calculating the Time"
|
|
|
|
func minutesToSeconds(minutes *C.int) *float64 {
|
|
|
|
ret := convertValue(minutes)
|
|
|
|
if ret == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if *ret > 0 {
|
|
|
|
*ret *= 60
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func pushEnumMetric(ch chan<- prometheus.Metric, values map[string]float64, name, subsystem, powerSupply string) {
|
|
|
|
for state, value := range values {
|
|
|
|
ch <- prometheus.MustNewConstMetric(
|
|
|
|
prometheus.NewDesc(
|
|
|
|
prometheus.BuildFQName(namespace, subsystem, name),
|
|
|
|
fmt.Sprintf("IOKit Power Source information field %s for <power_supply>.", name),
|
|
|
|
[]string{"power_supply", "state"}, nil,
|
|
|
|
),
|
|
|
|
prometheus.GaugeValue, value, powerSupply, state,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|