-
Notifications
You must be signed in to change notification settings - Fork 3
/
liquidctl-exporter.go
166 lines (142 loc) · 4.05 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
154
155
156
157
158
159
160
161
162
163
164
165
166
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 interface{} `json:"value"` // liquidctl also send string metrics
Unit string `json:"unit"`
}
// Metrics store per device ({deviceID: {metricID: prom.Gauge}})
var devices = map[string]map[string]*prometheus.GaugeVec{}
// 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.GaugeVec{}
for _, m := range d.Status {
name := metricName(m.Key, dname)
// Register metrics based on type.
switch t := m.Value.(type) {
case float64:
log.Infof("Registering metric '%s' for '%s' device", name, dname)
devices[dname][name] = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: name,
Help: fmt.Sprintf("%s %s (%s).", d.Description, m.Key, m.Unit),
},
[]string{"device"},
)
prometheus.MustRegister(devices[dname][name])
default: // Currently only float64 are implemented
log.Warnf("Registering metric '%s' for '%s' device failed since liquidctrl-exported does not yet support metrics of type '%T!'\n", name, dname, t)
}
}
}
}
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)
// Push metric to db if it was registered
_, ok := devices[dname][name]
if ok {
devices[dname][name].WithLabelValues(dname).Set(m.Value.(float64))
}
}
}
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", metricPrefix, name)
// trimming dots by split+join
return strings.Join(strings.Split(name, "."), "")
}
// returns Unix device as an identifier/name of a liquidctl managed device
func deviceName(n string) string {
return strings.Split(n, "/dev/")[1]
}