853 lines
22 KiB
Go
853 lines
22 KiB
Go
|
package main
|
||
|
|
||
|
// Copyright 2016 Nils Decker
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"regexp"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/namsral/flag"
|
||
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||
|
|
||
|
lua "github.com/sberk42/fritzbox_exporter/fritzbox_lua"
|
||
|
upnp "github.com/sberk42/fritzbox_exporter/fritzbox_upnp"
|
||
|
)
|
||
|
|
||
|
const serviceLoadRetryTime = 1 * time.Minute
|
||
|
|
||
|
// minimum TTL for cached results in seconds
|
||
|
const minCacheTTL = 30
|
||
|
|
||
|
var (
|
||
|
flagTest = flag.Bool("test", false, "print all available metrics to stdout")
|
||
|
flagLuaTest = flag.Bool("testLua", false, "read luaTest.json file make all contained calls and dump results")
|
||
|
flagCollect = flag.Bool("collect", false, "print configured metrics to stdout and exit")
|
||
|
flagJSONOut = flag.String("json-out", "", "store metrics also to JSON file when running test")
|
||
|
|
||
|
flagAddr = flag.String("listen-address", "127.0.0.1:9042", "The address to listen on for HTTP requests.")
|
||
|
flagMetricsFile = flag.String("metrics-file", "metrics.json", "The JSON file with the metric definitions.")
|
||
|
flagDisableLua = flag.Bool("nolua", false, "disable collecting lua metrics")
|
||
|
flagLuaMetricsFile = flag.String("lua-metrics-file", "metrics-lua.json", "The JSON file with the lua metric definitions.")
|
||
|
|
||
|
flagGatewayURL = flag.String("gateway-url", "http://fritz.box:49000", "The URL of the FRITZ!Box")
|
||
|
flagGatewayLuaURL = flag.String("gateway-luaurl", "http://fritz.box", "The URL of the FRITZ!Box UI")
|
||
|
flagUsername = flag.String("username", "", "The user for the FRITZ!Box UPnP service")
|
||
|
flagPassword = flag.String("password", "", "The password for the FRITZ!Box UPnP service")
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
collectErrors = prometheus.NewCounter(prometheus.CounterOpts{
|
||
|
Name: "fritzbox_exporter_collectErrors",
|
||
|
Help: "Number of collection errors.",
|
||
|
})
|
||
|
)
|
||
|
var (
|
||
|
luaCollectErrors = prometheus.NewCounter(prometheus.CounterOpts{
|
||
|
Name: "fritzbox_exporter_luaCollectErrors",
|
||
|
Help: "Number of lua collection errors.",
|
||
|
})
|
||
|
)
|
||
|
var collectLuaResultsCached = prometheus.NewCounter(prometheus.CounterOpts{
|
||
|
Name: "fritzbox_exporter_results_cached",
|
||
|
Help: "Number of results taken from cache.",
|
||
|
ConstLabels: prometheus.Labels{"Cache": "LUA"},
|
||
|
})
|
||
|
var collectUpnpResultsCached = prometheus.NewCounter(prometheus.CounterOpts{
|
||
|
Name: "fritzbox_exporter_results_cached",
|
||
|
Help: "Number of results taken from cache.",
|
||
|
ConstLabels: prometheus.Labels{"Cache": "UPNP"},
|
||
|
})
|
||
|
var collectLuaResultsLoaded = prometheus.NewCounter(prometheus.CounterOpts{
|
||
|
Name: "fritzbox_exporter_results_loaded",
|
||
|
Help: "Number of results loaded from fritzbox.",
|
||
|
ConstLabels: prometheus.Labels{"Cache": "LUA"},
|
||
|
})
|
||
|
var collectUpnpResultsLoaded = prometheus.NewCounter(prometheus.CounterOpts{
|
||
|
Name: "fritzbox_exporter_results_loaded",
|
||
|
Help: "Number of results loaded from fritzbox.",
|
||
|
ConstLabels: prometheus.Labels{"Cache": "UPNP"},
|
||
|
})
|
||
|
|
||
|
// JSONPromDesc metric description loaded from JSON
|
||
|
type JSONPromDesc struct {
|
||
|
FqName string `json:"fqName"`
|
||
|
Help string `json:"help"`
|
||
|
VarLabels []string `json:"varLabels"`
|
||
|
FixedLabels map[string]string `json:"fixedLabels"`
|
||
|
fixedLabelValues string // neeeded to create uniq lookup key when reporting
|
||
|
}
|
||
|
|
||
|
// ActionArg argument for upnp action
|
||
|
type ActionArg struct {
|
||
|
Name string `json:"Name"`
|
||
|
IsIndex bool `json:"IsIndex"`
|
||
|
ProviderAction string `json:"ProviderAction"`
|
||
|
Value string `json:"Value"`
|
||
|
}
|
||
|
|
||
|
// Metric upnp metric
|
||
|
type Metric struct {
|
||
|
// initialized loading JSON
|
||
|
Service string `json:"service"`
|
||
|
Action string `json:"action"`
|
||
|
ActionArgument *ActionArg `json:"actionArgument"`
|
||
|
Result string `json:"result"`
|
||
|
OkValue string `json:"okValue"`
|
||
|
PromDesc JSONPromDesc `json:"promDesc"`
|
||
|
PromType string `json:"promType"`
|
||
|
CacheEntryTTL int64 `json:"cacheEntryTTL"`
|
||
|
|
||
|
// initialized at startup
|
||
|
Desc *prometheus.Desc
|
||
|
MetricType prometheus.ValueType
|
||
|
}
|
||
|
|
||
|
// LuaTest JSON struct for API tests
|
||
|
type LuaTest struct {
|
||
|
Path string `json:"path"`
|
||
|
Params string `json:"params"`
|
||
|
}
|
||
|
|
||
|
// LuaLabelRename struct
|
||
|
type LuaLabelRename struct {
|
||
|
MatchRegex string `json:"matchRegex"`
|
||
|
RenameLabel string `json:"renameLabel"`
|
||
|
}
|
||
|
|
||
|
// LuaMetric struct
|
||
|
type LuaMetric struct {
|
||
|
// initialized loading JSON
|
||
|
Path string `json:"path"`
|
||
|
Params string `json:"params"`
|
||
|
ResultPath string `json:"resultPath"`
|
||
|
ResultKey string `json:"resultKey"`
|
||
|
OkValue string `json:"okValue"`
|
||
|
PromDesc JSONPromDesc `json:"promDesc"`
|
||
|
PromType string `json:"promType"`
|
||
|
CacheEntryTTL int64 `json:"cacheEntryTTL"`
|
||
|
|
||
|
// initialized at startup
|
||
|
Desc *prometheus.Desc
|
||
|
MetricType prometheus.ValueType
|
||
|
LuaPage lua.LuaPage
|
||
|
LuaMetricDef lua.LuaMetricValueDefinition
|
||
|
}
|
||
|
|
||
|
// LuaMetricsFile json struct
|
||
|
type LuaMetricsFile struct {
|
||
|
LabelRenames []LuaLabelRename `json:"labelRenames"`
|
||
|
Metrics []*LuaMetric `json:"metrics"`
|
||
|
}
|
||
|
|
||
|
type upnpCacheEntry struct {
|
||
|
Timestamp int64
|
||
|
Result *upnp.Result
|
||
|
}
|
||
|
|
||
|
type luaCacheEntry struct {
|
||
|
Timestamp int64
|
||
|
Result *map[string]interface{}
|
||
|
}
|
||
|
|
||
|
var metrics []*Metric
|
||
|
var luaMetrics []*LuaMetric
|
||
|
var upnpCache map[string]*upnpCacheEntry
|
||
|
var luaCache map[string]*luaCacheEntry
|
||
|
|
||
|
// FritzboxCollector main struct
|
||
|
type FritzboxCollector struct {
|
||
|
URL string
|
||
|
Gateway string
|
||
|
Username string
|
||
|
Password string
|
||
|
|
||
|
// support for lua collector
|
||
|
LuaSession *lua.LuaSession
|
||
|
LabelRenames *[]lua.LabelRename
|
||
|
|
||
|
sync.Mutex // protects Root
|
||
|
Root *upnp.Root
|
||
|
}
|
||
|
|
||
|
// simple ResponseWriter to collect output
|
||
|
type testResponseWriter struct {
|
||
|
header http.Header
|
||
|
statusCode int
|
||
|
body bytes.Buffer
|
||
|
}
|
||
|
|
||
|
func (w *testResponseWriter) Header() http.Header {
|
||
|
return w.header
|
||
|
}
|
||
|
|
||
|
func (w *testResponseWriter) Write(b []byte) (int, error) {
|
||
|
return w.body.Write(b)
|
||
|
}
|
||
|
|
||
|
func (w *testResponseWriter) WriteHeader(statusCode int) {
|
||
|
w.statusCode = statusCode
|
||
|
}
|
||
|
|
||
|
func (w *testResponseWriter) String() string {
|
||
|
return w.body.String()
|
||
|
}
|
||
|
|
||
|
// LoadServices tries to load the service information. Retries until success.
|
||
|
func (fc *FritzboxCollector) LoadServices() {
|
||
|
for {
|
||
|
root, err := upnp.LoadServices(fc.URL, fc.Username, fc.Password)
|
||
|
if err != nil {
|
||
|
fmt.Printf("cannot load services: %s\n", err)
|
||
|
|
||
|
time.Sleep(serviceLoadRetryTime)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
fmt.Printf("services loaded\n")
|
||
|
|
||
|
fc.Lock()
|
||
|
fc.Root = root
|
||
|
fc.Unlock()
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Describe describe metric
|
||
|
func (fc *FritzboxCollector) Describe(ch chan<- *prometheus.Desc) {
|
||
|
for _, m := range metrics {
|
||
|
ch <- m.Desc
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (fc *FritzboxCollector) reportMetric(ch chan<- prometheus.Metric, m *Metric, result upnp.Result, dupCache map[string]bool) {
|
||
|
|
||
|
val, ok := result[m.Result]
|
||
|
if !ok {
|
||
|
fmt.Printf("%s.%s has no result %s", m.Service, m.Action, m.Result)
|
||
|
collectErrors.Inc()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var floatval float64
|
||
|
switch tval := val.(type) {
|
||
|
case uint64:
|
||
|
floatval = float64(tval)
|
||
|
case bool:
|
||
|
if tval {
|
||
|
floatval = 1
|
||
|
} else {
|
||
|
floatval = 0
|
||
|
}
|
||
|
case string:
|
||
|
if tval == m.OkValue {
|
||
|
floatval = 1
|
||
|
} else {
|
||
|
floatval = 0
|
||
|
}
|
||
|
default:
|
||
|
fmt.Println("unknown type", val)
|
||
|
collectErrors.Inc()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
labels := make([]string, len(m.PromDesc.VarLabels))
|
||
|
for i, l := range m.PromDesc.VarLabels {
|
||
|
if l == "gateway" {
|
||
|
labels[i] = fc.Gateway
|
||
|
} else {
|
||
|
lval, ok := result[l]
|
||
|
if !ok {
|
||
|
fmt.Printf("%s.%s has no resul for label %s", m.Service, m.Action, l)
|
||
|
lval = ""
|
||
|
}
|
||
|
|
||
|
// convert hostname and MAC tolower to avoid problems with labels
|
||
|
if l == "HostName" || l == "MACAddress" {
|
||
|
labels[i] = strings.ToLower(fmt.Sprintf("%v", lval))
|
||
|
} else {
|
||
|
labels[i] = fmt.Sprintf("%v", lval)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check for duplicate labels to prevent collection failure
|
||
|
key := m.PromDesc.FqName + ":" + m.PromDesc.fixedLabelValues + strings.Join(labels, ",")
|
||
|
if dupCache[key] {
|
||
|
fmt.Printf("%s.%s reported before as: %s\n", m.Service, m.Action, key)
|
||
|
collectErrors.Inc()
|
||
|
return
|
||
|
}
|
||
|
dupCache[key] = true
|
||
|
|
||
|
metric, err := prometheus.NewConstMetric(m.Desc, m.MetricType, floatval, labels...)
|
||
|
if err != nil {
|
||
|
fmt.Printf("Error creating metric %s.%s: %s", m.Service, m.Action, err.Error())
|
||
|
} else {
|
||
|
ch <- metric
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (fc *FritzboxCollector) getActionResult(metric *Metric, actionName string, actionArg *upnp.ActionArgument) (upnp.Result, error) {
|
||
|
|
||
|
key := metric.Service + "|" + actionName
|
||
|
|
||
|
// for calls with argument also add arguement name and value to key
|
||
|
if actionArg != nil {
|
||
|
key += "|" + actionArg.Name + "|" + fmt.Sprintf("%v", actionArg.Value)
|
||
|
}
|
||
|
|
||
|
now := time.Now().Unix()
|
||
|
|
||
|
cacheEntry := upnpCache[key]
|
||
|
if cacheEntry == nil {
|
||
|
cacheEntry = &upnpCacheEntry{}
|
||
|
upnpCache[key] = cacheEntry
|
||
|
} else if now-cacheEntry.Timestamp > metric.CacheEntryTTL {
|
||
|
cacheEntry.Result = nil
|
||
|
}
|
||
|
|
||
|
if cacheEntry.Result == nil {
|
||
|
service, ok := fc.Root.Services[metric.Service]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("service %s not found", metric.Service)
|
||
|
}
|
||
|
|
||
|
action, ok := service.Actions[actionName]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("action %s not found in service %s", actionName, metric.Service)
|
||
|
}
|
||
|
|
||
|
data, err := action.Call(actionArg)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
cacheEntry.Timestamp = now
|
||
|
cacheEntry.Result = &data
|
||
|
collectUpnpResultsCached.Inc()
|
||
|
} else {
|
||
|
collectUpnpResultsLoaded.Inc()
|
||
|
}
|
||
|
|
||
|
return *cacheEntry.Result, nil
|
||
|
}
|
||
|
|
||
|
// Collect collect upnp metrics
|
||
|
func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
|
||
|
fc.Lock()
|
||
|
root := fc.Root
|
||
|
fc.Unlock()
|
||
|
|
||
|
if root == nil {
|
||
|
// Services not loaded yet
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// create cache for duplicate lookup, to prevent collection errors
|
||
|
var dupCache = make(map[string]bool)
|
||
|
|
||
|
for _, m := range metrics {
|
||
|
var actArg *upnp.ActionArgument
|
||
|
if m.ActionArgument != nil {
|
||
|
aa := m.ActionArgument
|
||
|
var value interface{}
|
||
|
value = aa.Value
|
||
|
|
||
|
if aa.ProviderAction != "" {
|
||
|
provRes, err := fc.getActionResult(m, aa.ProviderAction, nil)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Printf("Error getting provider action %s result for %s.%s: %s\n", aa.ProviderAction, m.Service, m.Action, err.Error())
|
||
|
collectErrors.Inc()
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
var ok bool
|
||
|
value, ok = provRes[aa.Value] // Value contains the result name for provider actions
|
||
|
if !ok {
|
||
|
fmt.Printf("provider action %s for %s.%s has no result", m.Service, m.Action, aa.Value)
|
||
|
collectErrors.Inc()
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if aa.IsIndex {
|
||
|
sval := fmt.Sprintf("%v", value)
|
||
|
count, err := strconv.Atoi(sval)
|
||
|
if err != nil {
|
||
|
fmt.Println(err.Error())
|
||
|
collectErrors.Inc()
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for i := 0; i < count; i++ {
|
||
|
actArg = &upnp.ActionArgument{Name: aa.Name, Value: i}
|
||
|
result, err := fc.getActionResult(m, m.Action, actArg)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err.Error())
|
||
|
collectErrors.Inc()
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
fc.reportMetric(ch, m, result, dupCache)
|
||
|
}
|
||
|
|
||
|
continue
|
||
|
} else {
|
||
|
actArg = &upnp.ActionArgument{Name: aa.Name, Value: value}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
result, err := fc.getActionResult(m, m.Action, actArg)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err.Error())
|
||
|
collectErrors.Inc()
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
fc.reportMetric(ch, m, result, dupCache)
|
||
|
}
|
||
|
|
||
|
// if lua is enabled now also collect metrics
|
||
|
if fc.LuaSession != nil {
|
||
|
fc.collectLua(ch, dupCache)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (fc *FritzboxCollector) collectLua(ch chan<- prometheus.Metric, dupCache map[string]bool) {
|
||
|
// create a map for caching results
|
||
|
now := time.Now().Unix()
|
||
|
|
||
|
for _, lm := range luaMetrics {
|
||
|
key := lm.Path + "_" + lm.Params
|
||
|
|
||
|
cacheEntry := luaCache[key]
|
||
|
if cacheEntry == nil {
|
||
|
cacheEntry = &luaCacheEntry{}
|
||
|
luaCache[key] = cacheEntry
|
||
|
} else if now-cacheEntry.Timestamp > lm.CacheEntryTTL {
|
||
|
cacheEntry.Result = nil
|
||
|
}
|
||
|
|
||
|
if cacheEntry.Result == nil {
|
||
|
pageData, err := fc.LuaSession.LoadData(lm.LuaPage)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Printf("Error loading %s for %s.%s: %s\n", lm.Path, lm.ResultPath, lm.ResultKey, err.Error())
|
||
|
luaCollectErrors.Inc()
|
||
|
fc.LuaSession.SID = "" // clear SID in case of error, so force reauthentication
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
var data map[string]interface{}
|
||
|
data, err = lua.ParseJSON(pageData)
|
||
|
if err != nil {
|
||
|
fmt.Printf("Error parsing JSON from %s for %s.%s: %s\n", lm.Path, lm.ResultPath, lm.ResultKey, err.Error())
|
||
|
luaCollectErrors.Inc()
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
cacheEntry.Result = &data
|
||
|
cacheEntry.Timestamp = now
|
||
|
collectLuaResultsLoaded.Inc()
|
||
|
} else {
|
||
|
collectLuaResultsCached.Inc()
|
||
|
}
|
||
|
|
||
|
metricVals, err := lua.GetMetrics(fc.LabelRenames, *cacheEntry.Result, lm.LuaMetricDef)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Printf("Error getting metric values for %s.%s: %s\n", lm.ResultPath, lm.ResultKey, err.Error())
|
||
|
luaCollectErrors.Inc()
|
||
|
cacheEntry.Result = nil // don't use invalid results for cache
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for _, mv := range metricVals {
|
||
|
fc.reportLuaMetric(ch, lm, mv, dupCache)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (fc *FritzboxCollector) reportLuaMetric(ch chan<- prometheus.Metric, lm *LuaMetric, value lua.LuaMetricValue, dupCache map[string]bool) {
|
||
|
|
||
|
labels := make([]string, len(lm.PromDesc.VarLabels))
|
||
|
for i, l := range lm.PromDesc.VarLabels {
|
||
|
if l == "gateway" {
|
||
|
labels[i] = fc.Gateway
|
||
|
} else {
|
||
|
lval, ok := value.Labels[l]
|
||
|
if !ok {
|
||
|
fmt.Printf("%s.%s from %s?%s has no resul for label %s", lm.ResultPath, lm.ResultKey, lm.Path, lm.Params, l)
|
||
|
lval = ""
|
||
|
}
|
||
|
|
||
|
// convert hostname and MAC tolower to avoid problems with labels
|
||
|
if l == "HostName" || l == "MACAddress" {
|
||
|
labels[i] = strings.ToLower(fmt.Sprintf("%v", lval))
|
||
|
} else {
|
||
|
labels[i] = fmt.Sprintf("%v", lval)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check for duplicate labels to prevent collection failure
|
||
|
key := lm.PromDesc.FqName + ":" + lm.PromDesc.fixedLabelValues + strings.Join(labels, ",")
|
||
|
if dupCache[key] {
|
||
|
fmt.Printf("%s.%s reported before as: %s\n", lm.ResultPath, lm.ResultPath, key)
|
||
|
luaCollectErrors.Inc()
|
||
|
return
|
||
|
}
|
||
|
dupCache[key] = true
|
||
|
|
||
|
metric, err := prometheus.NewConstMetric(lm.Desc, lm.MetricType, value.Value, labels...)
|
||
|
if err != nil {
|
||
|
fmt.Printf("Error creating metric %s.%s: %s", lm.ResultPath, lm.ResultPath, err.Error())
|
||
|
} else {
|
||
|
ch <- metric
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func test() {
|
||
|
root, err := upnp.LoadServices(*flagGatewayURL, *flagUsername, *flagPassword)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
var newEntry bool = false
|
||
|
var json bytes.Buffer
|
||
|
json.WriteString("[\n")
|
||
|
|
||
|
serviceKeys := []string{}
|
||
|
for k := range root.Services {
|
||
|
serviceKeys = append(serviceKeys, k)
|
||
|
}
|
||
|
sort.Strings(serviceKeys)
|
||
|
for _, k := range serviceKeys {
|
||
|
s := root.Services[k]
|
||
|
fmt.Printf("Service: %s (Url: %s)\n", k, s.ControlURL)
|
||
|
|
||
|
actionKeys := []string{}
|
||
|
for l := range s.Actions {
|
||
|
actionKeys = append(actionKeys, l)
|
||
|
}
|
||
|
sort.Strings(actionKeys)
|
||
|
for _, l := range actionKeys {
|
||
|
a := s.Actions[l]
|
||
|
fmt.Printf(" %s - arguments: variable [direction] (soap name, soap type)\n", a.Name)
|
||
|
for _, arg := range a.Arguments {
|
||
|
sv := arg.StateVariable
|
||
|
fmt.Printf(" %s [%s] (%s, %s)\n", arg.RelatedStateVariable, arg.Direction, arg.Name, sv.DataType)
|
||
|
}
|
||
|
|
||
|
if !a.IsGetOnly() {
|
||
|
fmt.Printf(" %s - not calling, since arguments required or no output\n", a.Name)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// only create JSON for Get
|
||
|
// TODO also create JSON templates for input actionParams
|
||
|
for _, arg := range a.Arguments {
|
||
|
// create new json entry
|
||
|
if newEntry {
|
||
|
json.WriteString(",\n")
|
||
|
} else {
|
||
|
newEntry = true
|
||
|
}
|
||
|
|
||
|
json.WriteString("\t{\n\t\t\"service\": \"")
|
||
|
json.WriteString(k)
|
||
|
json.WriteString("\",\n\t\t\"action\": \"")
|
||
|
json.WriteString(a.Name)
|
||
|
json.WriteString("\",\n\t\t\"result\": \"")
|
||
|
json.WriteString(arg.RelatedStateVariable)
|
||
|
json.WriteString("\"\n\t}")
|
||
|
}
|
||
|
|
||
|
fmt.Printf(" %s - calling - results: variable: value\n", a.Name)
|
||
|
res, err := a.Call(nil)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Printf(" FAILED:%s\n", err.Error())
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for _, arg := range a.Arguments {
|
||
|
fmt.Printf(" %s: %v\n", arg.RelatedStateVariable, res[arg.StateVariable.Name])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
json.WriteString("\n]")
|
||
|
|
||
|
if *flagJSONOut != "" {
|
||
|
err := ioutil.WriteFile(*flagJSONOut, json.Bytes(), 0644)
|
||
|
if err != nil {
|
||
|
fmt.Printf("Failed writing JSON file '%s': %s\n", *flagJSONOut, err.Error())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func testLua() {
|
||
|
|
||
|
jsonData, err := ioutil.ReadFile("luaTest.json")
|
||
|
if err != nil {
|
||
|
fmt.Println("error reading luaTest.json:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var luaTests []LuaTest
|
||
|
err = json.Unmarshal(jsonData, &luaTests)
|
||
|
if err != nil {
|
||
|
fmt.Println("error parsing luaTest JSON:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// create session struct and init params
|
||
|
luaSession := lua.LuaSession{BaseURL: *flagGatewayLuaURL, Username: *flagUsername, Password: *flagPassword}
|
||
|
|
||
|
for _, test := range luaTests {
|
||
|
fmt.Printf("TESTING: %s (%s)\n", test.Path, test.Params)
|
||
|
|
||
|
page := lua.LuaPage{Path: test.Path, Params: test.Params}
|
||
|
pageData, err := luaSession.LoadData(page)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err.Error())
|
||
|
} else {
|
||
|
fmt.Println(string(pageData))
|
||
|
}
|
||
|
|
||
|
fmt.Println()
|
||
|
fmt.Println()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getValueType(vt string) prometheus.ValueType {
|
||
|
switch vt {
|
||
|
case "CounterValue":
|
||
|
return prometheus.CounterValue
|
||
|
case "GaugeValue":
|
||
|
return prometheus.GaugeValue
|
||
|
case "UntypedValue":
|
||
|
return prometheus.UntypedValue
|
||
|
}
|
||
|
|
||
|
return prometheus.UntypedValue
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
flag.Parse()
|
||
|
|
||
|
u, err := url.Parse(*flagGatewayURL)
|
||
|
if err != nil {
|
||
|
fmt.Println("invalid URL:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if *flagTest {
|
||
|
test()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if *flagLuaTest {
|
||
|
testLua()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// read metrics
|
||
|
jsonData, err := ioutil.ReadFile(*flagMetricsFile)
|
||
|
if err != nil {
|
||
|
fmt.Println("error reading metric file:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
err = json.Unmarshal(jsonData, &metrics)
|
||
|
if err != nil {
|
||
|
fmt.Println("error parsing JSON:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// create a map for caching results
|
||
|
upnpCache = make(map[string]*upnpCacheEntry)
|
||
|
|
||
|
var luaSession *lua.LuaSession
|
||
|
var luaLabelRenames *[]lua.LabelRename
|
||
|
if !*flagDisableLua {
|
||
|
jsonData, err := ioutil.ReadFile(*flagLuaMetricsFile)
|
||
|
if err != nil {
|
||
|
fmt.Println("error reading lua metric file:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var lmf *LuaMetricsFile
|
||
|
err = json.Unmarshal(jsonData, &lmf)
|
||
|
if err != nil {
|
||
|
fmt.Println("error parsing lua JSON:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// create a map for caching results
|
||
|
luaCache = make(map[string]*luaCacheEntry)
|
||
|
|
||
|
// init label renames
|
||
|
lblRen := make([]lua.LabelRename, 0)
|
||
|
for _, ren := range lmf.LabelRenames {
|
||
|
regex, err := regexp.Compile(ren.MatchRegex)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println("error compiling lua rename regex:", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
lblRen = append(lblRen, lua.LabelRename{Pattern: *regex, Name: ren.RenameLabel})
|
||
|
}
|
||
|
luaLabelRenames = &lblRen
|
||
|
|
||
|
// init metrics
|
||
|
luaMetrics = lmf.Metrics
|
||
|
for _, lm := range luaMetrics {
|
||
|
pd := &lm.PromDesc
|
||
|
|
||
|
// make labels lower case
|
||
|
labels := make([]string, len(pd.VarLabels))
|
||
|
for i, l := range pd.VarLabels {
|
||
|
labels[i] = strings.ToLower(l)
|
||
|
}
|
||
|
|
||
|
// create fixed labels values
|
||
|
pd.fixedLabelValues = ""
|
||
|
for _, flv := range pd.FixedLabels {
|
||
|
pd.fixedLabelValues += flv + ","
|
||
|
}
|
||
|
|
||
|
lm.Desc = prometheus.NewDesc(pd.FqName, pd.Help, labels, pd.FixedLabels)
|
||
|
lm.MetricType = getValueType(lm.PromType)
|
||
|
|
||
|
lm.LuaPage = lua.LuaPage{
|
||
|
Path: lm.Path,
|
||
|
Params: lm.Params,
|
||
|
}
|
||
|
|
||
|
lm.LuaMetricDef = lua.LuaMetricValueDefinition{
|
||
|
Path: lm.ResultPath,
|
||
|
Key: lm.ResultKey,
|
||
|
OkValue: lm.OkValue,
|
||
|
Labels: pd.VarLabels,
|
||
|
}
|
||
|
|
||
|
// init TTL
|
||
|
if lm.CacheEntryTTL < minCacheTTL {
|
||
|
lm.CacheEntryTTL = minCacheTTL
|
||
|
}
|
||
|
}
|
||
|
|
||
|
luaSession = &lua.LuaSession{
|
||
|
BaseURL: *flagGatewayLuaURL,
|
||
|
Username: *flagUsername,
|
||
|
Password: *flagPassword,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// init metrics
|
||
|
for _, m := range metrics {
|
||
|
pd := &m.PromDesc
|
||
|
|
||
|
// make labels lower case
|
||
|
labels := make([]string, len(pd.VarLabels))
|
||
|
for i, l := range pd.VarLabels {
|
||
|
labels[i] = strings.ToLower(l)
|
||
|
}
|
||
|
|
||
|
// create fixed labels values
|
||
|
pd.fixedLabelValues = ""
|
||
|
for _, flv := range pd.FixedLabels {
|
||
|
pd.fixedLabelValues += flv + ","
|
||
|
}
|
||
|
|
||
|
m.Desc = prometheus.NewDesc(pd.FqName, pd.Help, labels, pd.FixedLabels)
|
||
|
m.MetricType = getValueType(m.PromType)
|
||
|
|
||
|
// init TTL
|
||
|
if m.CacheEntryTTL < minCacheTTL {
|
||
|
m.CacheEntryTTL = minCacheTTL
|
||
|
}
|
||
|
}
|
||
|
|
||
|
collector := &FritzboxCollector{
|
||
|
URL: *flagGatewayURL,
|
||
|
Gateway: u.Hostname(),
|
||
|
Username: *flagUsername,
|
||
|
Password: *flagPassword,
|
||
|
|
||
|
LuaSession: luaSession,
|
||
|
LabelRenames: luaLabelRenames,
|
||
|
}
|
||
|
|
||
|
if *flagCollect {
|
||
|
collector.LoadServices()
|
||
|
|
||
|
prometheus.MustRegister(collector)
|
||
|
prometheus.MustRegister(collectErrors)
|
||
|
if luaSession != nil {
|
||
|
prometheus.MustRegister(luaCollectErrors)
|
||
|
}
|
||
|
|
||
|
fmt.Println("collecting metrics via http")
|
||
|
|
||
|
// simulate HTTP request without starting actual http server
|
||
|
writer := testResponseWriter{header: http.Header{}}
|
||
|
request := http.Request{}
|
||
|
promhttp.Handler().ServeHTTP(&writer, &request)
|
||
|
|
||
|
fmt.Println(writer.String())
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
go collector.LoadServices()
|
||
|
|
||
|
prometheus.MustRegister(collector)
|
||
|
prometheus.MustRegister(collectErrors)
|
||
|
prometheus.MustRegister(collectUpnpResultsCached)
|
||
|
prometheus.MustRegister(collectUpnpResultsLoaded)
|
||
|
|
||
|
if luaSession != nil {
|
||
|
prometheus.MustRegister(luaCollectErrors)
|
||
|
prometheus.MustRegister(collectLuaResultsCached)
|
||
|
prometheus.MustRegister(collectLuaResultsLoaded)
|
||
|
}
|
||
|
|
||
|
http.Handle("/metrics", promhttp.Handler())
|
||
|
fmt.Printf("metrics available at http://%s/metrics\n", *flagAddr)
|
||
|
|
||
|
log.Fatal(http.ListenAndServe(*flagAddr, nil))
|
||
|
}
|