Files
shelly-exporter/collector/gen1_collector.go
T
lostakj 9db7aa2560
Build Docker image on push / docker (push) Successful in 22s
Initila commit
2026-08-18 00:46:59 +02:00

515 lines
24 KiB
Go

package collector
import (
"context"
"fmt"
"sort"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"lostak.dev/shelly-exporter/configuration"
"lostak.dev/shelly-exporter/shelly"
)
// These identifiers use the common Gen1 /status protocol. The generic gen1
// identifier is available for compatible devices not explicitly listed here.
var gen1Products = []string{
"gen1",
"plug", "plug_s",
"shelly_1", "shelly_1pm", "shelly_1l", "shelly_2", "shelly_2_5", "shelly_4pro", "shelly_uni",
"shelly_i3",
"shelly_button1", "shelly_trv",
"shelly_em", "shelly_3em",
"shelly_bulb", "shelly_bulb_rgbw", "shelly_duo", "shelly_vintage", "shelly_rgbw2", "shelly_dimmer",
"shelly_ht", "shelly_flood", "shelly_smoke", "shelly_door_window", "shelly_motion", "shelly_sense", "shelly_gas",
}
func init() {
for _, product := range gen1Products {
if err := RegisterProduct(product, newGen1Collector); err != nil {
panic(err)
}
}
}
type gen1Collector struct {
base baseCollector
client shelly.StatusClient
descriptors map[string]*prometheus.Desc
}
func newGen1Collector(config configuration.DeviceConfiguration, client shelly.StatusClient) (prometheus.Collector, error) {
if client == nil {
return nil, fmt.Errorf("client for device %q cannot be nil", config.Name)
}
c := &gen1Collector{
base: newBaseCollector(config.Name, config.Product),
client: client,
descriptors: make(map[string]*prometheus.Desc),
}
c.registerDescriptors()
return c, nil
}
func (c *gen1Collector) registerDescriptors() {
c.add("device_info", "Static Shelly device information.", "product", "mac", "firmware")
c.add("wifi_info", "Shelly Wi-Fi network information.", "ssid", "ip")
c.add("wifi_connected", "Whether the Wi-Fi station is connected (1) or not (0).")
c.add("wifi_rssi_dbm", "Received Wi-Fi signal strength in dBm.")
c.add("cloud_enabled", "Whether Shelly Cloud is enabled (1) or not (0).")
c.add("cloud_connected", "Whether the device is connected to Shelly Cloud (1) or not (0).")
c.add("mqtt_connected", "Whether the device is connected to MQTT (1) or not (0).")
c.add("update_available", "Whether a firmware update is available (1) or not (0).")
c.add("update_info", "Shelly firmware update information.", "status", "current_version", "new_version")
c.add("status_serial", "Sequence number of the Shelly status response.")
c.add("ram_size_bytes", "Total device RAM in bytes.")
c.add("ram_free_bytes", "Free device RAM in bytes.")
c.add("filesystem_size_bytes", "Total device filesystem size in bytes.")
c.add("filesystem_free_bytes", "Free device filesystem space in bytes.")
c.add("filesystem_mounted", "Whether the device data filesystem is mounted (1) or not (0).")
c.add("uptime_seconds", "Device uptime in seconds.")
c.add("relay_on", "Whether the relay output is on (1) or off (0).", "index")
c.add("relay_has_timer", "Whether a relay timer is active (1) or not (0).", "index")
c.add("relay_timer_duration_seconds", "Configured duration of the active relay timer in seconds.", "index")
c.add("relay_timer_remaining_seconds", "Remaining duration of the active relay timer in seconds.", "index")
c.add("relay_overpower", "Whether relay overpower protection is active (1) or not (0).", "index")
c.add("relay_valid", "Whether the relay status is valid (1) or not (0).", "index")
c.add("input_on", "Logical input state (1 for on, 0 for off).", "index")
c.add("input_event_info", "Latest input event and event sequence.", "index", "event", "last_sequence")
c.add("input_event_count_total", "Total input events since the device restarted.", "index")
c.add("meter_power_watts", "Current active power measured in watts.", "index")
c.add("meter_valid", "Whether the meter reading is valid (1) or not (0).", "index")
c.add("meter_timestamp_seconds", "Unix timestamp of the meter reading in seconds.", "index")
c.add("meter_overpower", "Whether the meter reports an overpower condition (1) or not (0).", "index")
c.add("meter_overpower_threshold_watts", "Configured meter overpower threshold in watts.", "index")
c.add("meter_recent_energy_watt_minutes", "Recent per-minute energy reported in watt-minutes.", "index", "minute")
c.add("meter_energy_watt_hours_total", "Total energy consumed in watt-hours, converted from the Shelly watt-minute total.", "index")
c.add("emeter_power_watts", "Current active power measured by an energy meter in watts.", "index")
c.add("emeter_reactive_power_var", "Current reactive power measured by an energy meter in var.", "index")
c.add("emeter_power_factor", "Power factor measured by an energy meter.", "index")
c.add("emeter_current_amperes", "Current measured by an energy meter in amperes.", "index")
c.add("emeter_voltage_volts", "RMS voltage measured by an energy meter in volts.", "index")
c.add("emeter_valid", "Whether the energy meter reading is valid (1) or not (0).", "index")
c.add("emeter_energy_watt_hours_total", "Total energy consumed as reported by the energy meter in watt-hours.", "index")
c.add("emeter_returned_energy_watt_hours_total", "Total energy returned to the grid in watt-hours.", "index")
c.add("total_power_watts", "Total active power across all device channels in watts.")
c.add("roller_info", "Roller operating state.", "index", "state", "stop_reason", "last_direction")
c.add("roller_power_watts", "Current roller power consumption in watts.", "index")
c.add("roller_valid", "Whether the roller power reading is valid (1) or not (0).", "index")
c.add("roller_safety_switch", "Whether the roller safety switch is active (1) or not (0).", "index")
c.add("roller_overtemperature", "Whether roller overtemperature protection is active (1) or not (0).", "index")
c.add("roller_position_percent", "Current roller position in percent.", "index")
c.add("roller_calibrating", "Whether roller calibration is running (1) or not (0).", "index")
c.add("roller_positioning", "Whether roller positioning control is available (1) or not (0).", "index")
c.add("light_info", "Light operating mode.", "index", "mode")
c.add("light_on", "Whether the light output is on (1) or off (0).", "index")
c.add("light_has_timer", "Whether a light timer is active (1) or not (0).", "index")
c.add("light_timer_remaining_seconds", "Remaining duration of the active light timer in seconds.", "index")
c.add("light_brightness_percent", "Light brightness in percent.", "index")
c.add("light_red", "Red channel value from 0 to 255.", "index")
c.add("light_green", "Green channel value from 0 to 255.", "index")
c.add("light_blue", "Blue channel value from 0 to 255.", "index")
c.add("light_white", "White channel value from 0 to 255.", "index")
c.add("light_gain_percent", "Color mode gain in percent.", "index")
c.add("light_color_temperature_kelvin", "Configured white color temperature in kelvin.", "index")
c.add("light_effect", "Selected light effect number.", "index")
c.add("temperature_celsius", "Device or sensor temperature in degrees Celsius.")
c.add("temperature_valid", "Whether the temperature reading is valid (1) or not (0).")
c.add("temperature_status_info", "Device temperature status.", "status")
c.add("overtemperature", "Whether overtemperature protection is active (1) or not (0).")
c.add("humidity_percent", "Relative humidity in percent.")
c.add("humidity_valid", "Whether the humidity reading is valid (1) or not (0).")
c.add("battery_percent", "Estimated remaining battery capacity in percent.")
c.add("battery_voltage_volts", "Measured battery voltage in volts.")
c.add("illuminance_lux", "Measured illuminance in lux.")
c.add("illuminance_valid", "Whether the illuminance reading is valid (1) or not (0).")
c.add("illuminance_info", "Classified illumination level.", "illumination")
c.add("sensor_valid", "Whether the primary sensor is valid (1) or not (0).")
c.add("sensor_error", "Product-specific sensor error code.")
c.add("connect_retries", "Number of Wi-Fi connection retries during the current wake cycle.")
c.add("motion", "Whether motion is detected (1) or not (0).")
c.add("motion_active", "Whether motion detection is active (1) or not (0).")
c.add("motion_timestamp_seconds", "Unix timestamp of the motion sensor reading.")
c.add("vibration", "Whether vibration is detected (1) or not (0).")
c.add("charger_connected", "Whether an external charger is connected (1) or not (0).")
c.add("smoke", "Whether smoke is detected (1) or not (0).")
c.add("flood", "Whether a flood condition is detected (1) or not (0).")
c.add("rain_sensor_mode", "Whether rain sensor mode is enabled (1) or not (0).")
c.add("door_window_info", "Door/window sensor state.", "state")
c.add("tilt_degrees", "Door/window sensor tilt in degrees.")
c.add("vibration_value", "Raw door/window vibration state; -1 means disabled.")
c.add("vibration_time_seconds", "Door/window vibration state validity time in seconds.")
c.add("adc_voltage_volts", "Voltage measured by an ADC input in volts.", "index")
c.add("external_temperature_celsius", "Temperature measured by an external sensor in degrees Celsius.", "index", "hardware_id")
c.add("external_humidity_percent", "Relative humidity measured by an external sensor in percent.", "index", "hardware_id")
c.add("thermostat_valve_position_percent", "Thermostatic valve position in percent.", "index")
c.add("thermostat_target_enabled", "Whether automatic target temperature control is enabled (1) or not (0).", "index")
c.add("thermostat_target_temperature_celsius", "Thermostat target temperature in degrees Celsius.", "index")
c.add("thermostat_temperature_celsius", "Temperature measured by the thermostat in degrees Celsius.", "index")
c.add("thermostat_temperature_valid", "Whether the thermostat temperature reading is valid (1) or not (0).", "index")
c.add("thermostat_schedule_enabled", "Whether the thermostat schedule is enabled (1) or not (0).", "index")
c.add("thermostat_schedule_profile", "Selected thermostat schedule profile.", "index")
c.add("thermostat_boost_minutes", "Remaining or configured thermostat boost duration in minutes.", "index")
c.add("thermostat_window_open", "Whether the thermostat reports an open window (1) or not (0).", "index")
c.add("thermostat_calibrated", "Whether the thermostatic valve is calibrated (1) or not (0).")
c.add("gas_concentration_ppm", "Measured combustible gas concentration in parts per million.")
c.add("gas_concentration_valid", "Whether the gas concentration reading is valid (1) or not (0).")
c.add("gas_sensor_info", "Gas sensor operating and alarm state.", "sensor_state", "self_test_state", "alarm_state")
c.add("gas_valve_info", "Gas valve addon state.", "index", "state")
}
func (c *gen1Collector) add(name, help string, extraLabels ...string) {
labels := append([]string{"device"}, extraLabels...)
c.descriptors[name] = prometheus.NewDesc("shelly_"+name, help, labels, nil)
}
func (c *gen1Collector) Describe(channel chan<- *prometheus.Desc) {
c.base.describe(channel)
for _, descriptor := range c.descriptors {
channel <- descriptor
}
}
func (c *gen1Collector) Collect(channel chan<- prometheus.Metric) {
started := time.Now()
var status gen1Status
err := c.client.GetStatus(context.Background(), &status)
c.base.collect(channel, err == nil, time.Since(started).Seconds())
if err != nil {
log.Warnf("Cannot collect status from Shelly device %q: %v", c.base.device, err)
return
}
c.collectCommon(channel, &status)
c.collectRelays(channel, status.Relays)
inputs := status.Inputs
if len(inputs) == 0 && status.Input != nil {
inputs = []gen1Input{{Input: status.Input}}
}
c.collectInputs(channel, inputs)
c.collectMeters(channel, status.Meters)
c.collectEMeters(channel, status.EMeters)
c.collectRollers(channel, status.Rollers)
c.collectLights(channel, status.Lights)
c.collectThermostats(channel, &status)
c.collectSensors(channel, &status)
}
func (c *gen1Collector) collectCommon(channel chan<- prometheus.Metric, status *gen1Status) {
firmware := ""
if status.Update != nil {
firmware = status.Update.OldVersion
}
c.gauge(channel, "device_info", 1, c.base.product, status.MAC, firmware)
if status.WiFiStation != nil {
c.gauge(channel, "wifi_info", 1, status.WiFiStation.SSID, status.WiFiStation.IP)
c.gauge(channel, "wifi_connected", boolFloat(status.WiFiStation.Connected))
c.gauge(channel, "wifi_rssi_dbm", float64(status.WiFiStation.RSSI))
}
if status.Cloud != nil {
c.gauge(channel, "cloud_enabled", boolFloat(status.Cloud.Enabled))
c.gauge(channel, "cloud_connected", boolFloat(status.Cloud.Connected))
}
if status.MQTT != nil {
c.gauge(channel, "mqtt_connected", boolFloat(status.MQTT.Connected))
}
if status.HasUpdate != nil || status.Update != nil {
available := status.HasUpdate != nil && *status.HasUpdate
if status.Update != nil {
available = available || status.Update.HasUpdate
}
c.gauge(channel, "update_available", boolFloat(available))
}
if status.Update != nil {
c.gauge(channel, "update_info", 1, status.Update.Status, status.Update.OldVersion, status.Update.NewVersion)
}
if status.Serial != nil {
c.gauge(channel, "status_serial", float64(*status.Serial))
}
if status.RAMTotal != nil {
c.gauge(channel, "ram_size_bytes", float64(*status.RAMTotal))
}
if status.RAMFree != nil {
c.gauge(channel, "ram_free_bytes", float64(*status.RAMFree))
}
if status.FSSize != nil {
c.gauge(channel, "filesystem_size_bytes", float64(*status.FSSize))
}
if status.FSFree != nil {
c.gauge(channel, "filesystem_free_bytes", float64(*status.FSFree))
}
if status.FSMounted != nil {
c.gauge(channel, "filesystem_mounted", boolFloat(*status.FSMounted))
}
if status.Uptime != nil {
c.gauge(channel, "uptime_seconds", *status.Uptime)
}
if status.TotalPower != nil {
c.gauge(channel, "total_power_watts", *status.TotalPower)
}
}
func (c *gen1Collector) collectRelays(channel chan<- prometheus.Metric, relays []gen1Relay) {
for index, relay := range relays {
label := strconv.Itoa(index)
c.optionalBool(channel, "relay_on", relay.On, label)
c.optionalBool(channel, "relay_has_timer", relay.HasTimer, label)
c.optionalGauge(channel, "relay_timer_duration_seconds", relay.TimerDuration, label)
c.optionalGauge(channel, "relay_timer_remaining_seconds", relay.TimerRemaining, label)
c.optionalBool(channel, "relay_overpower", relay.Overpower, label)
c.optionalBool(channel, "relay_valid", relay.Valid, label)
}
}
func (c *gen1Collector) collectInputs(channel chan<- prometheus.Metric, inputs []gen1Input) {
for index, input := range inputs {
label := strconv.Itoa(index)
c.optionalGauge(channel, "input_on", input.Input, label)
if input.Event != "" || input.LastSequence != "" {
c.gauge(channel, "input_event_info", 1, label, input.Event, input.LastSequence)
}
if input.EventCount != nil && *input.EventCount >= 0 {
c.counter(channel, "input_event_count_total", *input.EventCount, label)
}
}
}
func (c *gen1Collector) collectMeters(channel chan<- prometheus.Metric, meters []gen1Meter) {
for index, meter := range meters {
label := strconv.Itoa(index)
c.optionalGauge(channel, "meter_power_watts", meter.Power, label)
c.optionalBool(channel, "meter_valid", meter.Valid, label)
c.optionalGauge(channel, "meter_timestamp_seconds", meter.Timestamp, label)
if meter.Overpower != nil {
c.optionalGauge(channel, "meter_overpower_threshold_watts", meter.Overpower.Number, label)
c.optionalBool(channel, "meter_overpower", meter.Overpower.Bool, label)
}
if meter.Total != nil && *meter.Total >= 0 {
c.counter(channel, "meter_energy_watt_hours_total", *meter.Total/60, label)
}
for minute, recentEnergy := range meter.Counters {
c.gauge(channel, "meter_recent_energy_watt_minutes", recentEnergy, label, strconv.Itoa(minute))
}
}
}
func (c *gen1Collector) collectEMeters(channel chan<- prometheus.Metric, meters []gen1EMeter) {
for index, meter := range meters {
label := strconv.Itoa(index)
c.optionalGauge(channel, "emeter_power_watts", meter.Power, label)
c.optionalGauge(channel, "emeter_reactive_power_var", meter.ReactivePower, label)
c.optionalGauge(channel, "emeter_power_factor", meter.PowerFactor, label)
c.optionalGauge(channel, "emeter_current_amperes", meter.Current, label)
c.optionalGauge(channel, "emeter_voltage_volts", meter.Voltage, label)
c.optionalBool(channel, "emeter_valid", meter.Valid, label)
if meter.Total != nil && *meter.Total >= 0 {
c.counter(channel, "emeter_energy_watt_hours_total", *meter.Total, label)
}
if meter.TotalReturned != nil && *meter.TotalReturned >= 0 {
c.counter(channel, "emeter_returned_energy_watt_hours_total", *meter.TotalReturned, label)
}
}
}
func (c *gen1Collector) collectRollers(channel chan<- prometheus.Metric, rollers []gen1Roller) {
for index, roller := range rollers {
label := strconv.Itoa(index)
if roller.State != "" || roller.StopReason != "" || roller.LastDirection != "" {
c.gauge(channel, "roller_info", 1, label, roller.State, roller.StopReason, roller.LastDirection)
}
c.optionalGauge(channel, "roller_power_watts", roller.Power, label)
c.optionalBool(channel, "roller_valid", roller.Valid, label)
c.optionalBool(channel, "roller_safety_switch", roller.SafetySwitch, label)
c.optionalBool(channel, "roller_overtemperature", roller.Overtemperature, label)
c.optionalGauge(channel, "roller_position_percent", roller.CurrentPosition, label)
c.optionalBool(channel, "roller_calibrating", roller.Calibrating, label)
c.optionalBool(channel, "roller_positioning", roller.Positioning, label)
}
}
func (c *gen1Collector) collectLights(channel chan<- prometheus.Metric, lights []gen1Light) {
for index, light := range lights {
label := strconv.Itoa(index)
if light.Mode != "" {
c.gauge(channel, "light_info", 1, label, light.Mode)
}
c.optionalBool(channel, "light_on", light.On, label)
c.optionalBool(channel, "light_has_timer", light.HasTimer, label)
c.optionalGauge(channel, "light_timer_remaining_seconds", light.TimerRemaining, label)
c.optionalGauge(channel, "light_brightness_percent", light.Brightness, label)
c.optionalGauge(channel, "light_red", light.Red, label)
c.optionalGauge(channel, "light_green", light.Green, label)
c.optionalGauge(channel, "light_blue", light.Blue, label)
c.optionalGauge(channel, "light_white", light.White, label)
c.optionalGauge(channel, "light_gain_percent", light.Gain, label)
c.optionalGauge(channel, "light_color_temperature_kelvin", light.Temperature, label)
c.optionalGauge(channel, "light_effect", light.Effect, label)
}
}
func (c *gen1Collector) collectThermostats(channel chan<- prometheus.Metric, status *gen1Status) {
if len(status.Thermostats) > 0 {
c.optionalBool(channel, "thermostat_calibrated", status.Calibrated)
}
for index, thermostat := range status.Thermostats {
label := strconv.Itoa(index)
c.optionalGauge(channel, "thermostat_valve_position_percent", thermostat.Position, label)
if thermostat.Target != nil {
c.optionalBool(channel, "thermostat_target_enabled", thermostat.Target.Enabled, label)
if thermostat.Target.Units == "C" {
c.optionalGauge(channel, "thermostat_target_temperature_celsius", thermostat.Target.Value, label)
}
}
if thermostat.Temperature != nil {
temperature := thermostat.Temperature.C
if temperature == nil && thermostat.Temperature.Units == "C" {
temperature = thermostat.Temperature.Value
}
c.optionalGauge(channel, "thermostat_temperature_celsius", temperature, label)
c.optionalBool(channel, "thermostat_temperature_valid", thermostat.Temperature.Valid, label)
}
c.optionalBool(channel, "thermostat_schedule_enabled", thermostat.Schedule, label)
c.optionalGauge(channel, "thermostat_schedule_profile", thermostat.ScheduleProfile, label)
c.optionalGauge(channel, "thermostat_boost_minutes", thermostat.BoostMinutes, label)
c.optionalBool(channel, "thermostat_window_open", thermostat.WindowOpen, label)
}
}
func (c *gen1Collector) collectSensors(channel chan<- prometheus.Metric, status *gen1Status) {
temperature := status.Temperature
if temperature == nil && status.TemperatureSensor != nil {
temperature = status.TemperatureSensor.C
if temperature == nil && status.TemperatureSensor.Units == "C" {
temperature = status.TemperatureSensor.Value
}
}
c.optionalGauge(channel, "temperature_celsius", temperature)
if status.TemperatureSensor != nil {
c.optionalBool(channel, "temperature_valid", status.TemperatureSensor.Valid)
}
if status.TemperatureStatus != "" {
c.gauge(channel, "temperature_status_info", 1, status.TemperatureStatus)
}
c.optionalBool(channel, "overtemperature", status.Overtemperature)
if status.Humidity != nil {
c.optionalGauge(channel, "humidity_percent", status.Humidity.Value)
c.optionalBool(channel, "humidity_valid", status.Humidity.Valid)
}
if status.Battery != nil {
c.optionalGauge(channel, "battery_percent", status.Battery.Value)
c.optionalGauge(channel, "battery_voltage_volts", status.Battery.Voltage)
}
if status.Lux != nil {
c.optionalGauge(channel, "illuminance_lux", status.Lux.Value)
c.optionalBool(channel, "illuminance_valid", status.Lux.Valid)
if status.Lux.Illumination != "" {
c.gauge(channel, "illuminance_info", 1, status.Lux.Illumination)
}
}
c.optionalGauge(channel, "sensor_error", status.SensorError)
c.optionalGauge(channel, "connect_retries", status.ConnectRetries)
c.optionalBool(channel, "charger_connected", status.Charger)
c.optionalBool(channel, "smoke", status.Smoke)
c.optionalBool(channel, "flood", status.Flood)
c.optionalBool(channel, "rain_sensor_mode", status.RainSensor)
motion := status.Motion
sensorValid := status.Valid
if status.Sensor != nil {
if status.Sensor.Motion != nil {
motion = status.Sensor.Motion
}
if status.Sensor.Valid != nil {
sensorValid = status.Sensor.Valid
}
c.optionalBool(channel, "motion_active", status.Sensor.Active)
c.optionalGauge(channel, "motion_timestamp_seconds", status.Sensor.Timestamp)
c.optionalBool(channel, "vibration", status.Sensor.Vibration)
if status.Sensor.State != "" {
c.gauge(channel, "door_window_info", 1, status.Sensor.State)
}
}
c.optionalBool(channel, "motion", motion)
c.optionalBool(channel, "sensor_valid", sensorValid)
if status.Acceleration != nil {
c.optionalGauge(channel, "tilt_degrees", status.Acceleration.Tilt)
c.optionalGauge(channel, "vibration_value", status.Acceleration.Vibration)
c.optionalGauge(channel, "vibration_time_seconds", status.Acceleration.VibrationTime)
}
for index, adc := range status.ADCs {
c.optionalGauge(channel, "adc_voltage_volts", adc.Voltage, strconv.Itoa(index))
}
for _, index := range sortedKeys(status.ExternalTemperature) {
sensor := status.ExternalTemperature[index]
c.optionalGauge(channel, "external_temperature_celsius", sensor.C, index, sensor.HardwareID)
}
for _, index := range sortedKeys(status.ExternalHumidity) {
sensor := status.ExternalHumidity[index]
c.optionalGauge(channel, "external_humidity_percent", sensor.Humidity, index, sensor.HardwareID)
}
if status.GasSensor != nil {
c.gauge(channel, "gas_sensor_info", 1, status.GasSensor.SensorState, status.GasSensor.SelfTestState, status.GasSensor.AlarmState)
}
if status.Concentration != nil {
c.optionalGauge(channel, "gas_concentration_ppm", status.Concentration.PPM)
c.optionalBool(channel, "gas_concentration_valid", status.Concentration.Valid)
}
for index, valve := range status.Valves {
c.gauge(channel, "gas_valve_info", 1, strconv.Itoa(index), valve.State)
}
}
func (c *gen1Collector) gauge(channel chan<- prometheus.Metric, name string, value float64, labels ...string) {
c.emit(channel, name, prometheus.GaugeValue, value, labels...)
}
func (c *gen1Collector) counter(channel chan<- prometheus.Metric, name string, value float64, labels ...string) {
c.emit(channel, name, prometheus.CounterValue, value, labels...)
}
func (c *gen1Collector) emit(channel chan<- prometheus.Metric, name string, valueType prometheus.ValueType, value float64, labels ...string) {
allLabels := append([]string{c.base.device}, labels...)
channel <- prometheus.MustNewConstMetric(c.descriptors[name], valueType, value, allLabels...)
}
func (c *gen1Collector) optionalGauge(channel chan<- prometheus.Metric, name string, value *float64, labels ...string) {
if value != nil {
c.gauge(channel, name, *value, labels...)
}
}
func (c *gen1Collector) optionalBool(channel chan<- prometheus.Metric, name string, value *bool, labels ...string) {
if value != nil {
c.gauge(channel, name, boolFloat(*value), labels...)
}
}
func boolFloat(value bool) float64 {
if value {
return 1
}
return 0
}
func sortedKeys[T any](values map[string]T) []string {
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}