From 8ad200b18778e2f092a9d04d2c2e9f3de5b3c003 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Fri, 3 Mar 2023 18:23:21 +0000 Subject: [PATCH] Initial version --- .gitignore | 1 + README.md | 10 ++++- go.mod | 17 +++++++++ go.sum | 30 +++++++++++++++ main.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8be9449 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +aaisp-quota-exporter diff --git a/README.md b/README.md index 5829dd8..caa5c6f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ # aaisp-quota-exporter -Trivial Prometheus exporter to fetch and plot remaining monthly quota for AAISP users. \ No newline at end of file +Trivial Prometheus exporter to fetch and plot remaining monthly quota for AAISP +users. Uses the AAISP API documented at +[https://support.aa.net.uk/Home::1](https://support.aa.net.uk/Home::1). + +The exporter takes an optional `-bind` parameter with `[host]:port` argument. +It will default to `:9100` (bind to all addresses, port 9100). Metrics are +available at `/metrics`. + +It will update every 5 minutes. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..290449e --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module src.lwithers.me.uk/go/aaisp-quota-exporter + +go 1.20 + +require github.com/prometheus/client_golang v1.14.0 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.41.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + golang.org/x/sys v0.5.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9e7acd5 --- /dev/null +++ b/go.sum @@ -0,0 +1,30 @@ +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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw= +github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/main.go b/main.go new file mode 100644 index 0000000..49238ee --- /dev/null +++ b/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "crypto/tls" + "encoding/json" + "errors" + "flag" + "fmt" + "net/http" + "os" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +const ( + period = 5 * time.Minute + timeout = 10 * time.Second + api = "https://quota.aa.net.uk" +) + +var ( + cl *http.Client + bind = flag.String("bind", ":9100", "[host]:port to bind to.") + + monthlyQuotaBytes = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "aaisp_quota_monthly_bytes", + Help: "Monthly quota provided by AAISP.", + }) + quotaRemainingBytes = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "aaisp_quota_remaining_bytes", + Help: "Number of bytes remaining on this month's AAISP quota.", + }) +) + +func main() { + flag.Parse() + + cl = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, + }, + Timeout: timeout, + } + + go collect() + + http.Handle("/metrics", promhttp.Handler()) + err := http.ListenAndServe(*bind, nil) + switch { + case errors.Is(err, http.ErrServerClosed), err == nil: + // clean shutdown + default: + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func collect() { + for { + quota, err := getQuota() + if err != nil { + fmt.Fprintln(os.Stderr, err) + } else { + monthlyQuotaBytes.Set(quota.MonthlyQuotaBytes) + quotaRemainingBytes.Set(quota.QuotaRemainingBytes) + } + + now := time.Now() + align := now.Truncate(period).Add(period) + s := align.Sub(now) + if s > 0 { + time.Sleep(s) + } + } +} + +type quotaResponse struct { + MonthlyQuotaBytes float64 `json:"monthly_quota"` + QuotaRemainingBytes float64 `json:"quota_remaining"` +} + +func getQuota() (quotaResponse, error) { + req, err := http.NewRequest(http.MethodGet, api, nil) + if err != nil { + return quotaResponse{}, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "src.lwithers.me.uk/go/aaisp-quota-exporter v1.0") + + resp, err := cl.Do(req) + if err != nil { + return quotaResponse{}, err + } + defer resp.Body.Close() + + var q quotaResponse + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&q); err != nil { + return quotaResponse{}, fmt.Errorf("%s: could not decode response: %w", api, err) + } + + return q, nil +}