mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
feat(artwork): add a resolution chain trace collector
This commit is contained in:
parent
b617a878b9
commit
bda549f8a8
2 changed files with 97 additions and 0 deletions
54
core/artwork/trace.go
Normal file
54
core/artwork/trace.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
outcomeHit = "hit"
|
||||
outcomeMiss = "miss"
|
||||
outcomeUnreadable = "unreadable"
|
||||
outcomeSkipped = "skipped"
|
||||
outcomeWouldTry = "would-try"
|
||||
outcomeError = "error"
|
||||
outcomeNotReached = "not-reached"
|
||||
)
|
||||
|
||||
// traceStep is one candidate the priority chain considered.
|
||||
type traceStep struct {
|
||||
Candidate string
|
||||
Outcome string
|
||||
Detail string
|
||||
}
|
||||
|
||||
// chainTrace collects the walk of a single resolution. The artwork worker never attaches
|
||||
// one; only the CLI does, so resolution stays allocation-free in the hot path.
|
||||
type chainTrace struct {
|
||||
mu sync.Mutex
|
||||
steps []traceStep
|
||||
}
|
||||
|
||||
func (t *chainTrace) add(step traceStep) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.steps = append(t.steps, step)
|
||||
}
|
||||
|
||||
func (t *chainTrace) Steps() []traceStep {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return slices.Clone(t.steps)
|
||||
}
|
||||
|
||||
type traceCtxKey struct{}
|
||||
|
||||
func withTrace(ctx context.Context, t *chainTrace) context.Context {
|
||||
return context.WithValue(ctx, traceCtxKey{}, t)
|
||||
}
|
||||
|
||||
func traceFrom(ctx context.Context) *chainTrace {
|
||||
t, _ := ctx.Value(traceCtxKey{}).(*chainTrace)
|
||||
return t
|
||||
}
|
||||
43
core/artwork/trace_test.go
Normal file
43
core/artwork/trace_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("chainTrace", func() {
|
||||
It("returns nil when no trace is attached", func() {
|
||||
Expect(traceFrom(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("collects steps in order", func() {
|
||||
t := &chainTrace{}
|
||||
ctx := withTrace(context.Background(), t)
|
||||
|
||||
traceFrom(ctx).add(traceStep{Candidate: "cover.*", Outcome: outcomeMiss})
|
||||
traceFrom(ctx).add(traceStep{Candidate: "embedded", Outcome: outcomeHit, Detail: "/music/a.flac"})
|
||||
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "cover.*", Outcome: outcomeMiss},
|
||||
{Candidate: "embedded", Outcome: outcomeHit, Detail: "/music/a.flac"},
|
||||
}))
|
||||
})
|
||||
|
||||
It("is safe to use concurrently", func() {
|
||||
t := &chainTrace{}
|
||||
done := make(chan struct{})
|
||||
for range 10 {
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
t.add(traceStep{Candidate: "x", Outcome: outcomeMiss})
|
||||
done <- struct{}{}
|
||||
}()
|
||||
}
|
||||
for range 10 {
|
||||
<-done
|
||||
}
|
||||
Expect(t.Steps()).To(HaveLen(10))
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue