feat(prometheus): add metrics to Subsonic API and Plugins (#4266)
* Add prometheus metrics to subsonic and plugins * address feedback, do not log error if operation is not supported * add missing timestamp and client to stats * remove .view from subsonic route * directly inject DataStore in Prometheus, to avoid having to pass it in every call Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org> Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -2,13 +2,28 @@ package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/core/agents"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model/id"
|
||||
)
|
||||
|
||||
// newWasmBasePlugin creates a new instance of wasmBasePlugin with the required parameters.
|
||||
func newWasmBasePlugin[S any, P any](wasmPath, id, capability string, m metrics.Metrics, loader P, loadFunc loaderFunc[S, P]) *wasmBasePlugin[S, P] {
|
||||
return &wasmBasePlugin[S, P]{
|
||||
wasmPath: wasmPath,
|
||||
id: id,
|
||||
capability: capability,
|
||||
loader: loader,
|
||||
loadFunc: loadFunc,
|
||||
metrics: m,
|
||||
}
|
||||
}
|
||||
|
||||
// LoaderFunc is a generic function type that loads a plugin instance.
|
||||
type loaderFunc[S any, P any] func(ctx context.Context, loader P, path string) (S, error)
|
||||
|
||||
@@ -20,6 +35,7 @@ type wasmBasePlugin[S any, P any] struct {
|
||||
capability string
|
||||
loader P
|
||||
loadFunc loaderFunc[S, P]
|
||||
metrics metrics.Metrics
|
||||
}
|
||||
|
||||
func (w *wasmBasePlugin[S, P]) PluginID() string {
|
||||
@@ -34,6 +50,10 @@ func (w *wasmBasePlugin[S, P]) serviceName() string {
|
||||
return w.id + "_" + w.capability
|
||||
}
|
||||
|
||||
func (w *wasmBasePlugin[S, P]) getMetrics() metrics.Metrics {
|
||||
return w.metrics
|
||||
}
|
||||
|
||||
// getInstance loads a new plugin instance and returns a cleanup function.
|
||||
func (w *wasmBasePlugin[S, P]) getInstance(ctx context.Context, methodName string) (S, func(), error) {
|
||||
start := time.Now()
|
||||
@@ -57,7 +77,9 @@ func (w *wasmBasePlugin[S, P]) getInstance(ctx context.Context, methodName strin
|
||||
}
|
||||
|
||||
type wasmPlugin[S any] interface {
|
||||
PluginID() string
|
||||
getInstance(ctx context.Context, methodName string) (S, func(), error)
|
||||
getMetrics() metrics.Metrics
|
||||
}
|
||||
|
||||
type errorMapper interface {
|
||||
@@ -73,10 +95,25 @@ func callMethod[S any, R any](ctx context.Context, w wasmPlugin[S], methodName s
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
start := time.Now()
|
||||
defer done()
|
||||
r, err = fn(inst)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if em, ok := any(w).(errorMapper); ok {
|
||||
return r, em.mapError(err)
|
||||
mappedErr := em.mapError(err)
|
||||
|
||||
if !errors.Is(mappedErr, agents.ErrNotFound) {
|
||||
id := w.PluginID()
|
||||
isOk := mappedErr == nil
|
||||
metrics := w.getMetrics()
|
||||
if metrics != nil {
|
||||
metrics.RecordPluginRequest(ctx, id, methodName, isOk, elapsed.Milliseconds())
|
||||
}
|
||||
log.Trace(ctx, "callMethod", "plugin", id, "method", methodName, "ok", isOk, elapsed)
|
||||
}
|
||||
|
||||
return r, mappedErr
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user