-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
180 lines (151 loc) · 4.01 KB
/
main.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package main
import (
"encoding/json"
"net/http"
"regexp"
"strconv"
"github.com/fatih/structs"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/sh0rez/luxtronik2-exporter/pkg/luxtronik"
)
// Version of the app. To be set by ldflags
var Version = "dev"
// metrics
var (
gauges = make(map[string]*prometheus.GaugeVec)
lastUpdate = promauto.NewGauge(prometheus.GaugeOpts{
Name: "last_update",
Namespace: "luxtronik",
Help: "UNIX timestamp of the last time an update was received from the heatpump",
})
)
// Config holds the configuration structure
type Config struct {
Verbose bool `flag:"verbose" short:"v" help:"Show debug logs"`
Address string `flag:"address" short:"a" help:"IP or hostname of the heatpump"`
Filters luxtronik.Filters
Mutes []struct {
Domain string
Field string
}
}
func main() {
cmd := &cobra.Command{
Use: "luxtronik2-exporter",
Short: "Expose metrics from luxtronik2 based heatpumps in Prometheus format.",
Version: Version,
}
// file config
viper.SetConfigName("lux")
viper.AddConfigPath(".")
// flag config
for _, s := range structs.Fields(Config{}) {
if s.Tag("flag") != "" {
cmd.Flags().StringP(s.Tag("flag"), s.Tag("short"), s.Tag("default"), s.Tag("help"))
}
}
viper.BindPFlags(cmd.Flags())
// env config
viper.SetEnvPrefix("lux")
viper.AutomaticEnv()
cmd.Run = func(cmd *cobra.Command, args []string) {
// unmarshal sources
var config Config
if err := viper.ReadInConfig(); err != nil {
log.WithField("err", err).Fatal("Error getting config from sources")
}
if err := viper.Unmarshal(&config); err != nil {
log.Error(err)
log.WithField("err", err).Fatal("invalid config")
}
log.SetLevel(log.InfoLevel)
if config.Verbose {
log.SetLevel(log.DebugLevel)
}
run(&config)
}
if err := cmd.Execute(); err != nil {
log.Fatalln(err)
}
}
func run(config *Config) {
mutes = make(MuteList, len(config.Mutes))
for i, m := range config.Mutes {
mutes[i] = Mute{
domain: regexp.MustCompile(m.Domain),
field: regexp.MustCompile(m.Field),
}
}
// connect to the heatpump
lux := luxtronik.Connect(config.Address, config.Filters)
// create gauge metric for each domain
for name := range lux.Domains() {
gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "luxtronik",
Name: name,
},
[]string{
"attr",
},
)
prometheus.MustRegister(gauge)
gauges[name] = gauge
}
// register update handler, gets called by the update routine
// updates changed metrics
lux.OnUpdate = func(new []luxtronik.Location) {
for _, loc := range new {
domain := loc.Domain
field := loc.Field
value := lux.Value(domain, field)
setMetric(domain, field, value)
}
lastUpdate.SetToCurrentTime()
}
// expose all known values as metric
for domainName, domains := range lux.Domains() {
for field, value := range domains {
setMetric(domainName, field, value)
}
}
// serve the /metrics endpoint
http.Handle("/metrics", promhttp.Handler())
log.Fatalln(http.ListenAndServe(":2112", nil))
}
// jsonMetric represents the json-representation of a metric, created by the filter rules
type jsonMetric struct {
Unit string `json:"unit"`
Value string `json:"value"`
}
// setMetric updates sets the gauge of a metric to a value
func setMetric(domain, field, value string) {
gauge := gauges[domain]
var jv jsonMetric
err := json.Unmarshal([]byte(value), &jv)
v, err := strconv.ParseFloat(jv.Value, 64)
if err != nil {
if !mutes.muted(domain, field) {
log.WithFields(
log.Fields{
"domain": domain,
"field": field,
"value": value,
}).Warn("metric value parse failure")
}
return
}
id := field
if jv.Unit != "" {
id = id + "_" + jv.Unit
}
gauge.WithLabelValues(id).Set(v)
log.WithFields(log.Fields{
"id": id,
"value": v,
}).Debug("updated metric")
}