Initial version

This commit is contained in:
Laurence Withers 2023-03-03 18:23:21 +00:00
parent b9c0268b49
commit 8ad200b187
5 changed files with 165 additions and 1 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
aaisp-quota-exporter

View File

@ -1,3 +1,11 @@
# aaisp-quota-exporter
Trivial Prometheus exporter to fetch and plot remaining monthly quota for AAISP users.
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.

17
go.mod Normal file
View File

@ -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
)

30
go.sum Normal file
View File

@ -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=

108
main.go Normal file
View File

@ -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
}