cloud/shepherd: add equinix API metrics

This adds the following signals to our interaction with the Equinix API:

 1. Latency
 2. Traffic
 3. Errors
 4. Saturation

Change-Id: Ic2d5e36a7a26ab906ac1c2fa6741ebf86b9e551a
Reviewed-on: https://review.monogon.dev/c/monogon/+/1606
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/cloud/shepherd/equinix/wrapngo/duct_tape.go b/cloud/shepherd/equinix/wrapngo/duct_tape.go
index f58cf7b..d5dab7c 100644
--- a/cloud/shepherd/equinix/wrapngo/duct_tape.go
+++ b/cloud/shepherd/equinix/wrapngo/duct_tape.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 	"net/http"
+	"time"
 
 	"github.com/cenkalti/backoff/v4"
 	"github.com/packethost/packngo"
@@ -26,14 +27,10 @@
 // idempotent logic, as long as it cooperates with the above contract.
 func wrap[U any](ctx context.Context, cl *client, fn func(*packngo.Client) (U, error)) (U, error) {
 	var zero U
-	select {
-	case cl.serializer <- struct{}{}:
-	case <-ctx.Done():
-		return zero, ctx.Err()
+	if err := cl.serializer.up(ctx); err != nil {
+		return zero, err
 	}
-	defer func() {
-		<-cl.serializer
-	}()
+	defer cl.serializer.down()
 
 	bc := backoff.WithContext(cl.o.BackOff(), ctx)
 	pngo, err := cl.clientForContext(ctx)
@@ -60,11 +57,16 @@
 type injectContextRoundTripper struct {
 	ctx      context.Context
 	original http.RoundTripper
+	metrics  *metricsSet
 }
 
 func (r *injectContextRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
 	klog.V(5).Infof("Request -> %v", req.URL.String())
+	start := time.Now()
 	res, err := r.original.RoundTrip(req.WithContext(r.ctx))
+	latency := time.Since(start)
+	r.metrics.onAPIRequestDone(req, res, err, latency)
+
 	if err != nil {
 		klog.V(5).Infof("HTTP error <- %v", err)
 	} else {
@@ -78,6 +80,7 @@
 		Transport: &injectContextRoundTripper{
 			ctx:      ctx,
 			original: http.DefaultTransport,
+			metrics:  c.metrics,
 		},
 	}
 	return packngo.NewClient(packngo.WithAuth(c.username, c.token), packngo.WithHTTPClient(httpcl))