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 } }