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