diff --git a/main.go b/main.go new file mode 100644 index 0000000..b9fec80 --- /dev/null +++ b/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io" + "log" + "net" + "net/http" + _ "net/http/pprof" + "strconv" + "strings" + + "git.savin.nyc/alex/go-sensors/sensors" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + fanspeedDesc = prometheus.NewDesc( + "sensor_lm_fan_speed_rpm", + "fan speed (rotations per minute).", + []string{"fantype", "chip", "adaptor"}, + nil) + + voltageDesc = prometheus.NewDesc( + "sensor_lm_voltage_volts", + "voltage in volts", + []string{"intype", "chip", "adaptor"}, + nil) + + powerDesc = prometheus.NewDesc( + "sensor_lm_power_watts", + "power in watts", + []string{"powertype", "chip", "adaptor"}, + nil) + + temperatureDesc = prometheus.NewDesc( + "sensor_lm_temperature_celsius", + "temperature in celsius", + []string{"temptype", "chip", "adaptor"}, + nil) + + hddTempDesc = prometheus.NewDesc( + "sensor_hddsmart_temperature_celsius", + "temperature in celsius", + []string{"device", "id"}, + nil) +) + +func main() { + var ( + listenAddress = flag.String("web.listen-address", ":9255", "Address on which to expose metrics and web interface.") + metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") + hddtempAddress = flag.String("hddtemp-address", "localhost:7634", "Address to fetch hdd metrics from.") + ) + flag.Parse() + + hddcollector := NewHddCollector(*hddtempAddress) + if err := hddcollector.Init(); err != nil { + log.Printf("error readding hddtemps: %v", err) + } + prometheus.MustRegister(hddcollector) + + lmscollector := NewLmSensorsCollector() + lmscollector.Init() + prometheus.MustRegister(lmscollector) + + http.Handle(*metricsPath, prometheus.Handler()) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(` + Sensor Exporter + +

Sensor Exporter

+

Metrics

+ + `)) + }) + http.ListenAndServe(*listenAddress, nil) +} + +type ( + LmSensorsCollector struct{} +) + +func NewLmSensorsCollector() *LmSensorsCollector { + return &LmSensorsCollector{} +} + +func (l *LmSensorsCollector) Init() { + gosensors.Init() +} + +// Describe implements prometheus.Collector. +func (l *LmSensorsCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- fanspeedDesc + ch <- powerDesc + ch <- temperatureDesc + ch <- voltageDesc +} + +// Collect implements prometheus.Collector. +func (l *LmSensorsCollector) Collect(ch chan<- prometheus.Metric) { + for _, chip := range gosensors.GetDetectedChips() { + chipName := chip.String() + adaptorName := chip.AdapterName() + for _, feature := range chip.GetFeatures() { + if strings.HasPrefix(feature.Name, "fan") { + ch <- prometheus.MustNewConstMetric(fanspeedDesc, + prometheus.GaugeValue, + feature.GetValue(), + feature.GetLabel(), chipName, adaptorName) + } else if strings.HasPrefix(feature.Name, "temp") { + ch <- prometheus.MustNewConstMetric(temperatureDesc, + prometheus.GaugeValue, + feature.GetValue(), + feature.GetLabel(), chipName, adaptorName) + } else if strings.HasPrefix(feature.Name, "in") { + ch <- prometheus.MustNewConstMetric(voltageDesc, + prometheus.GaugeValue, + feature.GetValue(), + feature.GetLabel(), chipName, adaptorName) + } else if strings.HasPrefix(feature.Name, "power") { + ch <- prometheus.MustNewConstMetric(powerDesc, + prometheus.GaugeValue, + feature.GetValue(), + feature.GetLabel(), chipName, adaptorName) + } + } + } +} + +type ( + HddCollector struct { + address string + conn net.Conn + buf bytes.Buffer + } + + HddTemperature struct { + Device string + Id string + TemperatureCelsius float64 + } +) + +func NewHddCollector(address string) *HddCollector { + return &HddCollector{ + address: address, + } +} + +func (h *HddCollector) Init() error { + conn, err := net.Dial("tcp", h.address) + if err != nil { + return fmt.Errorf("error connecting to hddtemp address '%s': %v", h.address, err) + } + h.conn = conn + return nil +} + +func (h *HddCollector) readTempsFromConn() (string, error) { + if h.conn == nil { + if err := h.Init(); err != nil { + return "", err + } + } + + _, err := io.Copy(&h.buf, h.conn) + if err != nil { + return "", fmt.Errorf("Error reading from hddtemp socket: %v", err) + } + return h.buf.String(), nil +} + +func (h *HddCollector) Close() error { + if err := h.conn.Close(); err != nil { + return fmt.Errorf("Error closing hddtemp socket: %v", err) + } + return nil +} + +func parseHddTemps(s string) ([]HddTemperature, error) { + var hddtemps []HddTemperature + if len(s) < 1 || s[0] != '|' { + return nil, fmt.Errorf("Error parsing output from hddtemp: %s", s) + } + for _, item := range strings.Split(s[1:len(s)-1], "||") { + hddtemp, err := parseHddTemp(item) + if err != nil { + return nil, fmt.Errorf("Error parsing output from hddtemp: %v", err) + } else { + hddtemps = append(hddtemps, hddtemp) + } + } + return hddtemps, nil +} + +func parseHddTemp(s string) (HddTemperature, error) { + pieces := strings.Split(s, "|") + if len(pieces) != 4 { + return HddTemperature{}, fmt.Errorf("error parsing item from hddtemp, expected 4 tokens: %s", s) + } + dev, id, temp, unit := pieces[0], pieces[1], pieces[2], pieces[3] + + if unit == "*" { + return HddTemperature{Device: dev, Id: id, TemperatureCelsius: -1}, nil + } + + if unit != "C" { + return HddTemperature{}, fmt.Errorf("error parsing item from hddtemp, I only speak Celsius", s) + } + + ftemp, err := strconv.ParseFloat(temp, 64) + if err != nil { + return HddTemperature{}, fmt.Errorf("Error parsing temperature as float: %s", temp) + } + + return HddTemperature{Device: dev, Id: id, TemperatureCelsius: ftemp}, nil +} + +// Describe implements prometheus.Collector. +func (e *HddCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- hddTempDesc +} + +// Collect implements prometheus.Collector. +func (h *HddCollector) Collect(ch chan<- prometheus.Metric) { + tempsString, err := h.readTempsFromConn() + if err != nil { + log.Printf("error reading temps from hddtemp daemon: %v", err) + return + } + hddtemps, err := parseHddTemps(tempsString) + if err != nil { + log.Printf("error parsing temps from hddtemp daemon: %v", err) + return + } + + for _, ht := range hddtemps { + ch <- prometheus.MustNewConstMetric(hddTempDesc, + prometheus.GaugeValue, + ht.TemperatureCelsius, + ht.Device, + ht.Id) + } +} \ No newline at end of file