change to multidevice scrape and adding info

This commit is contained in:
Andre Kully 2021-12-23 16:49:57 +01:00
parent f591467354
commit 23b5987364
2 changed files with 199 additions and 130 deletions

84
main.go
View file

@ -27,6 +27,8 @@ const (
ERROR_PARSING_VALUE ERROR_PARSING_VALUE
) )
const namespace = "mystrom_exporter"
var ( var (
listenAddress = flag.String("web.listen-address", ":9452", listenAddress = flag.String("web.listen-address", ":9452",
"Address to listen on") "Address to listen on")
@ -65,44 +67,11 @@ func main() {
} }
// -- create a new registry for the exporter telemetry // -- create a new registry for the exporter telemetry
telemetryRegistry := prometheus.NewRegistry() telemetryRegistry := setupMetrics()
telemetryRegistry.MustRegister(prometheus.NewGoCollector())
telemetryRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
mystromDurationCounterVec = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "mystrom_request_duration_seconds_total",
Help: "Total duration of mystrom successful requests by target in seconds",
}, []string{"target"})
telemetryRegistry.MustRegister(mystromDurationCounterVec)
mystromRequestsCounterVec = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "mystrom_requests_total",
Help: "Number of mystrom request by status and target",
}, []string{"target", "status"})
telemetryRegistry.MustRegister(mystromRequestsCounterVec)
// -- make the build information is available through a metric
buildInfo := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "scripts",
Name: "build_info",
Help: "A metric with a constant '1' value labeled by build information.",
},
[]string{"version", "revision", "branch", "goversion", "builddate", "builduser"},
)
buildInfo.WithLabelValues(version.Version, version.Revision, version.Branch, version.GoVersion, version.BuildDate, version.BuildUser).Set(1)
telemetryRegistry.MustRegister(buildInfo)
exporter := mystrom.NewExporter()
// prometheus.MustRegister(exporter)
router := http.NewServeMux() router := http.NewServeMux()
router.Handle(*metricsPath, promhttp.HandlerFor(telemetryRegistry, promhttp.HandlerOpts{})) router.Handle(*metricsPath, promhttp.HandlerFor(telemetryRegistry, promhttp.HandlerOpts{}))
router.Handle(*devicePath, router.HandleFunc(*devicePath, scrapeHandler)
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
scrapeHandler(exporter, w, r)
}),
)
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(landingPage) w.Write(landingPage)
}) })
@ -110,7 +79,7 @@ func main() {
log.Fatal(http.ListenAndServe(*listenAddress, router)) log.Fatal(http.ListenAndServe(*listenAddress, router))
} }
func scrapeHandler(e *mystrom.Exporter, w http.ResponseWriter, r *http.Request) { func scrapeHandler(w http.ResponseWriter, r *http.Request) {
target := r.URL.Query().Get("target") target := r.URL.Query().Get("target")
if target == "" { if target == "" {
http.Error(w, "'target' parameter must be specified", http.StatusBadRequest) http.Error(w, "'target' parameter must be specified", http.StatusBadRequest)
@ -118,9 +87,10 @@ func scrapeHandler(e *mystrom.Exporter, w http.ResponseWriter, r *http.Request)
} }
log.Infof("got scrape request for target '%v'", target) log.Infof("got scrape request for target '%v'", target)
exporter := mystrom.NewExporter(target)
start := time.Now() start := time.Now()
gatherer, err := e.Scrape(target) gatherer, err := exporter.Scrape()
duration := time.Since(start).Seconds() duration := time.Since(start).Seconds()
if err != nil { if err != nil {
if strings.Contains(fmt.Sprintf("%v", err), "unable to connect with target") { if strings.Contains(fmt.Sprintf("%v", err), "unable to connect with target") {
@ -142,5 +112,43 @@ func scrapeHandler(e *mystrom.Exporter, w http.ResponseWriter, r *http.Request)
mystromRequestsCounterVec.WithLabelValues(target, OK.String()).Inc() mystromRequestsCounterVec.WithLabelValues(target, OK.String()).Inc()
promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}).ServeHTTP(w, r) promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}).ServeHTTP(w, r)
}
// -- setupMetrics creates a new registry for the exporter telemetry
func setupMetrics() *prometheus.Registry {
registry := prometheus.NewRegistry()
registry.MustRegister(prometheus.NewGoCollector())
registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
mystromDurationCounterVec = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Name: "request_duration_seconds_total",
Help: "Total duration of mystrom successful requests by target in seconds",
},
[]string{"target"})
registry.MustRegister(mystromDurationCounterVec)
mystromRequestsCounterVec = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Name: "requests_total",
Help: "Number of mystrom request by status and target",
},
[]string{"target", "status"})
registry.MustRegister(mystromRequestsCounterVec)
// -- make the build information is available through a metric
buildInfo := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "build_info",
Help: "A metric with a constant '1' value labeled by build information.",
},
[]string{"version", "revision", "branch", "goversion", "builddate", "builduser"},
)
buildInfo.WithLabelValues(version.Version, version.Revision, version.Branch, version.GoVersion, version.BuildDate, version.BuildUser).Set(1)
registry.MustRegister(buildInfo)
return registry
} }

View file

@ -7,11 +7,16 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/prometheus/common/log"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
const namespace = "mystrom" const namespace = "mystrom"
// 5 second timeout, might need to be increased
const reqTimeout = time.Second * 5
type switchReport struct { type switchReport struct {
Power float64 `json:"power"` Power float64 `json:"power"`
WattPerSec float64 `json:"Ws"` WattPerSec float64 `json:"Ws"`
@ -19,135 +24,191 @@ type switchReport struct {
Temperature float64 `json:"temperature"` Temperature float64 `json:"temperature"`
} }
type Exporter struct { type switchInfo struct {
myStromSwitchIp string Version string `json:"version"`
Mac string `json:"mac"`
SwType float64 `json:"type"`
SSID string `json:"ssid"`
Static bool `json:"static"`
Connected bool `json:"connected"`
} }
var ( // Exporter --
up = prometheus.NewDesc( type Exporter struct {
prometheus.BuildFQName(namespace, "", "up"), myStromSwitchIp string
"Was the last myStrom query successful.", switchType float64
nil, nil, }
)
myStromPower = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "report_power"),
"The current power consumed by devices attached to the switch",
nil, nil,
)
myStromRelay = prometheus.NewDesc( // NewExporter --
prometheus.BuildFQName(namespace, "", "report_relay"), func NewExporter(switchIP string) *Exporter {
"The current state of the relay (wether or not the relay is currently turned on)",
nil, nil,
)
myStromTemperature = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "report_temperatur"),
"The currently measured temperature by the switch. (Might initially be wrong, but will automatically correct itself over the span of a few hours)",
nil, nil,
)
myStromWattPerSec = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "report_watt_per_sec"),
"The average of energy consumed per second from last call this request",
nil, nil,
)
)
func NewExporter() *Exporter {
return &Exporter{ return &Exporter{
// myStromSwitchIp: myStromSwitchIp, myStromSwitchIp: switchIP,
} }
} }
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { // Scrape --
ch <- up func (e *Exporter) Scrape() (prometheus.Gatherer, error) {
ch <- myStromPower reg := prometheus.NewRegistry()
ch <- myStromRelay
ch <- myStromTemperature // --
ch <- myStromWattPerSec bodyInfo, err := e.fetchData("/api/v1/info")
if err != nil {
}
info := switchInfo{}
err = json.Unmarshal(bodyInfo, &info)
if err != nil {
// fmt.Println(err)
// ch <- prometheus.MustNewConstMetric(
// up, prometheus.GaugeValue, 0,
// )
return reg, fmt.Errorf("unable to decode switchReport: %v", err.Error())
}
log.Debugf("info: %#v", info)
e.switchType = info.SwType
if err := registerInfoMetrics(reg, info, e.myStromSwitchIp); err != nil {
return nil, fmt.Errorf("failed to register metrics : %v", err.Error())
}
// --
bodyData, err := e.fetchData("/report")
if err != nil {
}
report := switchReport{}
err = json.Unmarshal(bodyData, &report)
if err != nil {
// fmt.Println(err)
// ch <- prometheus.MustNewConstMetric(
// up, prometheus.GaugeValue, 0,
// )
return reg, fmt.Errorf("unable to decode switchReport: %v", err.Error())
}
log.Debugf("report: %#v", report)
if err := registerMetrics(reg, report, e.myStromSwitchIp, e.switchType); err != nil {
return nil, fmt.Errorf("failed to register metrics : %v", err.Error())
}
return reg, nil
} }
func (e *Exporter) Collect(ch chan<- prometheus.Metric) { // fetchData -- get the data from the switch under the given path
ch <- prometheus.MustNewConstMetric( func (e *Exporter) fetchData(urlpath string) ([]byte, error) {
up, prometheus.GaugeValue, 1, url := "http://" + e.myStromSwitchIp + urlpath
)
e.FetchSwitchMetrics(e.myStromSwitchIp, ch)
}
func (e *Exporter) FetchSwitchMetrics(switchIP string, ch chan<- prometheus.Metric) {
url := "http://" + switchIP + "/report"
switchClient := http.Client{ switchClient := http.Client{
Timeout: time.Second * 5, // 3 second timeout, might need to be increased Timeout: reqTimeout,
Transport: &http.Transport{
DisableCompression: true,
},
} }
req, err := http.NewRequest(http.MethodGet, url, nil) req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { if err != nil {
ch <- prometheus.MustNewConstMetric( // ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0, // up, prometheus.GaugeValue, 0,
) // )
} }
req.Header.Set("User-Agent", "myStrom-exporter") req.Header.Set("User-Agent", "myStrom-exporter")
res, getErr := switchClient.Do(req) res, getErr := switchClient.Do(req)
if getErr != nil { if getErr != nil {
ch <- prometheus.MustNewConstMetric( // ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0, // up, prometheus.GaugeValue, 0,
) // )
} }
if res.Body != nil { if res.Body != nil {
defer res.Body.Close() defer res.Body.Close()
} }
body, readErr := ioutil.ReadAll(res.Body) body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil { if readErr != nil {
ch <- prometheus.MustNewConstMetric( // ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0, // up, prometheus.GaugeValue, 0,
) // )
return []byte{}, fmt.Errorf("unable to read body: %v", readErr.Error())
} }
report := switchReport{} return body, nil
err = json.Unmarshal(body, &report) }
if err != nil {
fmt.Println(err) // registerMetrics --
ch <- prometheus.MustNewConstMetric( func registerMetrics(reg prometheus.Registerer, data switchReport, target string, st float64) error {
up, prometheus.GaugeValue, 0,
) // --
return collectorRelay := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "relay",
Help: "The current state of the relay (wether or not the relay is currently turned on)",
},
[]string{"target"})
if err := reg.Register(collectorRelay); err != nil {
return fmt.Errorf("failed to register metric %v: %v", "relay", err.Error())
} }
ch <- prometheus.MustNewConstMetric( if data.Relay {
myStromPower, prometheus.GaugeValue, report.Power, collectorRelay.WithLabelValues(target).Set(1)
)
if report.Relay {
ch <- prometheus.MustNewConstMetric(
myStromRelay, prometheus.GaugeValue, 1,
)
} else { } else {
ch <- prometheus.MustNewConstMetric( collectorRelay.WithLabelValues(target).Set(0)
myStromRelay, prometheus.GaugeValue, 0,
)
} }
ch <- prometheus.MustNewConstMetric( if st != 114 {
myStromWattPerSec, prometheus.GaugeValue, report.WattPerSec, // --
) collectorPower := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "power",
Help: "The current power consumed by devices attached to the switch",
},
[]string{"target"})
ch <- prometheus.MustNewConstMetric( if err := reg.Register(collectorPower); err != nil {
myStromTemperature, prometheus.GaugeValue, report.Temperature, return fmt.Errorf("failed to register metric %v: %v", "power", err.Error())
) }
collectorPower.WithLabelValues(target).Set(data.Power)
// --
collectorTemperature := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "temperature",
Help: "The currently measured temperature by the switch. (Might initially be wrong, but will automatically correct itself over the span of a few hours)",
},
[]string{"target"})
if err := reg.Register(collectorTemperature); err != nil {
return fmt.Errorf("failed to register metric %v: %v", "temperature", err.Error())
}
collectorTemperature.WithLabelValues(target).Set(data.Temperature)
}
return nil
} }
func (e *Exporter) Scrape(targetAddress string) (prometheus.Gatherer, error) { // registerMetrics --
reg := prometheus.NewRegistry() func registerInfoMetrics(reg prometheus.Registerer, data switchInfo, target string) error {
return reg, nil // --
collectorInfo := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "info",
Help: "",
},
[]string{"target", "version", "mac", "type", "ssid"})
if err := reg.Register(collectorInfo); err != nil {
return fmt.Errorf("failed to register metric %v: %v", "info", err.Error())
}
collectorInfo.WithLabelValues(target, data.Version, data.Mac, fmt.Sprintf("%v", data.SwType), data.SSID).Set(1)
return nil
} }