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>
109 lines
3.9 KiB
Go
109 lines
3.9 KiB
Go
package backend
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"music-server/internal/db/repository"
|
|
"music-server/internal/metrics"
|
|
"music-server/internal/testutil/fakedb"
|
|
|
|
"github.com/google/uuid"
|
|
"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"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func requested(mode PickMode) float64 {
|
|
return testutil.ToFloat64(metrics.SongsRequested.WithLabelValues(string(mode)))
|
|
}
|
|
|
|
func played(mode PickMode) float64 {
|
|
return testutil.ToFloat64(metrics.SongsPlayed.WithLabelValues(string(mode)))
|
|
}
|
|
|
|
func syncCount(syncType string) uint64 {
|
|
m := &dto.Metric{}
|
|
_ = metrics.SyncDuration.WithLabelValues(syncType).(prometheus.Metric).Write(m)
|
|
return m.GetHistogram().GetSampleCount()
|
|
}
|
|
|
|
func TestSongMetricsByPickMode(t *testing.T) {
|
|
store := fakedb.New()
|
|
addSoundtrackWithSongs(t, store, t.TempDir(), "Game A", 0, "Song 1")
|
|
m := NewMusicService(store, t.TempDir())
|
|
ctx := context.Background()
|
|
q := newQueue(t, m)
|
|
before := map[PickMode][2]float64{}
|
|
for _, mode := range []PickMode{PickRandom, PickLow, PickClassic} {
|
|
before[mode] = [2]float64{requested(mode), played(mode)}
|
|
}
|
|
|
|
pick := map[PickMode]func(context.Context, uuid.UUID) (string, error){
|
|
PickRandom: func(ctx context.Context, id uuid.UUID) (string, error) { return m.RandomSong(ctx, id) },
|
|
PickLow: func(ctx context.Context, id uuid.UUID) (string, error) { return m.RandomSongLowChance(ctx, id) },
|
|
PickClassic: func(ctx context.Context, id uuid.UUID) (string, error) { return m.RandomSongClassic(ctx, id) },
|
|
}
|
|
for songNo, mode := range []PickMode{PickRandom, PickLow, PickClassic} {
|
|
_, err := pick[mode](ctx, q)
|
|
require.NoError(t, err)
|
|
require.NoError(t, m.AddLatestToQueue(ctx, q))
|
|
require.NoError(t, m.SetPlayed(ctx, q, songNo))
|
|
}
|
|
// Fetched but never added: requested, not played.
|
|
_, err := m.RandomSong(ctx, q)
|
|
require.NoError(t, err)
|
|
require.NoError(t, m.AddLatestPlayed(ctx, q), "the current song is the classic one")
|
|
|
|
assert.Equal(t, before[PickRandom][0]+2, requested(PickRandom))
|
|
assert.Equal(t, before[PickLow][0]+1, requested(PickLow))
|
|
assert.Equal(t, before[PickClassic][0]+1, requested(PickClassic))
|
|
assert.Equal(t, before[PickRandom][1]+1, played(PickRandom))
|
|
assert.Equal(t, before[PickLow][1]+1, played(PickLow))
|
|
assert.Equal(t, before[PickClassic][1]+2, played(PickClassic))
|
|
}
|
|
|
|
func TestSongsPlayedIgnoresSongsOutsideTheQueue(t *testing.T) {
|
|
m := NewMusicService(fakedb.New(), t.TempDir())
|
|
before := played(PickRandom)
|
|
require.NoError(t, m.SetPlayed(context.Background(), newQueue(t, m), 3))
|
|
assert.Equal(t, before, played(PickRandom))
|
|
}
|
|
|
|
func TestSyncMetrics(t *testing.T) {
|
|
f := newSyncFixture(t)
|
|
f.write("Game A/01.mp3", "a1")
|
|
f.write("Game A/02.mp3", "a2")
|
|
f.write("Game B/01.mp3", "b1")
|
|
changesBefore, fullBefore := syncCount("changes"), syncCount("full")
|
|
metrics.SyncLastSuccess.Set(0)
|
|
|
|
f.run(false)
|
|
|
|
assert.Equal(t, changesBefore+1, syncCount("changes"))
|
|
assert.Equal(t, 2.0, testutil.ToFloat64(metrics.LibrarySoundtracks))
|
|
assert.Equal(t, 3.0, testutil.ToFloat64(metrics.LibrarySongs))
|
|
assert.InDelta(t, float64(time.Now().Unix()), testutil.ToFloat64(metrics.SyncLastSuccess), 5)
|
|
|
|
f.store.AddTestSong(repository.Song{SoundtrackID: 999, SongName: "gone", Path: "/does/not/exist.mp3"})
|
|
f.run(true)
|
|
|
|
assert.Equal(t, fullBefore+1, syncCount("full"))
|
|
assert.Equal(t, 1.0, testutil.ToFloat64(metrics.BrokenSongs))
|
|
assert.Equal(t, 3.0, testutil.ToFloat64(metrics.LibrarySongs), "the broken song is removed")
|
|
}
|
|
|
|
func TestSyncWithErrorsIsNotALastSuccess(t *testing.T) {
|
|
metrics.SyncLastSuccess.Set(0)
|
|
s := NewSyncService(fakedb.New(), filepath.Join(t.TempDir(), "missing"), nil)
|
|
require.True(t, s.Start(false))
|
|
s.Wait()
|
|
require.NotEmpty(t, s.Result().CatchedErrors)
|
|
|
|
assert.Zero(t, testutil.ToFloat64(metrics.SyncLastSuccess))
|
|
}
|