2021-12-23 16:46:18 +01:00
|
|
|
//go:generate stringer -type MystromReqStatus main.go
|
2021-03-18 15:33:22 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2021-11-12 14:23:12 +01:00
|
|
|
"os"
|
2021-12-23 16:36:04 +01:00
|
|
|
"strings"
|
2021-03-18 15:33:22 +01:00
|
|
|
"time"
|
2021-11-12 12:03:52 +01:00
|
|
|
|
2021-12-23 16:54:45 +01:00
|
|
|
"github.com/gorilla/mux"
|
2021-11-12 12:03:52 +01:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2021-12-23 15:25:59 +01:00
|
|
|
"github.com/prometheus/common/log"
|
2021-12-23 15:17:58 +01:00
|
|
|
|
2021-12-23 16:36:04 +01:00
|
|
|
"mystrom-exporter/pkg/mystrom"
|
2021-12-23 15:17:58 +01:00
|
|
|
"mystrom-exporter/pkg/version"
|
2021-03-18 15:33:22 +01:00
|
|
|
)
|
|
|
|
|
2021-12-23 16:46:18 +01:00
|
|
|
// -- MystromRequestStatusType represents the request to MyStrom device status
|
|
|
|
type MystromReqStatus uint32
|
2021-03-18 15:33:22 +01:00
|
|
|
|
2021-12-23 16:46:18 +01:00
|
|
|
const (
|
|
|
|
OK MystromReqStatus = iota
|
|
|
|
ERROR_SOCKET
|
|
|
|
ERROR_TIMEOUT
|
|
|
|
ERROR_PARSING_VALUE
|
|
|
|
)
|
2021-03-18 15:33:22 +01:00
|
|
|
|
2021-12-23 16:49:57 +01:00
|
|
|
const namespace = "mystrom_exporter"
|
|
|
|
|
2021-03-18 15:33:22 +01:00
|
|
|
var (
|
|
|
|
listenAddress = flag.String("web.listen-address", ":9452",
|
|
|
|
"Address to listen on")
|
|
|
|
metricsPath = flag.String("web.metrics-path", "/metrics",
|
2021-12-23 16:36:04 +01:00
|
|
|
"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")
|
2021-12-23 15:17:58 +01:00
|
|
|
showVersion = flag.Bool("version", false,
|
|
|
|
"Show version information.")
|
2021-03-18 15:33:22 +01:00
|
|
|
)
|
2021-12-23 16:36:04 +01:00
|
|
|
var (
|
|
|
|
mystromDurationCounterVec *prometheus.CounterVec
|
|
|
|
mystromRequestsCounterVec *prometheus.CounterVec
|
|
|
|
)
|
2021-12-23 16:48:29 +01:00
|
|
|
var landingPage = []byte(`<html>
|
2022-08-29 22:00:05 +02:00
|
|
|
<head>
|
|
|
|
<title>myStrom switch report Exporter</title>
|
|
|
|
<style>
|
|
|
|
label{
|
|
|
|
display:inline-block;
|
|
|
|
width:75px;
|
|
|
|
}
|
|
|
|
form label {
|
|
|
|
margin: 10px;
|
|
|
|
}
|
|
|
|
form input {
|
|
|
|
margin: 10px;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
2021-12-23 16:48:29 +01:00
|
|
|
<body>
|
|
|
|
<h1>myStrom Exporter</h1>
|
2022-08-29 22:00:05 +02:00
|
|
|
<form action="` + *devicePath + `">
|
|
|
|
<label>Target:</label> <input type="text" name="target" placeholder="X.X.X.X" value="1.2.3.4"><br>
|
|
|
|
<input type="submit" value="Submit">
|
|
|
|
</form>
|
2021-12-23 16:48:29 +01:00
|
|
|
<p><a href='` + *metricsPath + `'>Metrics</a></p>
|
|
|
|
</body>
|
|
|
|
</html>`)
|
2021-03-18 15:33:22 +01:00
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
2021-12-23 16:36:04 +01:00
|
|
|
// -- show version information
|
2021-12-23 15:17:58 +01:00
|
|
|
if *showVersion {
|
|
|
|
v, err := version.Print("mystrom_exporter")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to print version information: %#v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintln(os.Stdout, v)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2021-12-23 16:36:04 +01:00
|
|
|
// -- create a new registry for the exporter telemetry
|
2021-12-23 16:49:57 +01:00
|
|
|
telemetryRegistry := setupMetrics()
|
2021-03-18 15:33:22 +01:00
|
|
|
|
2021-12-23 16:54:45 +01:00
|
|
|
router := mux.NewRouter()
|
2021-12-23 16:36:04 +01:00
|
|
|
router.Handle(*metricsPath, promhttp.HandlerFor(telemetryRegistry, promhttp.HandlerOpts{}))
|
2021-12-23 16:49:57 +01:00
|
|
|
router.HandleFunc(*devicePath, scrapeHandler)
|
2021-12-23 16:36:04 +01:00
|
|
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
2021-12-23 16:48:29 +01:00
|
|
|
w.Write(landingPage)
|
2021-03-18 15:33:22 +01:00
|
|
|
})
|
2021-12-23 16:36:04 +01:00
|
|
|
log.Infoln("Listening on address " + *listenAddress)
|
|
|
|
log.Fatal(http.ListenAndServe(*listenAddress, router))
|
|
|
|
}
|
2021-11-12 12:03:52 +01:00
|
|
|
|
2021-12-23 16:49:57 +01:00
|
|
|
func scrapeHandler(w http.ResponseWriter, r *http.Request) {
|
2021-12-23 16:36:04 +01:00
|
|
|
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)
|
2021-12-23 16:49:57 +01:00
|
|
|
exporter := mystrom.NewExporter(target)
|
2021-12-23 16:36:04 +01:00
|
|
|
|
|
|
|
start := time.Now()
|
2021-12-23 16:49:57 +01:00
|
|
|
gatherer, err := exporter.Scrape()
|
2021-12-23 16:36:04 +01:00
|
|
|
duration := time.Since(start).Seconds()
|
2021-11-12 14:23:12 +01:00
|
|
|
if err != nil {
|
2021-12-23 16:36:04 +01:00
|
|
|
if strings.Contains(fmt.Sprintf("%v", err), "unable to connect with target") {
|
2021-12-23 16:46:18 +01:00
|
|
|
mystromRequestsCounterVec.WithLabelValues(target, ERROR_SOCKET.String()).Inc()
|
2021-12-23 16:36:04 +01:00
|
|
|
} else if strings.Contains(fmt.Sprintf("%v", err), "i/o timeout") {
|
2021-12-23 16:46:18 +01:00
|
|
|
mystromRequestsCounterVec.WithLabelValues(target, ERROR_TIMEOUT.String()).Inc()
|
|
|
|
} else {
|
|
|
|
mystromRequestsCounterVec.WithLabelValues(target, ERROR_PARSING_VALUE.String()).Inc()
|
2021-12-23 16:36:04 +01:00
|
|
|
}
|
|
|
|
http.Error(
|
|
|
|
w,
|
|
|
|
fmt.Sprintf("failed to scrape target '%v': %v", target, err),
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
)
|
|
|
|
log.Error(err)
|
|
|
|
return
|
2021-11-12 14:23:12 +01:00
|
|
|
}
|
2021-12-23 16:36:04 +01:00
|
|
|
mystromDurationCounterVec.WithLabelValues(target).Add(duration)
|
2021-12-23 16:46:18 +01:00
|
|
|
mystromRequestsCounterVec.WithLabelValues(target, OK.String()).Inc()
|
2021-12-23 16:36:04 +01:00
|
|
|
|
|
|
|
promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}).ServeHTTP(w, r)
|
2021-12-23 16:49:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// -- 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)
|
2021-11-12 14:23:12 +01:00
|
|
|
|
2021-12-23 16:49:57 +01:00
|
|
|
return registry
|
2021-03-18 15:33:22 +01:00
|
|
|
}
|