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