First version

This commit is contained in:
Diego Fernando Carrión 2024-10-19 15:43:07 +02:00
parent 3ca82d6287
commit 1114af5f90
Signed by: CRThaze
GPG Key ID: 8279B79A1A7F8194
4 changed files with 295 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
speedporter

17
go.mod Normal file
View File

@ -0,0 +1,17 @@
module git.sdf.org/CRThaze/speedporter
go 1.22.7
require github.com/prometheus/client_golang v1.20.5
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

24
go.sum Normal file
View File

@ -0,0 +1,24 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=

231
main.go Normal file
View File

@ -0,0 +1,231 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
bandwidthScale = 125000.0
defaultListenPort = 9090
defaultListenAddr = "0.0.0.0"
defaultTestPeriod = 5
defaultSpeedtestExec = "/usr/bin/speedtest"
listenPortEnvVar = "SPEEDTEST_METRICS_LISTEN_PORT"
listenAddrEnvVar = "SPEEDTEST_METRICS_LISTEN_ADDR"
testPeriodEnvVar = "SPEEDTEST_PERIOD_MINS"
speedtestExecEnvVar = "SPEEDTEST_EXEC"
)
var (
labels = []string{
"interface_name",
"interface_mac",
"test_host",
}
uploadSpeed = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "isp_upload_speed",
Help: "The upload speed of the network",
},
labels,
)
downloadSpeed = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "isp_download_speed",
Help: "The download speed of the network",
},
labels,
)
pingLatency = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "isp_ping_latency",
Help: "The ping latency of the network",
},
labels,
)
listenPort uint16
listenAddr string
testPeriodMins uint
speedtestExec string
)
type SpeedTestResult struct {
Type string
Timestamp time.Time
Ping struct {
Jitter float64
Latency float64
Low float64
High float64
}
Download struct {
Bandwidth int
Bytes int
Elapsed int
Latency struct {
IQM float64
Low float64
High float64
Jitter float64
}
}
Upload struct {
Bandwidth int
Bytes int
Elapsed int
Latency struct {
IQM float64
Low float64
High float64
Jitter float64
}
}
PacketLoss int
ISP string
Interface struct {
InternalIP string
Name string
MACAddr string
IsVPN bool
ExternalIP string
}
Server struct {
ID int
Host string
Port int16
Name string
Location string
Country string
IP string
}
Result struct {
ID string
URL string
Persisted bool
}
}
func init() {
prometheus.MustRegister(uploadSpeed)
prometheus.MustRegister(downloadSpeed)
prometheus.MustRegister(pingLatency)
if lpEV, ok := os.LookupEnv(listenPortEnvVar); !ok {
listenPort = defaultListenPort
} else {
val, err := strconv.Atoi(lpEV)
if err != nil {
panic(fmt.Sprintf(
"Invalid Listen Port Specified in Environment Variable: %s",
listenPortEnvVar,
))
}
listenPort = uint16(val)
if int(listenPort) != val {
panic(fmt.Sprintf(
"Invalid Listen Port Specified in Environment Variable: %s=%d",
listenPortEnvVar,
val,
))
}
}
if tpEV, ok := os.LookupEnv(testPeriodEnvVar); !ok {
testPeriodMins = defaultTestPeriod
} else {
val, err := strconv.Atoi(tpEV)
if err != nil {
panic(fmt.Sprintf(
"Invalid Test Period Specified in Environment Variable: %s",
testPeriodEnvVar,
))
}
testPeriodMins = uint(val)
if int(testPeriodMins) != val {
panic(fmt.Sprintf(
"Invalid Test Period Specified in Environment Variable: %s=%d",
testPeriodEnvVar,
val,
))
}
}
if laEV, ok := os.LookupEnv(listenAddrEnvVar); !ok {
listenAddr = defaultListenAddr
} else if net.ParseIP(laEV) == nil {
panic(fmt.Sprintf(
"Invalid Listen Address Specified in Environment Variable: %s=%s",
listenAddrEnvVar,
laEV,
))
} else {
listenAddr = laEV
}
if seEV, ok := os.LookupEnv(speedtestExecEnvVar); !ok {
speedtestExec = defaultSpeedtestExec
} else {
speedtestExec = seEV
}
cmd := exec.Command(speedtestExec, "-h")
_, err := cmd.Output()
if err != nil {
panic(fmt.Sprintf("Error executing command: %v", err))
}
}
func runTest() {
cmd := exec.Command(speedtestExec, "-f", "json")
output, err := cmd.Output()
if err != nil {
log.Printf("Error executing command: %v", err)
return
}
var result SpeedTestResult
err = json.Unmarshal(output, &result)
if err != nil {
log.Printf("Error parsing JSON: %v\n", err)
return
} else if result.Type != "result" {
log.Printf("Did not receive a result: %s\n", result.Type)
return
}
uploadBandwidth := float64(result.Upload.Bandwidth) / bandwidthScale
downloadBandwidth := float64(result.Download.Bandwidth) / bandwidthScale
labelVals := []string{
result.Interface.Name,
result.Interface.MACAddr,
result.Server.Host,
}
uploadSpeed.WithLabelValues(labelVals...).Set(uploadBandwidth)
downloadSpeed.WithLabelValues(labelVals...).Set(downloadBandwidth)
pingLatency.WithLabelValues(labelVals...).Set(result.Ping.Latency)
}
func main() {
go func() {
for {
runTest()
time.Sleep(time.Duration(testPeriodMins) * time.Minute)
}
}()
http.Handle("/metrics", promhttp.Handler())
fmt.Printf("Starting server on %s:%d", listenAddr, listenPort)
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", listenAddr, listenPort), nil))
}