Initial version
This commit is contained in:
parent
b9c0268b49
commit
8ad200b187
|
@ -0,0 +1 @@
|
|||
aaisp-quota-exporter
|
10
README.md
10
README.md
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue