Initial commit
This commit is contained in:
257
internal/speedport/exporter.go
Normal file
257
internal/speedport/exporter.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package speedport
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const namespace = "speedport"
|
||||
|
||||
// Exporter collects Speedport metrics and exposes them as Prometheus metrics.
|
||||
type Exporter struct {
|
||||
client *Client
|
||||
logger *slog.Logger
|
||||
|
||||
mu sync.Mutex
|
||||
scrapeErrors prometheus.Counter
|
||||
scrapeDuration prometheus.Gauge
|
||||
up prometheus.Gauge
|
||||
|
||||
// DSL metrics
|
||||
dslUpRate *prometheus.GaugeVec
|
||||
dslDownRate *prometheus.GaugeVec
|
||||
dslSNR *prometheus.GaugeVec
|
||||
dslAttenuation *prometheus.GaugeVec
|
||||
dslPower *prometheus.GaugeVec
|
||||
dslErrors *prometheus.GaugeVec
|
||||
dslUptime prometheus.Gauge
|
||||
|
||||
// Connection metrics
|
||||
onlineStatus prometheus.Gauge
|
||||
|
||||
// Generic metric for all raw values
|
||||
rawValues *prometheus.GaugeVec
|
||||
rawInfo *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewExporter creates a new Speedport Prometheus exporter.
|
||||
func NewExporter(client *Client, logger *slog.Logger) *Exporter {
|
||||
return &Exporter{
|
||||
client: client,
|
||||
logger: logger,
|
||||
|
||||
scrapeErrors: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Name: "scrape_errors_total",
|
||||
Help: "Total number of scrape errors.",
|
||||
}),
|
||||
scrapeDuration: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "scrape_duration_seconds",
|
||||
Help: "Duration of the last scrape in seconds.",
|
||||
}),
|
||||
up: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "up",
|
||||
Help: "Whether the Speedport is reachable (1=up, 0=down).",
|
||||
}),
|
||||
|
||||
dslUpRate: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "dsl",
|
||||
Name: "rate_kbps",
|
||||
Help: "DSL line rate in kbit/s.",
|
||||
}, []string{"direction", "type"}),
|
||||
|
||||
dslSNR: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "dsl",
|
||||
Name: "snr_db",
|
||||
Help: "DSL signal-to-noise ratio margin in dB.",
|
||||
}, []string{"direction"}),
|
||||
|
||||
dslAttenuation: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "dsl",
|
||||
Name: "attenuation_db",
|
||||
Help: "DSL line attenuation in dB.",
|
||||
}, []string{"direction"}),
|
||||
|
||||
dslPower: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "dsl",
|
||||
Name: "power_dbm",
|
||||
Help: "DSL signal power in dBm.",
|
||||
}, []string{"direction"}),
|
||||
|
||||
dslErrors: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "dsl",
|
||||
Name: "errors_total",
|
||||
Help: "DSL error counters (CRC, FEC, HEC).",
|
||||
}, []string{"direction", "type"}),
|
||||
|
||||
dslUptime: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "dsl",
|
||||
Name: "uptime_seconds",
|
||||
Help: "Internet connection uptime in seconds.",
|
||||
}),
|
||||
|
||||
onlineStatus: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "online_status",
|
||||
Help: "Online status (1=online, 0=offline).",
|
||||
}),
|
||||
|
||||
rawValues: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "raw_value",
|
||||
Help: "Raw numeric values from the router status page.",
|
||||
}, []string{"varid", "vartype"}),
|
||||
|
||||
rawInfo: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "info",
|
||||
Help: "Router information as labels (value is always 1).",
|
||||
}, []string{"device_name", "firmware_version", "serial_number", "dsl_mode"}),
|
||||
}
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
e.scrapeErrors.Describe(ch)
|
||||
e.scrapeDuration.Describe(ch)
|
||||
e.up.Describe(ch)
|
||||
e.dslUpRate.Describe(ch)
|
||||
e.dslSNR.Describe(ch)
|
||||
e.dslAttenuation.Describe(ch)
|
||||
e.dslPower.Describe(ch)
|
||||
e.dslErrors.Describe(ch)
|
||||
e.dslUptime.Describe(ch)
|
||||
e.onlineStatus.Describe(ch)
|
||||
e.rawValues.Describe(ch)
|
||||
e.rawInfo.Describe(ch)
|
||||
}
|
||||
|
||||
// Collect implements prometheus.Collector.
|
||||
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
// Reset vector metrics to avoid stale entries
|
||||
e.dslUpRate.Reset()
|
||||
e.dslSNR.Reset()
|
||||
e.dslAttenuation.Reset()
|
||||
e.dslPower.Reset()
|
||||
e.dslErrors.Reset()
|
||||
e.rawValues.Reset()
|
||||
e.rawInfo.Reset()
|
||||
|
||||
status, err := e.client.GetStatus()
|
||||
if err != nil {
|
||||
e.logger.Error("failed to fetch status", "error", err)
|
||||
e.scrapeErrors.Inc()
|
||||
e.up.Set(0)
|
||||
} else {
|
||||
e.up.Set(1)
|
||||
e.processStatus(status)
|
||||
}
|
||||
|
||||
e.scrapeDuration.Set(time.Since(start).Seconds())
|
||||
|
||||
// Collect all metrics
|
||||
e.scrapeErrors.Collect(ch)
|
||||
e.scrapeDuration.Collect(ch)
|
||||
e.up.Collect(ch)
|
||||
e.dslUpRate.Collect(ch)
|
||||
e.dslSNR.Collect(ch)
|
||||
e.dslAttenuation.Collect(ch)
|
||||
e.dslPower.Collect(ch)
|
||||
e.dslErrors.Collect(ch)
|
||||
e.dslUptime.Collect(ch)
|
||||
e.onlineStatus.Collect(ch)
|
||||
e.rawValues.Collect(ch)
|
||||
e.rawInfo.Collect(ch)
|
||||
}
|
||||
|
||||
// processStatus extracts metrics from status data.
|
||||
func (e *Exporter) processStatus(sd *StatusData) {
|
||||
dsl := ExtractDSLInfo(sd)
|
||||
|
||||
// Online status
|
||||
if strings.EqualFold(dsl.OnlineStatus, "online") {
|
||||
e.onlineStatus.Set(1)
|
||||
} else {
|
||||
e.onlineStatus.Set(0)
|
||||
}
|
||||
|
||||
// DSL rates
|
||||
if dsl.UpstreamRate > 0 {
|
||||
e.dslUpRate.WithLabelValues("upstream", "current").Set(dsl.UpstreamRate)
|
||||
}
|
||||
if dsl.DownstreamRate > 0 {
|
||||
e.dslUpRate.WithLabelValues("downstream", "current").Set(dsl.DownstreamRate)
|
||||
}
|
||||
if dsl.UpstreamMaxRate > 0 {
|
||||
e.dslUpRate.WithLabelValues("upstream", "max").Set(dsl.UpstreamMaxRate)
|
||||
}
|
||||
if dsl.DownstreamMaxRate > 0 {
|
||||
e.dslUpRate.WithLabelValues("downstream", "max").Set(dsl.DownstreamMaxRate)
|
||||
}
|
||||
|
||||
// SNR
|
||||
if dsl.UpstreamNoise > 0 {
|
||||
e.dslSNR.WithLabelValues("upstream").Set(dsl.UpstreamNoise)
|
||||
}
|
||||
if dsl.DownstreamNoise > 0 {
|
||||
e.dslSNR.WithLabelValues("downstream").Set(dsl.DownstreamNoise)
|
||||
}
|
||||
|
||||
// Attenuation
|
||||
if dsl.UpstreamAttn > 0 {
|
||||
e.dslAttenuation.WithLabelValues("upstream").Set(dsl.UpstreamAttn)
|
||||
}
|
||||
if dsl.DownstreamAttn > 0 {
|
||||
e.dslAttenuation.WithLabelValues("downstream").Set(dsl.DownstreamAttn)
|
||||
}
|
||||
|
||||
// Power
|
||||
e.dslPower.WithLabelValues("upstream").Set(dsl.UpstreamPower)
|
||||
e.dslPower.WithLabelValues("downstream").Set(dsl.DownstreamPower)
|
||||
|
||||
// Error counters
|
||||
e.dslErrors.WithLabelValues("upstream", "crc").Set(dsl.UpstreamCRC)
|
||||
e.dslErrors.WithLabelValues("downstream", "crc").Set(dsl.DownstreamCRC)
|
||||
e.dslErrors.WithLabelValues("upstream", "fec").Set(dsl.UpstreamFEC)
|
||||
e.dslErrors.WithLabelValues("downstream", "fec").Set(dsl.DownstreamFEC)
|
||||
e.dslErrors.WithLabelValues("upstream", "hec").Set(dsl.UpstreamHEC)
|
||||
e.dslErrors.WithLabelValues("downstream", "hec").Set(dsl.DownstreamHEC)
|
||||
|
||||
// Uptime
|
||||
if dsl.Uptime > 0 {
|
||||
e.dslUptime.Set(dsl.Uptime)
|
||||
}
|
||||
|
||||
// Info metric
|
||||
e.rawInfo.WithLabelValues(
|
||||
sd.Get("device_name"),
|
||||
sd.Get("firmware_version"),
|
||||
sd.Get("serial_number"),
|
||||
dsl.DSLOperatingMode,
|
||||
).Set(1)
|
||||
|
||||
// Export all numeric raw values
|
||||
for _, entry := range sd.Raw {
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(entry.VarValue), 64); err == nil {
|
||||
e.rawValues.WithLabelValues(entry.VarID, entry.VarType).Set(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user