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
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"music-server/internal/db/repository"
|
||||
"music-server/internal/logging"
|
||||
"music-server/internal/metrics"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -21,6 +22,24 @@ import (
|
||||
// maxPickAttempts bounds how many broken songs are skipped before giving up.
|
||||
const maxPickAttempts = 100
|
||||
|
||||
// PickMode is how a random song was picked. It is stored with queued songs and used as
|
||||
// the mode label of the song metrics.
|
||||
type PickMode string
|
||||
|
||||
const (
|
||||
PickRandom PickMode = "random"
|
||||
PickLow PickMode = "low"
|
||||
PickClassic PickMode = "classic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Start every mode at 0, so the series exist before the first song.
|
||||
for _, mode := range []PickMode{PickRandom, PickLow, PickClassic} {
|
||||
metrics.SongsRequested.WithLabelValues(string(mode))
|
||||
metrics.SongsPlayed.WithLabelValues(string(mode))
|
||||
}
|
||||
}
|
||||
|
||||
type SongInfo struct {
|
||||
SoundtrackUuid uuid.UUID `json:"SoundtrackUuid"`
|
||||
Soundtrack string `json:"Soundtrack"`
|
||||
@@ -145,7 +164,11 @@ func (m *MusicService) markPlayed(ctx context.Context, songs []repository.ListQu
|
||||
if err := m.q.AddSoundtrackPlayedByUUID(ctx, song.SoundtrackUuid); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.q.AddSongPlayedByUUID(ctx, song.SongUuid)
|
||||
if err := m.q.AddSongPlayedByUUID(ctx, song.SongUuid); err != nil {
|
||||
return err
|
||||
}
|
||||
metrics.SongsPlayed.WithLabelValues(song.Mode).Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MusicService) loadQueue(ctx context.Context, queueID uuid.UUID) (repository.Queue, []repository.ListQueueSongsRow, error) {
|
||||
@@ -167,7 +190,7 @@ func (m *MusicService) RandomSong(ctx context.Context, queueID uuid.UUID) (strin
|
||||
for i, s := range soundtracks {
|
||||
candidates[i] = weightedSoundtrack{soundtrack: s, weight: 1}
|
||||
}
|
||||
return m.pickAndRemember(ctx, queueID, candidates)
|
||||
return m.pickAndRemember(ctx, queueID, candidates, PickRandom)
|
||||
}
|
||||
|
||||
// RandomSongLowChance picks a random song, favouring soundtracks played less than average.
|
||||
@@ -176,23 +199,30 @@ func (m *MusicService) RandomSongLowChance(ctx context.Context, queueID uuid.UUI
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return m.pickAndRemember(ctx, queueID, lowChanceWeights(soundtracks))
|
||||
return m.pickAndRemember(ctx, queueID, lowChanceWeights(soundtracks), PickLow)
|
||||
}
|
||||
|
||||
func (m *MusicService) pickAndRemember(ctx context.Context, queueID uuid.UUID, candidates []weightedSoundtrack) (string, error) {
|
||||
func (m *MusicService) pickAndRemember(
|
||||
ctx context.Context, queueID uuid.UUID, candidates []weightedSoundtrack, mode PickMode,
|
||||
) (string, error) {
|
||||
song, err := m.pickSong(ctx, candidates)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return m.remember(ctx, queueID, song)
|
||||
return m.remember(ctx, queueID, song, mode)
|
||||
}
|
||||
|
||||
// remember stores the song as the queue's last fetched song, for AddLatestToQueue.
|
||||
func (m *MusicService) remember(ctx context.Context, queueID uuid.UUID, song repository.Song) (string, error) {
|
||||
err := m.q.SetQueueLastFetched(ctx, repository.SetQueueLastFetchedParams{Uuid: queueID, LastFetchedSongUuid: song.Uuid})
|
||||
// remember stores the song and how it was picked as the queue's last fetched song,
|
||||
// for AddLatestToQueue.
|
||||
func (m *MusicService) remember(ctx context.Context, queueID uuid.UUID, song repository.Song, mode PickMode) (string, error) {
|
||||
modeName := string(mode)
|
||||
err := m.q.SetQueueLastFetched(ctx, repository.SetQueueLastFetchedParams{
|
||||
Uuid: queueID, LastFetchedSongUuid: song.Uuid, LastFetchedMode: &modeName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
metrics.SongsRequested.WithLabelValues(modeName).Inc()
|
||||
return song.Path, nil
|
||||
}
|
||||
|
||||
@@ -212,7 +242,7 @@ func (m *MusicService) RandomSongClassic(ctx context.Context, queueID uuid.UUID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return m.remember(ctx, queueID, row.Song)
|
||||
return m.remember(ctx, queueID, row.Song, PickClassic)
|
||||
}
|
||||
return "", ErrNoSongs
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user