From 5698d182a2c09bdb108dbfd64eef7a5f690c92b8 Mon Sep 17 00:00:00 2001
From: Richard Mitchell <richard.j.mitchell@gmail.com>
Date: Mon, 24 Sep 2018 21:39:08 +0100
Subject: [PATCH] Add bridge restart metric

---
 README.md  |  5 +++++
 sensors.go | 35 ++++++++++++++++++++++++++++++++++-
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index f62dbf3..c50e22b 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,11 @@ Some sensor type values you might find useful:
 * `ZLLPresence`: the presence sensor in the Hue motion sensor
 * `ZLLLightLevel`: the light level sensor in the Hue motion sensor
 
+## General metrics
+
+* `hue_group_scrapes_failed`, `hue_light_scrapes_failed`, `hue_sensor_scrapes_failed`: count of failures when trying to scrape from the Hue API.
+* `hue_bridge_restarts`: count of times the bridge has restarted (*estimated based on sensor data*).
+
 ## Metric structure
 
 > Hey, why didn't you combine the metrics for brightness and hue and saturation and on and reachable?
diff --git a/sensors.go b/sensors.go
index 660b974..766cf4b 100644
--- a/sensors.go
+++ b/sensors.go
@@ -1,6 +1,8 @@
 package main
 
 import (
+	"math"
+
 	hue "github.com/collinux/gohue"
 	"github.com/prometheus/client_golang/prometheus"
 	log "github.com/prometheus/common/log"
@@ -16,6 +18,7 @@ type sensorCollector struct {
 	sensorBattery       *prometheus.GaugeVec
 	sensorReachable     *prometheus.GaugeVec
 	sensorScrapesFailed prometheus.Counter
+	bridgeRestarts      prometheus.Counter
 }
 
 var variableSensorLabelNames = []string{
@@ -37,6 +40,13 @@ func contains(a []string, x string) bool {
 	return false
 }
 
+func max(a, b int64) int64 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
 // NewSensorCollector Create a new Hue collector for sensors
 func NewSensorCollector(namespace string, bridge Bridge, ignoreTypes []string, matchNames bool) prometheus.Collector {
 	c := sensorCollector{
@@ -96,6 +106,14 @@ func NewSensorCollector(namespace string, bridge Bridge, ignoreTypes []string, m
 				Help:      "Count of scrapes of sensor data from the Hue bridge that have failed",
 			},
 		),
+		bridgeRestarts: prometheus.NewCounter(
+			prometheus.CounterOpts{
+				Namespace: namespace,
+				Subsystem: "bridge",
+				Name:      "restarts",
+				Help:      "Count of number of bridge restarts detected",
+			},
+		),
 	}
 
 	return c
@@ -108,6 +126,7 @@ func (c sensorCollector) Describe(ch chan<- *prometheus.Desc) {
 	c.sensorOn.Describe(ch)
 	c.sensorReachable.Describe(ch)
 	c.sensorScrapesFailed.Describe(ch)
+	c.bridgeRestarts.Describe(ch)
 }
 
 func (c sensorCollector) recordSensor(sensor hue.Sensor, sensorName string, deviceID string, sensorValue float64) {
@@ -149,6 +168,8 @@ func (c sensorCollector) Collect(ch chan<- prometheus.Metric) {
 		c.sensorScrapesFailed.Inc()
 	}
 	sensorNames := make(map[string]string)
+	sensorLastUpdatedHistory := make(map[string]int64)
+	restartDetected := false
 
 	for _, sensor := range sensors {
 		var sensorValue float64
@@ -179,7 +200,10 @@ func (c sensorCollector) Collect(ch chan<- prometheus.Metric) {
 		} else {
 			continue
 		}
-
+		if sensorLastUpdatedHistory[sensor.UniqueID] > math.MinInt64 && sensor.State.LastUpdated.Unix() == math.MinInt64 {
+			restartDetected = true
+		}
+		sensorLastUpdatedHistory[sensor.UniqueID] = sensor.State.LastUpdated.Unix()
 		c.recordSensor(sensor, sensor.Name, deviceID, sensorValue)
 	}
 	// kinda inefficient looping over them twice, but simplies code when name matching is enabled
@@ -201,13 +225,22 @@ func (c sensorCollector) Collect(ch chan<- prometheus.Metric) {
 				sensorName = sensor.Name
 			}
 		}
+		if sensorLastUpdatedHistory[sensor.UniqueID] > math.MinInt64 && sensor.State.LastUpdated.Unix() == math.MinInt64 {
+			restartDetected = true
+		}
+		sensorLastUpdatedHistory[sensor.UniqueID] = sensor.State.LastUpdated.Unix()
 		c.recordSensor(sensor, sensorName, deviceID, sensorValue)
 	}
 
+	if restartDetected {
+		c.bridgeRestarts.Inc()
+	}
+
 	c.sensorValue.Collect(ch)
 	c.sensorBattery.Collect(ch)
 	c.sensorLastUpdated.Collect(ch)
 	c.sensorOn.Collect(ch)
 	c.sensorReachable.Collect(ch)
 	c.sensorScrapesFailed.Collect(ch)
+	c.bridgeRestarts.Collect(ch)
 }
-- 
GitLab