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
)
const namespace = "mystrom_exporter"
var (
listenAddress = flag.String("web.listen-address", ":9452",
"Address to listen on")
@ -65,44 +67,11 @@ func main() {
}
// -- create a new registry for the exporter telemetry
telemetryRegistry := prometheus.NewRegistry()
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)
telemetryRegistry := setupMetrics()
router := http.NewServeMux()
router.Handle(*metricsPath, promhttp.HandlerFor(telemetryRegistry, promhttp.HandlerOpts{}))
router.Handle(*devicePath,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
scrapeHandler(exporter, w, r)
}),
)
router.HandleFunc(*devicePath, scrapeHandler)
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(landingPage)
})
@ -110,7 +79,7 @@ func main() {
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")
if target == "" {
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)
exporter := mystrom.NewExporter(target)
start := time.Now()
gatherer, err := e.Scrape(target)
gatherer, err := exporter.Scrape()
duration := time.Since(start).Seconds()
if err != nil {
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()
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"
"time"
"github.com/prometheus/common/log"
"github.com/prometheus/client_golang/prometheus"
)
const namespace = "mystrom"
// 5 second timeout, might need to be increased
const reqTimeout = time.Second * 5
type switchReport struct {
Power float64 `json:"power"`
WattPerSec float64 `json:"Ws"`
@ -19,135 +24,191 @@ type switchReport struct {
Temperature float64 `json:"temperature"`
}
type Exporter struct {
myStromSwitchIp string
type switchInfo struct {
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 (
up = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "up"),
"Was the last myStrom query successful.",
nil, nil,
)
myStromPower = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "report_power"),
"The current power consumed by devices attached to the switch",
nil, nil,
)
// Exporter --
type Exporter struct {
myStromSwitchIp string
switchType float64
}
myStromRelay = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "report_relay"),
"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 {
// NewExporter --
func NewExporter(switchIP string) *Exporter {
return &Exporter{
// myStromSwitchIp: myStromSwitchIp,
myStromSwitchIp: switchIP,
}
}
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- up
ch <- myStromPower
ch <- myStromRelay
ch <- myStromTemperature
ch <- myStromWattPerSec
// Scrape --
func (e *Exporter) Scrape() (prometheus.Gatherer, error) {
reg := prometheus.NewRegistry()
// --
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) {
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 1,
)
e.FetchSwitchMetrics(e.myStromSwitchIp, ch)
}
func (e *Exporter) FetchSwitchMetrics(switchIP string, ch chan<- prometheus.Metric) {
url := "http://" + switchIP + "/report"
// fetchData -- get the data from the switch under the given path
func (e *Exporter) fetchData(urlpath string) ([]byte, error) {
url := "http://" + e.myStromSwitchIp + urlpath
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)
if err != nil {
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0,
)
// ch <- prometheus.MustNewConstMetric(
// up, prometheus.GaugeValue, 0,
// )
}
req.Header.Set("User-Agent", "myStrom-exporter")
res, getErr := switchClient.Do(req)
if getErr != nil {
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0,
)
// ch <- prometheus.MustNewConstMetric(
// up, prometheus.GaugeValue, 0,
// )
}
if res.Body != nil {
defer res.Body.Close()
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0,
)
// ch <- prometheus.MustNewConstMetric(
// up, prometheus.GaugeValue, 0,
// )
return []byte{}, fmt.Errorf("unable to read body: %v", readErr.Error())
}
report := switchReport{}
err = json.Unmarshal(body, &report)
if err != nil {
fmt.Println(err)
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0,
)
return
return body, nil
}
// registerMetrics --
func registerMetrics(reg prometheus.Registerer, data switchReport, target string, st float64) error {
// --
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(
myStromPower, prometheus.GaugeValue, report.Power,
)
if report.Relay {
ch <- prometheus.MustNewConstMetric(
myStromRelay, prometheus.GaugeValue, 1,
)
if data.Relay {
collectorRelay.WithLabelValues(target).Set(1)
} else {
ch <- prometheus.MustNewConstMetric(
myStromRelay, prometheus.GaugeValue, 0,
)
collectorRelay.WithLabelValues(target).Set(0)
}
ch <- prometheus.MustNewConstMetric(
myStromWattPerSec, prometheus.GaugeValue, report.WattPerSec,
)
if st != 114 {
// --
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(
myStromTemperature, prometheus.GaugeValue, report.Temperature,
)
if err := reg.Register(collectorPower); err != nil {
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) {
reg := prometheus.NewRegistry()
// registerMetrics --
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
}