Expose Prometheus metrics on a separate port
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>
This commit is contained in:
1 parent
c62b65d7cd
commit
f79f51ea4c
28 files changed
+864
-70
No files matched your search
@@ -0,0 +1,75 @@
|
||||
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"))
|
||||
}
|
||||
Reference in new issue
Block a user