package library import ( "sync" "time" ) // ScanMetrics holds timing and count data collected during a library scan. // Worker-pool fields are protected by a mutex; DB-writer fields are // single-threaded and use plain addition. type ScanMetrics struct { mu sync.Mutex // Top-level phases (wall-clock). Total time.Duration `json:"total"` LoadExisting time.Duration `json:"loadExisting"` WalkDuration time.Duration `json:"walkDuration"` ExtractionWallClock time.Duration `json:"extractionWallClock"` DBWritesWallClock time.Duration `json:"dbWritesWallClock"` OrphanCleanup time.Duration `json:"orphanCleanup"` PostScanVariants time.Duration `json:"postScanVariants"` // Per-format extraction (cumulative across workers). FormatExtraction map[string]int64 `json:"formatExtraction"` FormatCount map[string]int64 `json:"formatCount"` // Sub-operation cumulative times (across workers). TagExtraction time.Duration `json:"tagExtraction"` DurationExtraction time.Duration `json:"durationExtraction"` // DB sub-operations (cumulative, single-threaded DB writer). BatchCommits time.Duration `json:"batchCommits"` CoverArtSave time.Duration `json:"coverArtSave"` // Thumbnail generation (async worker pool). ThumbnailWallClock time.Duration `json:"thumbnailWallClock"` ThumbnailGeneration time.Duration `json:"thumbnailGeneration"` ThumbnailSmall time.Duration `json:"thumbnailSmall"` ThumbnailMedium time.Duration `json:"thumbnailMedium"` ThumbnailLarge time.Duration `json:"thumbnailLarge"` // Full-rescan-specific phases. ClearQueue time.Duration `json:"clearQueue"` ClearDatabase time.Duration `json:"clearDatabase"` ClearCoverFiles time.Duration `json:"clearCoverFiles"` // File counts. Added int64 `json:"added"` Updated int64 `json:"updated"` Skipped int64 `json:"skipped"` Removed int64 `json:"removed"` } func newScanMetrics() *ScanMetrics { return &ScanMetrics{ FormatExtraction: make(map[string]int64), FormatCount: make(map[string]int64), } } // addExtraction records per-file extraction timing from a worker // goroutine. It is safe for concurrent use. func (m *ScanMetrics) addExtraction( fileType string, tagTime, durationTime time.Duration, ) { m.mu.Lock() defer m.mu.Unlock() total := tagTime + durationTime m.FormatExtraction[fileType] += total.Milliseconds() m.FormatCount[fileType]++ m.TagExtraction += tagTime m.DurationExtraction += durationTime } // addCoverArtSave records the time spent saving an original cover // art file. Called from the single-threaded DB writer. func (m *ScanMetrics) addCoverArtSave(d time.Duration) { m.CoverArtSave += d } // addThumbnailTier records the time spent generating a single // thumbnail tier. Safe for concurrent use from the thumbnail // worker pool. func (m *ScanMetrics) addThumbnailTier( suffix string, d time.Duration, ) { m.mu.Lock() defer m.mu.Unlock() m.ThumbnailGeneration += d switch suffix { case "_sm": m.ThumbnailSmall += d case "_md": m.ThumbnailMedium += d case "_lg": m.ThumbnailLarge += d } }