Metrics are served on METRICS_PORT (default 2112, "off" to disable), never on the public port. Besides the Go runtime and process metrics this adds: - http_requests_total and http_request_duration_seconds per route pattern, from an Echo middleware. - musicserver_songs_requested_total and musicserver_songs_played_total by pick mode (random, low, classic). The mode is now stored with each queued song (migration 000006, not released yet). - Library size, sync duration, last successful sync, broken songs from the last full sync, and the database connection pool. Refs #35, #36, #37 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
2.3 KiB
Go
76 lines
2.3 KiB
Go
package metrics
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
echoMiddleware "github.com/labstack/echo/v5/middleware"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
|
dto "github.com/prometheus/client_model/go"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func newEcho() *echo.Echo {
|
|
e := echo.New()
|
|
e.Use(Middleware())
|
|
e.Use(echoMiddleware.Recover())
|
|
e.GET("/items/:id", func(c *echo.Context) error { return c.String(http.StatusOK, "item") })
|
|
e.GET("/fail", func(c *echo.Context) error { return errors.New("broken") })
|
|
e.GET("/panic", func(c *echo.Context) error { panic("boom") })
|
|
return e
|
|
}
|
|
|
|
func serve(e *echo.Echo, method, target string) {
|
|
e.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(method, target, nil))
|
|
}
|
|
|
|
func requests(method, route, code string) float64 {
|
|
return testutil.ToFloat64(httpRequests.WithLabelValues(method, route, code))
|
|
}
|
|
|
|
func TestMiddlewareCountsRequestsByRoutePattern(t *testing.T) {
|
|
e := newEcho()
|
|
before := requests("GET", "/items/:id", "200")
|
|
|
|
serve(e, http.MethodGet, "/items/1?x=secret")
|
|
serve(e, http.MethodGet, "/items/2")
|
|
|
|
assert.Equal(t, before+2, requests("GET", "/items/:id", "200"))
|
|
assert.Equal(t, 0.0, requests("GET", "/items/1?x=secret", "200"), "the URL is never a label")
|
|
assert.Equal(t, 0.0, requests("GET", "/items/1", "200"))
|
|
}
|
|
|
|
func TestMiddlewareRecordsErrorStatus(t *testing.T) {
|
|
e := newEcho()
|
|
failBefore := requests("GET", "/fail", "500")
|
|
panicBefore := requests("GET", "/panic", "500")
|
|
missingBefore := requests("GET", unmatchedRoute, "404")
|
|
|
|
serve(e, http.MethodGet, "/fail")
|
|
serve(e, http.MethodGet, "/panic")
|
|
serve(e, http.MethodGet, "/no/such/page?q=1")
|
|
|
|
assert.Equal(t, failBefore+1, requests("GET", "/fail", "500"))
|
|
assert.Equal(t, panicBefore+1, requests("GET", "/panic", "500"), "Recover turns the panic into a 500")
|
|
assert.Equal(t, missingBefore+1, requests("GET", unmatchedRoute, "404"))
|
|
}
|
|
|
|
func observations(method, route string) uint64 {
|
|
m := &dto.Metric{}
|
|
_ = httpDuration.WithLabelValues(method, route).(prometheus.Metric).Write(m)
|
|
return m.GetHistogram().GetSampleCount()
|
|
}
|
|
|
|
func TestMiddlewareRecordsLatency(t *testing.T) {
|
|
e := newEcho()
|
|
before := observations("GET", "/items/:id")
|
|
|
|
serve(e, http.MethodGet, "/items/1")
|
|
|
|
assert.Equal(t, before+1, observations("GET", "/items/:id"))
|
|
}
|