First version
This commit is contained in:
parent
3ca82d6287
commit
1114af5f90
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal 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
17
go.mod
Normal 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
24
go.sum
Normal 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
231
main.go
Normal 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))
|
||||
}
|
Loading…
Reference in New Issue
Block a user