mirror of
https://github.com/jimeh/macos-battery-exporter.git
synced 2026-02-19 01:16:40 +00:00
feat(init): initial working implementation
This commit is contained in:
130
battery/battery.go
Normal file
130
battery/battery.go
Normal file
@@ -0,0 +1,130 @@
|
||||
//go:build darwin
|
||||
|
||||
package battery
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Battery struct {
|
||||
// BatteryCellDisconnectCount is the number of times the battery cells have
|
||||
// been disconnected.
|
||||
BatteryCellDisconnectCount int
|
||||
|
||||
// BuiltIn indicates if the battery is built-in or not.
|
||||
BuiltIn bool
|
||||
|
||||
// ChargeRateAmps is the current charge rate in mAh. Negative values indicate
|
||||
// discharge, positive values indicate charging.
|
||||
ChargeRateAmps int64
|
||||
|
||||
// ChargeRateWatts is the current charge rate in mWh. Negative values indicate
|
||||
// discharge, positive values indicate charging.
|
||||
ChargeRateWatts float64
|
||||
|
||||
// CurrentCapacityAmps is the current battery capacity in mAh.
|
||||
CurrentCapacityAmps int
|
||||
|
||||
// CurrentCapacityWatts is the current battery capacity in mWh.
|
||||
CurrentCapacityWatts float64
|
||||
|
||||
// CurrentPercentage is the current battery capacity as a percentage.
|
||||
CurrentPercentage int
|
||||
|
||||
// CycleCount is the current cycle count.
|
||||
CycleCount int
|
||||
|
||||
// DesignCapacityAmps is the design capacity in mAh.
|
||||
DesignCapacityAmps int
|
||||
|
||||
// DesignCapacityWatts is the design capacity in mWh.
|
||||
DesignCapacityWatts float64
|
||||
|
||||
// DeviceName is the battery device name.
|
||||
DeviceName string
|
||||
|
||||
// FullyCharged indicates if the battery is fully charged.
|
||||
FullyCharged bool
|
||||
|
||||
// Health is the battery health as a percentage (0-100%).
|
||||
Health int
|
||||
|
||||
// IsCharging indicates if the battery is currently charging.
|
||||
IsCharging bool
|
||||
|
||||
// MaxCapacityAmps is the maximum capacity in mAh.
|
||||
MaxCapacityAmps int
|
||||
|
||||
// MaxCapacityWatts is the maximum capacity in mWh.
|
||||
MaxCapacityWatts float64
|
||||
|
||||
// Serial is the battery serial number.
|
||||
Serial string
|
||||
|
||||
// Temperature is the current temperature in °C.
|
||||
Temperature float64
|
||||
|
||||
// TimeRemaining is the estimated time remaining until the battery is
|
||||
// fully charged or discharged.
|
||||
TimeRemaining time.Duration
|
||||
|
||||
// Voltage is the current voltage in mV.
|
||||
Voltage int64
|
||||
}
|
||||
|
||||
func newBattery(b *batteryRaw) *Battery {
|
||||
volts := float64(b.Voltage) / 1000
|
||||
|
||||
return &Battery{
|
||||
BatteryCellDisconnectCount: b.BatteryCellDisconnectCount,
|
||||
BuiltIn: b.BuiltIn,
|
||||
ChargeRateAmps: b.Amperage,
|
||||
ChargeRateWatts: roundTo(float64(b.Amperage)*volts, 3),
|
||||
CurrentCapacityAmps: b.CurrentCapacity,
|
||||
CurrentCapacityWatts: roundTo(float64(b.CurrentCapacity)*volts, 3),
|
||||
CurrentPercentage: b.CurrentPercentage,
|
||||
CycleCount: b.CycleCount,
|
||||
DesignCapacityAmps: b.DesignCapacity,
|
||||
DesignCapacityWatts: roundTo(float64(b.DesignCapacity)*volts, 3),
|
||||
DeviceName: b.DeviceName,
|
||||
FullyCharged: b.FullyCharged,
|
||||
Health: b.Health,
|
||||
IsCharging: b.IsCharging,
|
||||
MaxCapacityAmps: b.MaxCapacity,
|
||||
MaxCapacityWatts: roundTo(float64(b.MaxCapacity)*volts, 3),
|
||||
Serial: b.Serial,
|
||||
Temperature: float64(b.Temperature) / 100,
|
||||
TimeRemaining: time.Duration(b.TimeRemaining) * time.Minute,
|
||||
Voltage: b.Voltage,
|
||||
}
|
||||
}
|
||||
|
||||
func Get() (*Battery, error) {
|
||||
batteriesRaw, err := getAllRaw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBattery(batteriesRaw[0]), nil
|
||||
}
|
||||
|
||||
func GetAll() ([]*Battery, error) {
|
||||
batteriesRaw, err := getAllRaw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batteries := []*Battery{}
|
||||
for _, b := range batteriesRaw {
|
||||
batteries = append(batteries, newBattery(b))
|
||||
}
|
||||
|
||||
return batteries, nil
|
||||
}
|
||||
|
||||
// roundTo rounds a float64 to 'places' decimal places
|
||||
func roundTo(value float64, places int) float64 {
|
||||
shift := math.Pow(10, float64(places))
|
||||
return math.Round(value*shift) / shift
|
||||
}
|
||||
48
battery/ioreg.go
Normal file
48
battery/ioreg.go
Normal file
@@ -0,0 +1,48 @@
|
||||
//go:build darwin
|
||||
|
||||
package battery
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
// batteryRaw is the raw data structure returned by ioreg.
|
||||
type batteryRaw struct {
|
||||
Amperage int64 `plist:"Amperage"`
|
||||
AvgTimeToEmpty int `plist:"AvgTimeToEmpty"`
|
||||
AvgTimeToFull int `plist:"AvgTimeToFull"`
|
||||
BatteryCellDisconnectCount int `plist:"BatteryCellDisconnectCount"`
|
||||
BuiltIn bool `plist:"built-in"`
|
||||
CurrentCapacity int `plist:"AppleRawCurrentCapacity"`
|
||||
CurrentPercentage int `plist:"CurrentCapacity"`
|
||||
CycleCount int `plist:"CycleCount"`
|
||||
DesignCapacity int `plist:"DesignCapacity"`
|
||||
DeviceName string `plist:"DeviceName"`
|
||||
DesignCycleCount int `plist:"DesignCycleCount9C"`
|
||||
ExternalConnected bool `plist:"ExternalConnected"`
|
||||
FullyCharged bool `plist:"FullyCharged"`
|
||||
Health int `plist:"MaxCapacity"`
|
||||
IsCharging bool `plist:"IsCharging"`
|
||||
MaxCapacity int `plist:"AppleRawMaxCapacity"`
|
||||
Serial string `plist:"Serial"`
|
||||
Temperature int `plist:"Temperature"`
|
||||
TimeRemaining int `plist:"TimeRemaining"`
|
||||
Voltage int64 `plist:"Voltage"`
|
||||
}
|
||||
|
||||
func getAllRaw() ([]*batteryRaw, error) {
|
||||
b, err := exec.Command("ioreg", "-ra", "-c", "AppleSmartBattery").Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batteries := []*batteryRaw{}
|
||||
_, err = plist.Unmarshal(b, &batteries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return batteries, nil
|
||||
}
|
||||
19
go.mod
Normal file
19
go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
module github.com/jimeh/macos-battery-exporter
|
||||
|
||||
go 1.21.4
|
||||
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/prometheus/common v0.45.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
31
go.sum
Normal file
31
go.sum
Normal file
@@ -0,0 +1,31 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
117
main.go
Normal file
117
main.go
Normal file
@@ -0,0 +1,117 @@
|
||||
//go:build darwin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jimeh/macos-battery-exporter/prom"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/expfmt"
|
||||
)
|
||||
|
||||
var (
|
||||
outputFlag = flag.String(
|
||||
"o", "", "Output file to write to in Prometheus format",
|
||||
)
|
||||
serverFlag = flag.Bool("s", false, "Run as a Prometheus metrics server")
|
||||
bindFlag = flag.String(
|
||||
"b", "127.0.0.1", "Bind address to run server on",
|
||||
)
|
||||
portFlag = flag.Int("p", 9108, "Port to run server on")
|
||||
namespaceFlag = flag.String("n", "node", "Namespace for metrics")
|
||||
logLevelFlag = flag.String("l", "info", "Log level")
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := mainE(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mainE() error {
|
||||
flag.Parse()
|
||||
|
||||
err := setupSLog(*logLevelFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *serverFlag {
|
||||
opts := prom.ServerOptions{
|
||||
Bind: *bindFlag,
|
||||
Port: *portFlag,
|
||||
}
|
||||
|
||||
return prom.RunServer(
|
||||
*namespaceFlag,
|
||||
prometheus.DefaultRegisterer.(*prometheus.Registry),
|
||||
opts,
|
||||
)
|
||||
} else {
|
||||
registry := prometheus.NewRegistry()
|
||||
err := registry.Register(prom.NewCollector(*namespaceFlag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gatherers := prometheus.Gatherers{registry}
|
||||
metricFamilies, err := gatherers.Gather()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, mf := range metricFamilies {
|
||||
_, err := expfmt.MetricFamilyToText(&sb, mf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if *outputFlag != "" {
|
||||
return writeToFile(sb.String(), *outputFlag)
|
||||
} else {
|
||||
fmt.Print(sb.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupSLog(levelStr string) error {
|
||||
var level slog.Level
|
||||
err := level.UnmarshalText([]byte(levelStr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: level,
|
||||
})
|
||||
logger := slog.New(handler)
|
||||
|
||||
slog.SetDefault(logger)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeToFile(data, outputFile string) error {
|
||||
file, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
350
prom/collector.go
Normal file
350
prom/collector.go
Normal file
@@ -0,0 +1,350 @@
|
||||
//go:build darwin
|
||||
|
||||
package prom
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/jimeh/macos-battery-exporter/battery"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
serialLabel = "serial"
|
||||
deviceNameLabel = "device_name"
|
||||
builtInLabel = "built_in"
|
||||
)
|
||||
|
||||
type Collector struct {
|
||||
descInfo *prometheus.Desc
|
||||
descBatteryCellDisconnectCount *prometheus.Desc
|
||||
descChargeRateAmps *prometheus.Desc
|
||||
descChargeRateWatts *prometheus.Desc
|
||||
descCurrentCapacityAmps *prometheus.Desc
|
||||
descCurrentCapacityWatts *prometheus.Desc
|
||||
descCurrentPercentage *prometheus.Desc
|
||||
descCycleCount *prometheus.Desc
|
||||
descDesignCapacityAmps *prometheus.Desc
|
||||
descDesignCapacityWatts *prometheus.Desc
|
||||
descFullyCharged *prometheus.Desc
|
||||
descHealth *prometheus.Desc
|
||||
descIsCharging *prometheus.Desc
|
||||
descMaxCapacityAmps *prometheus.Desc
|
||||
descMaxCapacityWatts *prometheus.Desc
|
||||
descTemperature *prometheus.Desc
|
||||
descTimeRemaining *prometheus.Desc
|
||||
descVoltage *prometheus.Desc
|
||||
}
|
||||
|
||||
var _ prometheus.Collector = &Collector{}
|
||||
|
||||
func NewCollector(namespace string) *Collector {
|
||||
c := &Collector{
|
||||
descInfo: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "battery", "info"),
|
||||
"Basic details about the battery.",
|
||||
[]string{serialLabel, deviceNameLabel, builtInLabel},
|
||||
nil,
|
||||
),
|
||||
descBatteryCellDisconnectCount: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "cell_disconnect_count",
|
||||
),
|
||||
"Total number of times a battery cell has been disconnected.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descChargeRateAmps: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "charge_rate_amps",
|
||||
),
|
||||
"Current charge rate in Ah.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descChargeRateWatts: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "charge_rate_watts",
|
||||
),
|
||||
"Current charge rate in Wh.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descCurrentCapacityAmps: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "current_capacity_amps",
|
||||
),
|
||||
"Current charge capacity in Ah.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descCurrentCapacityWatts: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "current_capacity_watts",
|
||||
),
|
||||
"Current charge capacity in Wh.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descCurrentPercentage: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "current_percentage",
|
||||
),
|
||||
"Current battery charge percentage.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descCycleCount: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "cycle_count",
|
||||
),
|
||||
"Current battery cycle count.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descDesignCapacityAmps: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "design_capacity_amps",
|
||||
),
|
||||
"Design capacity in Ah.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descDesignCapacityWatts: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "design_capacity_watts",
|
||||
),
|
||||
"Design capacity in Wh.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descFullyCharged: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "fully_charged",
|
||||
),
|
||||
"Indicates if the battery is fully charged.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descHealth: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "health",
|
||||
),
|
||||
"Battery health as a percentage (0-100%).",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descIsCharging: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "is_charging",
|
||||
),
|
||||
"Indicates if the battery is currently charging.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descMaxCapacityAmps: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "max_capacity_amps",
|
||||
),
|
||||
"Design capacity in Ah.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descMaxCapacityWatts: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "max_capacity_watts",
|
||||
),
|
||||
"Design capacity in Wh.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descTemperature: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "temperature_celsius",
|
||||
),
|
||||
"Current battery temperature in °C.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descTimeRemaining: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "time_remaining_seconds",
|
||||
),
|
||||
"Estimated time remaining until battery is fully "+
|
||||
"charged or discharged.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
descVoltage: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(
|
||||
namespace, "battery", "voltage_volts",
|
||||
),
|
||||
"Current battery voltage in V.",
|
||||
[]string{serialLabel},
|
||||
nil,
|
||||
),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- c.descInfo
|
||||
ch <- c.descBatteryCellDisconnectCount
|
||||
ch <- c.descChargeRateAmps
|
||||
ch <- c.descChargeRateWatts
|
||||
ch <- c.descCurrentCapacityAmps
|
||||
ch <- c.descCurrentCapacityWatts
|
||||
ch <- c.descCurrentPercentage
|
||||
ch <- c.descCycleCount
|
||||
ch <- c.descDesignCapacityAmps
|
||||
ch <- c.descDesignCapacityWatts
|
||||
ch <- c.descFullyCharged
|
||||
ch <- c.descHealth
|
||||
ch <- c.descIsCharging
|
||||
ch <- c.descMaxCapacityAmps
|
||||
ch <- c.descMaxCapacityWatts
|
||||
ch <- c.descTemperature
|
||||
ch <- c.descTimeRemaining
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
slog.Debug("collecting battery metrics")
|
||||
batteries, err := battery.GetAll()
|
||||
if err != nil {
|
||||
slog.Error(
|
||||
"failed to get battery details",
|
||||
slog.String("error", err.Error()),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
for _, battery := range batteries {
|
||||
labels := []string{battery.Serial}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descInfo,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
battery.Serial, battery.DeviceName, boolToString(battery.BuiltIn),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descBatteryCellDisconnectCount,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.BatteryCellDisconnectCount),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descChargeRateAmps,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.ChargeRateAmps)/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descChargeRateWatts,
|
||||
prometheus.GaugeValue,
|
||||
battery.ChargeRateWatts/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descCurrentCapacityAmps,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.CurrentCapacityAmps)/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descCurrentCapacityWatts,
|
||||
prometheus.GaugeValue,
|
||||
battery.CurrentCapacityWatts/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descCurrentPercentage,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.CurrentPercentage),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descCycleCount,
|
||||
prometheus.CounterValue,
|
||||
float64(battery.CycleCount),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descDesignCapacityAmps,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.DesignCapacityAmps)/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descDesignCapacityWatts,
|
||||
prometheus.GaugeValue,
|
||||
battery.DesignCapacityWatts/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descFullyCharged,
|
||||
prometheus.GaugeValue,
|
||||
boolToFloat64(battery.FullyCharged),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descHealth,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.Health),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descIsCharging,
|
||||
prometheus.GaugeValue,
|
||||
boolToFloat64(battery.IsCharging),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descMaxCapacityAmps,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.MaxCapacityAmps)/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descMaxCapacityWatts,
|
||||
prometheus.GaugeValue,
|
||||
battery.MaxCapacityWatts/1000,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descTemperature,
|
||||
prometheus.GaugeValue,
|
||||
battery.Temperature,
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descTimeRemaining,
|
||||
prometheus.GaugeValue,
|
||||
battery.TimeRemaining.Seconds(),
|
||||
labels...,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.descVoltage,
|
||||
prometheus.GaugeValue,
|
||||
float64(battery.Voltage)/1000,
|
||||
labels...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func boolToString(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
|
||||
return "false"
|
||||
}
|
||||
|
||||
func boolToFloat64(b bool) float64 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
111
prom/server.go
Normal file
111
prom/server.go
Normal file
@@ -0,0 +1,111 @@
|
||||
//go:build darwin
|
||||
|
||||
package prom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
type Registry interface {
|
||||
prometheus.Registerer
|
||||
prometheus.Gatherer
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
Bind string
|
||||
Port int
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
*http.Server
|
||||
registry Registry
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
func NewServer(registry *prometheus.Registry, options ServerOptions) *Server {
|
||||
if options.Bind == "" {
|
||||
options.Bind = "127.0.0.1"
|
||||
}
|
||||
if options.Port == 0 {
|
||||
options.Port = 9108
|
||||
}
|
||||
if options.ReadTimeout == 0 {
|
||||
options.ReadTimeout = 5 * time.Second
|
||||
}
|
||||
if options.WriteTimeout == 0 {
|
||||
options.WriteTimeout = 10 * time.Second
|
||||
}
|
||||
if options.IdleTimeout == 0 {
|
||||
options.IdleTimeout = 30 * time.Second
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(
|
||||
"/metrics",
|
||||
promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
|
||||
)
|
||||
|
||||
return &Server{
|
||||
mux: mux,
|
||||
registry: registry,
|
||||
Server: &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", options.Bind, options.Port),
|
||||
ReadTimeout: options.ReadTimeout,
|
||||
WriteTimeout: options.WriteTimeout,
|
||||
IdleTimeout: options.IdleTimeout,
|
||||
Handler: mux,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Register(
|
||||
collectors ...prometheus.Collector,
|
||||
) error {
|
||||
for _, c := range collectors {
|
||||
err := s.registry.Register(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe() error {
|
||||
slog.Info(
|
||||
"starting prometheus server",
|
||||
slog.String("addr", s.Addr),
|
||||
)
|
||||
|
||||
return s.Server.ListenAndServe()
|
||||
}
|
||||
|
||||
func RunServer(
|
||||
namespace string,
|
||||
registry *prometheus.Registry,
|
||||
options ServerOptions,
|
||||
) error {
|
||||
if namespace == "" {
|
||||
namespace = "node"
|
||||
}
|
||||
|
||||
s := NewServer(registry, options)
|
||||
|
||||
collector := NewCollector(namespace)
|
||||
err := s.Register(collector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.ListenAndServe()
|
||||
}
|
||||
Reference in New Issue
Block a user