|
|
|
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 (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
|
|
|
|
upnp "github.com/ndecker/fritzbox_exporter/fritzbox_upnp"
|
|
|
|
)
|
|
|
|
|
|
|
|
const serviceLoadRetryTime = 1 * time.Minute
|
|
|
|
|
|
|
|
var (
|
|
|
|
flag_test = flag.Bool("test", false, "print all available metrics to stdout")
|
|
|
|
flag_addr = flag.String("listen-address", ":9133", "The address to listen on for HTTP requests.")
|
|
|
|
|
|
|
|
flag_gateway_address = flag.String("gateway-address", "fritz.box", "The URL of the upnp service")
|
|
|
|
flag_gateway_port = flag.Int("gateway-port", 49000, "The URL of the upnp service")
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
collect_errors = prometheus.NewCounter(prometheus.CounterOpts{
|
|
|
|
Name: "fritzbox_exporter_collect_errors",
|
|
|
|
Help: "Number of collection errors.",
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
type Metric struct {
|
|
|
|
Service string
|
|
|
|
Action string
|
|
|
|
Result string
|
|
|
|
OkValue string
|
|
|
|
|
|
|
|
Desc *prometheus.Desc
|
|
|
|
MetricType prometheus.ValueType
|
|
|
|
}
|
|
|
|
|
|
|
|
var metrics = []*Metric{
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetTotalPacketsReceived",
|
|
|
|
Result: "TotalPacketsReceived",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_packets_received",
|
|
|
|
"packets received on gateway WAN interface",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.CounterValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetTotalPacketsSent",
|
|
|
|
Result: "TotalPacketsSent",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_packets_sent",
|
|
|
|
"packets sent on gateway WAN interface",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.CounterValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetAddonInfos",
|
|
|
|
Result: "TotalBytesReceived",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_bytes_received",
|
|
|
|
"bytes received on gateway WAN interface",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.CounterValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetAddonInfos",
|
|
|
|
Result: "TotalBytesSent",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_bytes_sent",
|
|
|
|
"bytes sent on gateway WAN interface",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.CounterValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetCommonLinkProperties",
|
|
|
|
Result: "Layer1UpstreamMaxBitRate",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_layer1_upstream_max_bitrate",
|
|
|
|
"Layer1 upstream max bitrate",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.GaugeValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetCommonLinkProperties",
|
|
|
|
Result: "Layer1DownstreamMaxBitRate",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_layer1_downstream_max_bitrate",
|
|
|
|
"Layer1 downstream max bitrate",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.GaugeValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
|
|
|
|
Action: "GetCommonLinkProperties",
|
|
|
|
Result: "PhysicalLinkStatus",
|
|
|
|
OkValue: "Up",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_layer1_link_status",
|
|
|
|
"Status of physical link (Up = 1)",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.GaugeValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANIPConnection:1",
|
|
|
|
Action: "GetStatusInfo",
|
|
|
|
Result: "ConnectionStatus",
|
|
|
|
OkValue: "Connected",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_connection_status",
|
|
|
|
"WAN connection status (Connected = 1)",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.GaugeValue,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Service: "urn:schemas-upnp-org:service:WANIPConnection:1",
|
|
|
|
Action: "GetStatusInfo",
|
|
|
|
Result: "Uptime",
|
|
|
|
Desc: prometheus.NewDesc(
|
|
|
|
"gateway_wan_connection_uptime_seconds",
|
|
|
|
"WAN connection uptime",
|
|
|
|
[]string{"gateway"},
|
|
|
|
nil,
|
|
|
|
),
|
|
|
|
MetricType: prometheus.GaugeValue,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
type FritzboxCollector struct {
|
|
|
|
Gateway string
|
|
|
|
Port uint16
|
|
|
|
|
|
|
|
sync.Mutex // protects Root
|
|
|
|
Root *upnp.Root
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadServices tries to load the service information. Retries until success.
|
|
|
|
func (fc *FritzboxCollector) LoadServices() {
|
|
|
|
for {
|
|
|
|
root, err := upnp.LoadServices(fc.Gateway, fc.Port)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *FritzboxCollector) Describe(ch chan<- *prometheus.Desc) {
|
|
|
|
for _, m := range metrics {
|
|
|
|
ch <- m.Desc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
|
|
|
|
fc.Lock()
|
|
|
|
root := fc.Root
|
|
|
|
fc.Unlock()
|
|
|
|
|
|
|
|
if root == nil {
|
|
|
|
// Services not loaded yet
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
var last_service string
|
|
|
|
var last_method string
|
|
|
|
var last_result upnp.Result
|
|
|
|
|
|
|
|
for _, m := range metrics {
|
|
|
|
if m.Service != last_service || m.Action != last_method {
|
|
|
|
service, ok := root.Services[m.Service]
|
|
|
|
if !ok {
|
|
|
|
// TODO
|
|
|
|
fmt.Println("cannot find service", m.Service)
|
|
|
|
fmt.Println(root.Services)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
action, ok := service.Actions[m.Action]
|
|
|
|
if !ok {
|
|
|
|
// TODO
|
|
|
|
fmt.Println("cannot find action", m.Action)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
last_result, err = action.Call()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
collect_errors.Inc()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val, ok := last_result[m.Result]
|
|
|
|
if !ok {
|
|
|
|
fmt.Println("result not found", m.Result)
|
|
|
|
collect_errors.Inc()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
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", val)
|
|
|
|
collect_errors.Inc()
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ch <- prometheus.MustNewConstMetric(
|
|
|
|
m.Desc,
|
|
|
|
m.MetricType,
|
|
|
|
floatval,
|
|
|
|
fc.Gateway,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func test() {
|
|
|
|
root, err := upnp.LoadServices(*flag_gateway_address, uint16(*flag_gateway_port))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range root.Services {
|
|
|
|
fmt.Printf("%s: %s\n", s.Device.FriendlyName, s.ServiceType)
|
|
|
|
for _, a := range s.Actions {
|
|
|
|
if !a.IsGetOnly() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := a.Call()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf(" %s\n", a.Name)
|
|
|
|
for _, arg := range a.Arguments {
|
|
|
|
fmt.Printf(" %s: %v\n", arg.RelatedStateVariable, res[arg.StateVariable.Name])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if *flag_test {
|
|
|
|
test()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
collector := &FritzboxCollector{
|
|
|
|
Gateway: *flag_gateway_address,
|
|
|
|
Port: uint16(*flag_gateway_port),
|
|
|
|
}
|
|
|
|
|
|
|
|
go collector.LoadServices()
|
|
|
|
|
|
|
|
prometheus.MustRegister(collector)
|
|
|
|
prometheus.MustRegister(collect_errors)
|
|
|
|
|
|
|
|
http.Handle("/metrics", prometheus.Handler())
|
|
|
|
http.ListenAndServe(*flag_addr, nil)
|
|
|
|
}
|