83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
// DriveExporter is a Prometheus collector that, on each scrape, discovers the
|
|
// host's drives and emits their numeric health columns as gauges. The label set
|
|
// (the schema's string columns) is attached to every metric so the Prometheus
|
|
// and InfluxDB outputs describe each drive identically.
|
|
type DriveExporter struct {
|
|
descs map[string]*prometheus.Desc
|
|
labels []column // String columns carried as labels.
|
|
gauges []column // Numeric columns emitted as gauges.
|
|
// collect discovers the drives; a field so tests can inject a fixed set
|
|
// without touching real hardware.
|
|
collect func() ([]*Drive, int64)
|
|
}
|
|
|
|
// NewDriveExporter builds the collector with one gauge descriptor per numeric
|
|
// column, labelled with the schema's string columns.
|
|
func NewDriveExporter() *DriveExporter {
|
|
labels, gauges := labelColumns(), gaugeColumns()
|
|
labelNames := make([]string, len(labels))
|
|
for i, c := range labels {
|
|
labelNames[i] = c.name
|
|
}
|
|
descs := make(map[string]*prometheus.Desc, len(gauges))
|
|
for _, c := range gauges {
|
|
descs[c.name] = prometheus.NewDesc(namespace+"_"+c.name, "drive health metric: "+c.name, labelNames, nil)
|
|
}
|
|
return &DriveExporter{descs: descs, labels: labels, gauges: gauges, collect: collect}
|
|
}
|
|
|
|
// Reload is a no-op; the exporter holds no configurable state.
|
|
func (e *DriveExporter) Reload() {}
|
|
|
|
// Describe sends every metric descriptor to the channel.
|
|
func (e *DriveExporter) Describe(ch chan<- *prometheus.Desc) {
|
|
for _, d := range e.descs {
|
|
ch <- d
|
|
}
|
|
}
|
|
|
|
// Collect discovers the drives and emits a gauge per numeric column, with the
|
|
// shared identity label set.
|
|
func (e *DriveExporter) Collect(ch chan<- prometheus.Metric) {
|
|
drives, _ := e.collect()
|
|
for _, d := range drives {
|
|
labelValues := make([]string, len(e.labels))
|
|
for i, c := range e.labels {
|
|
labelValues[i] = format(c.raw(d))
|
|
}
|
|
for _, c := range e.gauges {
|
|
val, ok := gaugeValue(c, d)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ch <- prometheus.MustNewConstMetric(e.descs[c.name], prometheus.GaugeValue, val, labelValues...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// gaugeValue converts a numeric column's value to a float for Prometheus,
|
|
// reporting ok=false when the value is unknown.
|
|
func gaugeValue(c column, d *Drive) (float64, bool) {
|
|
r := c.raw(d)
|
|
switch t := r.(type) {
|
|
case nil:
|
|
return 0, false
|
|
case int:
|
|
return float64(t), true
|
|
case float64:
|
|
return t, true
|
|
case bool:
|
|
if t {
|
|
return 1, true
|
|
}
|
|
return 0, true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|