forked from paha/liquidctl-exporter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
liquidctl-exporter.go
153 lines (131 loc) · 3.53 KB
/
liquidctl-exporter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/log"
)
const (
metricPrefix = "liquidctl"
envPort = "LIQUIDCTL_EXPORTER_PORT"
envInterval = "LIQUIDCTL_EXPORTER_INTERVAL"
envPath = "LIQUIDCTL_EXPORTER_PATH"
)
var (
defaultPort = "9530"
defaultInterval = "10"
defaultLiquidCMD = "/usr/local/bin/liquidctl"
)
// liquidctl statistic object as of v1.6.x
type liquidctlStatistic struct {
Bus string `json:"bus"`
Address string `json:"address"`
Description string `json:"description"`
Status []status `json:"status"`
}
// liquidctl status
type status struct {
Key string `json:"key"`
Value float64 `json:"value"`
Unit string `json:"unit"`
}
// Metrics store per device ({deviceID: {metricID: prom.Gauge}})
var devices = map[string]map[string]prometheus.Gauge{}
// path to liquidctl executable
var path string
func init() {
p, ok := os.LookupEnv(envPath)
if !ok {
path = defaultLiquidCMD
} else {
path = p
}
log.Infof("liquidctl configured path, %s", path)
// Register metrics available for each liquidctl device
for _, d := range liquidctl_stats() {
dname := deviceName(d.Address)
devices[dname] = map[string]prometheus.Gauge{}
for _, m := range d.Status {
name := metricName(m.Key, dname)
log.Infof("Registering metric %s for %s device", name, dname)
devices[dname][name] = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: name,
Help: fmt.Sprintf("%s %s (%s).", d.Description, m.Key, m.Unit),
},
)
prometheus.MustRegister(devices[dname][name])
}
}
}
func main() {
log.Info("Starting liquidctl exporter")
port, ok := os.LookupEnv(envPort)
if !ok {
port = defaultPort
}
interval, ok := os.LookupEnv(envInterval)
if !ok {
interval = defaultInterval
}
i, _ := strconv.Atoi(interval)
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head><title>liquidctl Exporter</title></head>
<body>
<h1>liquidctl Exporter</h1>
<p><a href="/metrics">Metrics</a></p>
</body>
</html>`))
})
log.Infof("Exposing mertics over HTTP on port: %s", port)
go http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
// collection loop, without Desc/Collector pathern
for {
for _, d := range liquidctl_stats() {
dname := deviceName(d.Address)
for _, m := range d.Status {
name := metricName(m.Key, dname)
devices[dname][name].Set(m.Value)
}
}
time.Sleep(time.Second * time.Duration(i))
}
}
func liquidctl_stats() []liquidctlStatistic {
cmd := exec.Command(path, "status", "--json")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
var stats []liquidctlStatistic
if err := json.Unmarshal([]byte(out.String()), &stats); err != nil {
log.Fatal(err)
}
return stats
}
func metricName(n, device string) string {
// Format: metricPrefix_deviceID_metric
// replace spaces with underscores
name := strings.ReplaceAll(n, " ", "_")
// trim + signes
name = strings.Trim(strings.ToLower(name), "+")
// Append common prefix for all metrics
name = fmt.Sprintf("%s_%s_%s", metricPrefix, device, name)
// trimming dots by split+join
return strings.Join(strings.Split(name, "."), "")
}
// returns address unchanged to work on TrueNAS since /dev/ is not used on FreeBSD
func deviceName(n string) string {
return n
}