put the scraping in own package
This commit is contained in:
parent
b0dd938b5d
commit
e2639a5638
4 changed files with 247 additions and 162 deletions
2
Makefile
2
Makefile
|
@ -22,6 +22,8 @@ linux: ## builds the linux version of the exporter
|
|||
GOOS=linux GOARCH=amd64 go build $(GOFLAGS) -ldflags '$(LDFLAGS)'
|
||||
mac: ## builds the macos version of the exporter
|
||||
GOOS=darwin GOARCH=amd64 go build $(GOFLAGS) -ldflags '$(LDFLAGS)'
|
||||
mac-arm: ## builds the macos (m1) version of the exporter
|
||||
GOOS=darwin GOARCH=arm64 go build $(GOFLAGS) -ldflags '$(LDFLAGS)'
|
||||
arm64:
|
||||
GOOS=linux GOARCH=arm64 go build $(GOFLAGS) -ldflags '$(LDFLAGS)'
|
||||
arm:
|
||||
|
|
27
README.md
27
README.md
|
@ -28,9 +28,31 @@ $ ./mystrom-exporter --help
|
|||
```
|
||||
| Flag | Description | Default |
|
||||
| ---- | ----------- | ------- |
|
||||
| switch.ip-address | IP address of the switch you try to monitor | `` |
|
||||
| web.listen-address | Address to listen on | `:9452` |
|
||||
| web.metrics-path | Path under which to expose metrics | `/metrics` |
|
||||
| web.metrics-path | Path under which to expose exporters own metrics | `/metrics` |
|
||||
| web.device-path | Path under which the metrics of the devices are fetched | `/device` |
|
||||
|
||||
## Prometheus configuration
|
||||
A enhancement has been made to have only one exporter which can scrape multiple devices. This is configured in
|
||||
Prometheus as follows assuming we have 4 mystrom devices and the exporter is running locally on the smae machine as
|
||||
the Prometheus.
|
||||
```yaml
|
||||
- job_name: mystrom
|
||||
scrape_interval: 30s
|
||||
metrics_path: /device
|
||||
honor_labels: true
|
||||
static_configs:
|
||||
- targets:
|
||||
- '192.168.105.11'
|
||||
- '192.168.105.12'
|
||||
- '192.168.105.13'
|
||||
- '192.168.105.14'
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- target_label: __address__
|
||||
replacement: 127.0.0.1:9452
|
||||
```
|
||||
|
||||
## Supported architectures
|
||||
Using the make file, you can easily build for the following architectures, those can also be considered the tested ones:
|
||||
|
@ -40,6 +62,7 @@ Using the make file, you can easily build for the following architectures, those
|
|||
| Linux | arm64 |
|
||||
| Linux | arm |
|
||||
| Mac | amd64 |
|
||||
| Mac | arm64 |
|
||||
|
||||
Since go is cross compatible with windows, and mac arm as well, you should be able to build the binary for those as well, but they aren't tested.
|
||||
The docker image is only built & tested for amd64.
|
||||
|
|
217
main.go
217
main.go
|
@ -1,18 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/common/log"
|
||||
|
||||
"mystrom-exporter/pkg/mystrom"
|
||||
"mystrom-exporter/pkg/version"
|
||||
)
|
||||
|
||||
|
@ -29,151 +29,22 @@ var (
|
|||
listenAddress = flag.String("web.listen-address", ":9452",
|
||||
"Address to listen on")
|
||||
metricsPath = flag.String("web.metrics-path", "/metrics",
|
||||
"Path under which to expose metrics")
|
||||
switchIP = flag.String("switch.ip-address", "",
|
||||
"IP address of the switch you try to monitor")
|
||||
"Path under which to expose exporters own metrics")
|
||||
devicePath = flag.String("web.device-path", "/device",
|
||||
"Path under which the metrics of the devices are fetched")
|
||||
showVersion = flag.Bool("version", false,
|
||||
"Show version information.")
|
||||
|
||||
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,
|
||||
var (
|
||||
mystromDurationCounterVec *prometheus.CounterVec
|
||||
mystromRequestsCounterVec *prometheus.CounterVec
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
type Exporter struct {
|
||||
myStromSwitchIp string
|
||||
}
|
||||
|
||||
func NewExporter(myStromSwitchIp string) *Exporter {
|
||||
return &Exporter{
|
||||
myStromSwitchIp: myStromSwitchIp,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- up
|
||||
ch <- myStromPower
|
||||
ch <- myStromRelay
|
||||
ch <- myStromTemperature
|
||||
ch <- myStromWattPerSec
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
report, err := FetchReport(switchIP)
|
||||
|
||||
if err != nil {
|
||||
log.Infof("Error occured, while fetching metrics: %s", err)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
up, prometheus.GaugeValue, 0,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromPower, prometheus.GaugeValue, report.Power,
|
||||
)
|
||||
|
||||
if report.Relay {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromRelay, prometheus.GaugeValue, 1,
|
||||
)
|
||||
} else {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromRelay, prometheus.GaugeValue, 0,
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromWattPerSec, prometheus.GaugeValue, report.WattPerSec,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromTemperature, prometheus.GaugeValue, report.Temperature,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func FetchReport(switchIP string) (*switchReport, error) {
|
||||
log.Infof("Trying to connect to switch at: %s", switchIP)
|
||||
url := "http://" + switchIP + "/report"
|
||||
|
||||
switchClient := http.Client{
|
||||
Timeout: time.Second * 5, // 3 second timeout, might need to be increased
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "myStrom-exporter")
|
||||
|
||||
res, getErr := switchClient.Do(req)
|
||||
if getErr != nil {
|
||||
log.Infof("Error while trying to connect to switch: %s", getErr)
|
||||
return nil, getErr
|
||||
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Infof("Error while reading body: %s", readErr)
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
report := switchReport{}
|
||||
err = json.Unmarshal(body, &report)
|
||||
if err != nil {
|
||||
log.Infof("Error while unmarshaling report: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &report, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Show version information
|
||||
// -- show version information
|
||||
if *showVersion {
|
||||
v, err := version.Print("mystrom_exporter")
|
||||
if err != nil {
|
||||
|
@ -184,13 +55,18 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *switchIP == "" {
|
||||
flag.Usage()
|
||||
fmt.Println("\nNo switch.ip-address provided")
|
||||
os.Exit(1)
|
||||
}
|
||||
// -- create a new registry for the exporter telemetry
|
||||
telemetryRegistry := prometheus.NewRegistry()
|
||||
telemetryRegistry.MustRegister(prometheus.NewGoCollector())
|
||||
telemetryRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
|
||||
|
||||
// make the build information is available through a metric
|
||||
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)
|
||||
|
||||
// -- make the build information is available through a metric
|
||||
buildInfo := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "scripts",
|
||||
|
@ -200,12 +76,19 @@ func main() {
|
|||
[]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 := NewExporter(*switchIP)
|
||||
prometheus.MustRegister(exporter, buildInfo)
|
||||
exporter := mystrom.NewExporter()
|
||||
// prometheus.MustRegister(exporter)
|
||||
|
||||
http.Handle(*metricsPath, promhttp.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
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("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`<html>
|
||||
<head><title>myStrom switch report Exporter</title></head>
|
||||
<body>
|
||||
|
@ -214,12 +97,36 @@ func main() {
|
|||
</body>
|
||||
</html>`))
|
||||
})
|
||||
|
||||
_, err := FetchReport(*switchIP)
|
||||
if err != nil {
|
||||
log.Fatalf("Switch at address %s couldn't be reached. Ensure it is reachable before starting the exporter", *switchIP)
|
||||
}
|
||||
|
||||
log.Infoln("Listening on address " + *listenAddress)
|
||||
log.Fatal(http.ListenAndServe(*listenAddress, nil))
|
||||
log.Fatal(http.ListenAndServe(*listenAddress, router))
|
||||
}
|
||||
|
||||
func scrapeHandler(e *mystrom.Exporter, w http.ResponseWriter, r *http.Request) {
|
||||
target := r.URL.Query().Get("target")
|
||||
if target == "" {
|
||||
http.Error(w, "'target' parameter must be specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("got scrape request for target '%v'", target)
|
||||
|
||||
start := time.Now()
|
||||
gatherer, err := e.Scrape(target)
|
||||
duration := time.Since(start).Seconds()
|
||||
if err != nil {
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "unable to connect with target") {
|
||||
} else if strings.Contains(fmt.Sprintf("%v", err), "i/o timeout") {
|
||||
}
|
||||
http.Error(
|
||||
w,
|
||||
fmt.Sprintf("failed to scrape target '%v': %v", target, err),
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
mystromDurationCounterVec.WithLabelValues(target).Add(duration)
|
||||
|
||||
promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}).ServeHTTP(w, r)
|
||||
|
||||
}
|
||||
|
|
153
pkg/mystrom/mystrom.go
Normal file
153
pkg/mystrom/mystrom.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
package mystrom
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const namespace = "mystrom"
|
||||
|
||||
type switchReport struct {
|
||||
Power float64 `json:"power"`
|
||||
WattPerSec float64 `json:"Ws"`
|
||||
Relay bool `json:relay`
|
||||
Temperature float64 `json:"temperature`
|
||||
}
|
||||
|
||||
type Exporter struct {
|
||||
myStromSwitchIp string
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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 {
|
||||
return &Exporter{
|
||||
// myStromSwitchIp: myStromSwitchIp,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- up
|
||||
ch <- myStromPower
|
||||
ch <- myStromRelay
|
||||
ch <- myStromTemperature
|
||||
ch <- myStromWattPerSec
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
switchClient := http.Client{
|
||||
Timeout: time.Second * 5, // 3 second timeout, might need to be increased
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
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,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
up, prometheus.GaugeValue, 0,
|
||||
)
|
||||
}
|
||||
|
||||
report := switchReport{}
|
||||
err = json.Unmarshal(body, &report)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
up, prometheus.GaugeValue, 0,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromPower, prometheus.GaugeValue, report.Power,
|
||||
)
|
||||
|
||||
if report.Relay {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromRelay, prometheus.GaugeValue, 1,
|
||||
)
|
||||
} else {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromRelay, prometheus.GaugeValue, 0,
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromWattPerSec, prometheus.GaugeValue, report.WattPerSec,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
myStromTemperature, prometheus.GaugeValue, report.Temperature,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (e *Exporter) Scrape(targetAddress string) (prometheus.Gatherer, error) {
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
return reg, nil
|
||||
}
|
Loading…
Reference in a new issue