Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static 'site' label to all metrics #28

Merged
merged 2 commits into from
Jun 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ image::examples/grafana.png[Grafana]

=== Docker

. `docker run -d --name fronius-exporter -p "8080:8080" quay.io/ccremer/fronius-exporter`
. `docker run -d --name fronius-exporter -p "8080:8080" quay.io/ccremer/fronius-exporter --help`

=== Helm Chart

Expand All @@ -53,7 +53,7 @@ With https://ccremer.github.io/charts/fronius-exporter[fronius-exporter]

[source,console]
----
fronius-exporter --url http://symo.ip.or.hostname/solar_api/v1/GetPowerFlowRealtimeData.fcgi
fronius-exporter --symo.url http://symo.ip.or.hostname/solar_api/v1/GetPowerFlowRealtimeData.fcgi --symo.site my-site-name
----

Upon each call to `/metrics`, the exporter will do a GET request on the given URL, and translate the JSON response to Prometheus metrics format.
Expand All @@ -71,7 +71,7 @@ All flags are also configurable with Environment variables.
* Replace the `.` delimiter with `__`
* CLI flags take precedence

.Following calls are requivalent
.Following calls are equivalent
----
fronius-exporter --symo.url http://...
SYMO__URL=http://... fronius-exporter
Expand Down
30 changes: 24 additions & 6 deletions cfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cfg
import (
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
Expand Down Expand Up @@ -33,14 +34,15 @@ func setupCliFlags(version string, fs *flag.FlagSet, config *Configuration) {
fmt.Fprintf(os.Stderr, "Usage of %s (%s):\n", os.Args[0], version)
fs.PrintDefaults()
}
fs.String("bind-addr", config.BindAddr, "IP Address to bind to listen for Prometheus scrapes")
fs.String("log.level", config.Log.Level, "Logging level")
fs.BoolP("log.verbose", "v", config.Log.Verbose, "Shortcut for --log.level=debug")
fs.String("bind-addr", config.BindAddr, "IP Address to bind to listen for Prometheus scrapes.")
fs.String("log.level", config.Log.Level, "Logging level.")
fs.BoolP("log.verbose", "v", config.Log.Verbose, "Shortcut for --log.level=debug.")
fs.StringSlice("symo.header", config.Symo.Headers,
"List of \"key: value\" headers to append to the requests going to Fronius Symo")
fs.StringP("symo.url", "u", config.Symo.URL, "Target URL of Fronius Symo device")
"List of \"key: value\" headers to append to the requests going to Fronius Symo. Example: --symo.header \"authorization=Basic <base64>\".")
fs.StringP("symo.url", "u", config.Symo.URL, "Target URL of Fronius Symo device.")
fs.StringP("symo.site", "s", config.Symo.Site, "Site name of the Fronius Symo device, it's added as a static label to the metrics. Defaults to the hostname in --symo.url.")
fs.Int64("symo.timeout", int64(config.Symo.Timeout.Seconds()),
"Timeout in seconds when collecting metrics from Fronius Symo. Should not be larger than the scrape interval")
"Timeout in seconds when collecting metrics from Fronius Symo. Should not be larger than the scrape interval.")

}

Expand All @@ -56,6 +58,8 @@ func postLoadProcess(config *Configuration) {
}
config.Symo.Headers = parsedHeaders

config.Symo.Site = getSiteOrHostName(config.Symo.Site, config.Symo.URL)

level, err := log.ParseLevel(config.Log.Level)
if err != nil {
log.WithError(err).Warn("Could not parse log level, fallback to info level")
Expand Down Expand Up @@ -130,3 +134,17 @@ func ConvertHeaders(headers []string, header *http.Header) {
header.Set(key, value)
}
}

// getSiteOrHostName returns the site name as it was given in the config.
// If that's empty, it returns the hostname or IP address given in the Symo URL.
// If the URL cannot be parsed either, it returns empty string.
func getSiteOrHostName(site, rawURL string) string {
if site != "" {
return site
}
u, err := url.Parse(rawURL)
if err != nil {
return ""
}
return u.Host
}
30 changes: 30 additions & 0 deletions cfg/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,33 @@ func Test_parseHeaderString(t *testing.T) {
})
}
}

func Test_getSiteOrHostName(t *testing.T) {
tests := map[string]struct {
site string
url string
expected string
}{
"GivenEmptySite_ThenReturnHostnameFromURL": {
site: "",
url: "http://hostname.tld:8080/somepath",
expected: "hostname.tld:8080",
},
"GivenConfiguredSite_ThenReturnSameValue": {
site: "my-site",
url: "http://hostname.tld:8080/somepath",
expected: "my-site",
},
"GivenEmptySite_WhenUrlInvalid_ThenReturnEmpty": {
site: "",
url: "hostname.tld",
expected: "",
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
result := getSiteOrHostName(tt.site, tt.url)
assert.Equal(t, tt.expected, result)
})
}
}
1 change: 1 addition & 0 deletions cfg/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type (
// SymoConfig configures the Fronius Symo device
SymoConfig struct {
URL string `koanf:"url"`
Site string `koanf:"site"`
Timeout time.Duration `koanf:"timeout"`
Headers []string `koanf:"header"`
}
Expand Down
76 changes: 46 additions & 30 deletions metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,77 @@ import (
var (
namespace = "fronius"
scrapeDurationGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "scrape_duration_seconds",
Help: "Time it took to scrape the device in seconds",
Namespace: namespace,
Name: "scrape_duration_seconds",
ConstLabels: getConstantLabels(),
Help: "Time it took to scrape the device in seconds",
})
scrapeErrorCount = promauto.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Name: "scrape_error_count",
Help: "Number of scrape errors",
Namespace: namespace,
Name: "scrape_error_count",
ConstLabels: getConstantLabels(),
Help: "Number of scrape errors",
})

inverterPowerGaugeVec = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "inverter_power",
Help: "Power flow of the inverter in Watt",
Namespace: namespace,
Name: "inverter_power",
ConstLabels: getConstantLabels(),
Help: "Power flow of the inverter in Watt",
}, []string{"inverter"})

sitePowerLoadGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_power_load",
Help: "Site power load in Watt",
Namespace: namespace,
Name: "site_power_load",
ConstLabels: getConstantLabels(),
Help: "Site power load in Watt",
})
sitePowerGridGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_power_grid",
Help: "Site power supplied to or provided from the grid in Watt",
Namespace: namespace,
Name: "site_power_grid",
ConstLabels: getConstantLabels(),
Help: "Site power supplied to or provided from the grid in Watt",
})
sitePowerAccuGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_power_accu",
Help: "Site power supplied to or provided from the accumulator(s) in Watt",
Namespace: namespace,
Name: "site_power_accu",
ConstLabels: getConstantLabels(),
Help: "Site power supplied to or provided from the accumulator(s) in Watt",
})
sitePowerPhotovoltaicsGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_power_photovoltaic",
Help: "Site power supplied to or provided from the accumulator(s) in Watt",
Namespace: namespace,
Name: "site_power_photovoltaic",
ConstLabels: getConstantLabels(),
Help: "Site power supplied to or provided from the accumulator(s) in Watt",
})

siteAutonomyRatioGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_autonomy_ratio",
Help: "Relative autonomy ratio of the site",
Namespace: namespace,
Name: "site_autonomy_ratio",
ConstLabels: getConstantLabels(),
Help: "Relative autonomy ratio of the site",
})
siteSelfConsumptionRatioGauge = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_selfconsumption_ratio",
Help: "Relative self consumption ratio of the site",
Namespace: namespace,
Name: "site_selfconsumption_ratio",
ConstLabels: getConstantLabels(),
Help: "Relative self consumption ratio of the site",
})

siteEnergyGaugeVec = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "site_energy_consumption",
Help: "Energy consumption in kWh",
Namespace: namespace,
Name: "site_energy_consumption",
ConstLabels: getConstantLabels(),
Help: "Energy consumption in kWh",
}, []string{"time_frame"})
)

func getConstantLabels() prometheus.Labels {
return map[string]string{
"site": config.Symo.Site,
}
}

func collectMetricsFromTarget(client *fronius.SymoClient) {
start := time.Now()
log.WithFields(log.Fields{
Expand Down