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 { Error string `json:"error"` 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) } if q.Error != "" { return quotaResponse{}, fmt.Errorf("%s: reported error: %q", api, q.Error) } if q.MonthlyQuotaBytes < 1 { return quotaResponse{}, fmt.Errorf("%s: returned zero values", api) } return q, nil }