change to multidevice scrape and adding info
This commit is contained in:
parent
f591467354
commit
23b5987364
2 changed files with 199 additions and 130 deletions
84
main.go
84
main.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue