Every parser in `backend/metadata` is header-only -- a few hundred bytes and return -- so on a spinning disk a scan is not waiting on CPU or on bytes, it is waiting on the head to arrive. Two things follow, and the drive says which. **How many reads should be in flight.** This was a flat 2 for anything rotational, which is a pre-NCQ assumption: a modern SATA disk reports a queue depth of 32 and reorders outstanding reads into the order its head passes over them, and was being handed a quarter of what it can use. It gets 4 now. A drive that reports 1 -- a USB bridge, a pre-2004 disk -- services one command at a time in the order given, where every extra worker is one more seek competing for one head and the scan gets *slower* the harder it is pushed; that keeps 2. **And that the next seek should already be queued.** A prefetch stage between the walk and the workers issues `POSIX_FADV_WILLNEED` over the first 512 KB of each file -- enough for an ID3v2 tag carrying cover art, or FLAC's STREAMINFO and PICTURE blocks. The buffered channel *is* the lookahead: the goroutine runs 16 files ahead of the workers, hinting as it goes, so the read a worker needs has been in flight for sixteen files' worth of parsing by the time it asks. Rotational only; an SSD gets the channel back unwrapped and pays nothing, since it has no seek to hide and already has one worker per core. `workersForProfile` is the policy on its own so it can be tested against drives this machine does not have, and the scan logs the device, its rotational flag and its queue depth, so the decision is inspectable rather than inferred. Also: `ScanConcurrency` has been a validated three-value config field with exactly one caller, passing the constant `auto` -- so choosing `ssd` or `hdd` by hand did nothing at all. It reads the config now. The two modes overrule detection about the *disk* and not about its queue, since a user who picks `hdd` on a queueing drive still wants that drive's queue used. What is not here is inode-ordered dispatch. It needs the streaming walk restructured to buffer per directory, and with queueing the drive is already reordering what the hints put in front of it; that wants a measurement on real hardware before the complexity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MeQt5hgXg5YGoNZQ9ozG7L
123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
package library
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"yellowjacket/backend/system"
|
|
)
|
|
|
|
// How many workers a scan gets is decided by two facts about the
|
|
// device, and the second one is new: a spinning disk that can queue
|
|
// commands wants several reads in flight, and one that cannot wants
|
|
// almost none. Before this it was a flat 2 for anything rotational,
|
|
// which is a pre-NCQ assumption — a modern SATA disk reports a queue
|
|
// depth of 32 and was being given a quarter of what it can use.
|
|
func TestWorkersForProfile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const cpus = 16
|
|
|
|
ssd := system.DiskProfile{Device: "sda", QueueDepth: 32}
|
|
hddQueued := system.DiskProfile{
|
|
Device: "sdb", Rotational: true, QueueDepth: 32,
|
|
}
|
|
hddSerial := system.DiskProfile{
|
|
Device: "sdc", Rotational: true, QueueDepth: 1,
|
|
}
|
|
// Neither NVMe nor a device-mapper volume publishes queue_depth.
|
|
// An unknown depth must not be read as "cannot queue", or every
|
|
// such device would be scanned as if it were a 2003 drive.
|
|
unknown := system.DiskProfile{Device: "dm-0", Rotational: true}
|
|
|
|
tests := []struct {
|
|
name string
|
|
mode ScanConcurrency
|
|
profile system.DiskProfile
|
|
want int
|
|
}{
|
|
{"ssd auto", ScanConcurrencyAuto, ssd, cpus},
|
|
{"queueing hdd auto", ScanConcurrencyAuto, hddQueued, hddWorkerCountQueued},
|
|
{"serial hdd auto", ScanConcurrencyAuto, hddSerial, hddWorkerCountSerial},
|
|
{"unknown depth queues", ScanConcurrencyAuto, unknown, hddWorkerCountQueued},
|
|
|
|
// The mode overrules detection about the *disk*, never about
|
|
// its queue: forcing hdd on a queueing drive still uses it.
|
|
{"forced hdd on an ssd", ScanConcurrencyHDD, ssd, hddWorkerCountQueued},
|
|
{"forced ssd on an hdd", ScanConcurrencySSD, hddQueued, cpus},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := workersForProfile(tt.mode, tt.profile, cpus); got != tt.want {
|
|
t.Errorf(
|
|
"workersForProfile(%q, %+v) = %d, want %d",
|
|
tt.mode, tt.profile, got, tt.want,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A machine with fewer cores than the policy asks for gets its cores.
|
|
func TestWorkersNeverExceedTheCPUCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
hdd := system.DiskProfile{Rotational: true, QueueDepth: 32}
|
|
|
|
if got := workersForProfile(ScanConcurrencyAuto, hdd, 1); got != 1 {
|
|
t.Errorf("single-core hdd = %d workers, want 1", got)
|
|
}
|
|
}
|
|
|
|
// The prefetch stage must forward every item and nothing else: it is a
|
|
// pass-through with a side effect, and a scan that drops a file because
|
|
// of a *hint* would be a spectacular way to lose part of a library.
|
|
func TestReadaheadForwardsEveryFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
in := make(chan scanWork, 4)
|
|
for _, p := range []string{"/a", "/b", "/c", "/d"} {
|
|
in <- scanWork{absolutePath: p}
|
|
}
|
|
|
|
close(in)
|
|
|
|
var got []string
|
|
for w := range readaheadWork(
|
|
context.Background(),
|
|
in,
|
|
system.DiskProfile{Rotational: true, QueueDepth: 32},
|
|
) {
|
|
got = append(got, w.absolutePath)
|
|
}
|
|
|
|
want := []string{"/a", "/b", "/c", "/d"}
|
|
if len(got) != len(want) {
|
|
t.Fatalf("forwarded %v, want %v", got, want)
|
|
}
|
|
|
|
for i := range want {
|
|
if got[i] != want[i] {
|
|
t.Errorf("item %d = %q, want %q", i, got[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// On an SSD the stage is not inserted at all — the channel comes back
|
|
// unchanged, so a scan there pays nothing for a feature it cannot use.
|
|
func TestReadaheadIsSkippedOnSolidState(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
in := make(chan scanWork)
|
|
out := readaheadWork(
|
|
context.Background(), in, system.DiskProfile{QueueDepth: 32},
|
|
)
|
|
|
|
if out != (<-chan scanWork)(in) {
|
|
t.Error("an ssd must get the original channel, unwrapped")
|
|
}
|
|
}
|