Browse Source
* Extract powersupply linux code from collector common file. * Add Darwin powersupply collector. Signed-off-by: Alessio Caiazza <nolith@abisso.org>pull/2182/head
Alessio Caiazza
3 years ago
committed by
GitHub
3 changed files with 597 additions and 155 deletions
@ -0,0 +1,418 @@ |
|||||||
|
// 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 <IOKit/ps/IOPowerSources.h> |
||||||
|
#include <IOKit/ps/IOPSKeys.h> |
||||||
|
#include <CoreFoundation/CFArray.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, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue