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
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"music-server/internal/db/repository"
|
||||
"music-server/internal/logging"
|
||||
"music-server/internal/metrics"
|
||||
|
||||
"github.com/MShekow/directory-checksum/directory_checksum"
|
||||
"github.com/google/uuid"
|
||||
@@ -199,6 +200,7 @@ func (s *SyncService) run(ctx context.Context, full bool) {
|
||||
run := &syncRun{q: s.q, musicPath: s.musicPath, full: full, progress: func() { s.foldersSynced.Add(1) }}
|
||||
result := run.execute(ctx, func(total int) { s.foldersTotal.Store(int64(total)) })
|
||||
result.TotalTime = formatDuration(time.Since(start))
|
||||
s.recordMetrics(ctx, full, time.Since(start), len(result.CatchedErrors) == 0)
|
||||
|
||||
if s.onComplete != nil {
|
||||
s.onComplete(ctx)
|
||||
@@ -218,6 +220,42 @@ func (s *SyncService) run(ctx context.Context, full bool) {
|
||||
zap.Int("errors", len(result.CatchedErrors)))
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Start both sync types at 0, so the series exist before the first sync.
|
||||
metrics.SyncDuration.WithLabelValues(syncType(false))
|
||||
metrics.SyncDuration.WithLabelValues(syncType(true))
|
||||
}
|
||||
|
||||
// syncType is the type label of the sync metrics.
|
||||
func syncType(full bool) string {
|
||||
if full {
|
||||
return "full"
|
||||
}
|
||||
return "changes"
|
||||
}
|
||||
|
||||
// recordMetrics updates the sync and library metrics after a sync.
|
||||
func (s *SyncService) recordMetrics(ctx context.Context, full bool, took time.Duration, success bool) {
|
||||
metrics.SyncDuration.WithLabelValues(syncType(full)).Observe(took.Seconds())
|
||||
if success {
|
||||
metrics.SyncLastSuccess.SetToCurrentTime()
|
||||
}
|
||||
if err := s.UpdateLibraryMetrics(ctx); err != nil {
|
||||
logging.GetLogger().Error("Failed to count the library", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateLibraryMetrics sets the library size metrics from the database.
|
||||
func (s *SyncService) UpdateLibraryMetrics(ctx context.Context) error {
|
||||
count, err := s.q.CountLibrary(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metrics.LibrarySoundtracks.Set(float64(count.Soundtracks))
|
||||
metrics.LibrarySongs.Set(float64(count.Songs))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *syncRun) execute(ctx context.Context, setTotal func(int)) SyncResponse {
|
||||
var err error
|
||||
r.existing, err = r.q.GetAllSoundtracksIncludingDeleted(ctx)
|
||||
@@ -623,6 +661,7 @@ func (r *syncRun) removeBrokenSongs(ctx context.Context) {
|
||||
}
|
||||
_ = g.Wait()
|
||||
|
||||
metrics.BrokenSongs.Set(float64(len(broken)))
|
||||
if len(broken) > 0 {
|
||||
if err := r.q.RemoveBrokenSongs(ctx, broken); err != nil {
|
||||
r.addError("RemoveBrokenSongs", err, "")
|
||||
|
||||
Reference in new issue
Block a user