diff --git a/AGENTS.md b/AGENTS.md index e1c4d2d7..a04abcb9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -148,6 +148,63 @@ Before committing: run `task lint-fix` then `task test`. Pull requests should NOT include a test plan section. +## Benchmarks + +Naming convention: `Benchmark{Component}_{Operation}_{Scale}` (e.g., `BenchmarkSlugSearchCache_Search_500k`) + +All benchmarks must: +- Call `b.ReportAllocs()` for allocation tracking +- Use `b.Run()` subtests for scale tiers or variants +- Set up data before `b.ResetTimer()` +- Use `for b.Loop()` iteration pattern + +```bash +task bench # Run all benchmarks +task bench-db # Run database benchmarks only +task bench-baseline # Generate baseline (commit the output) +task bench-compare # Compare current vs baseline via benchstat +``` + +Optimization targets and thresholds: [docs/optimization-targets.md](docs/optimization-targets.md) + +## Background Agent Mode + +When running as a background agent (scheduled, headless, or autonomous): + +### Always Allowed +- Run `task test`, `task lint`, `task vulncheck`, `task nilcheck`, `task deadlock` +- Run `task bench` and `task bench-compare` +- Read any file, run `go vet`, analyze code +- Report findings as GitHub issues with `agent-finding` label +- Run `task fuzz` with default time limits + +### Create PR with Evidence (Human Review Required) +- Performance optimizations — must include before/after benchstat output in PR description +- Refactoring that changes function signatures or public API +- Adding new dependencies +- Changes to security-sensitive files: + - `pkg/api/middleware/` (auth) + - `pkg/zapscript/utils.go` (command execution) + - `pkg/readers/shared/ndef/` (untrusted input parsing) + - `pkg/config/auth.go` (auth config) +- Database schema changes or migrations + +### Never +- Modify tests to make failing code pass (fix the code, not the test) +- Remove or weaken linter rules +- Add `nolint` directives without justification +- Disable security checks (gosec, govulncheck) +- Change the `forbidigo` rules for sync.Mutex/RWMutex +- Modify CI workflow files +- Push directly to main +- Change benchmark baselines without human review + +### Reporting Format +Title: `[agent:{type}] {summary}` — types: security, perf, quality +Body: evidence, affected files, proposed fix, risk assessment +Label: `agent-finding` +For perf findings: include benchstat comparison + ## When Stuck Don't guess — ask for help or gather more information first. diff --git a/Taskfile.dist.yml b/Taskfile.dist.yml index 73795eca..83c87263 100644 --- a/Taskfile.dist.yml +++ b/Taskfile.dist.yml @@ -80,10 +80,6 @@ tasks: vars: FUZZ_TIME: '{{default "30s" .FUZZ_TIME}}' cmds: - # ZapScript parser fuzz tests - - go test -run "^$" -fuzz=FuzzParseScript -fuzztime={{.FUZZ_TIME}} ./pkg/zapscript/parser/... - - go test -run "^$" -fuzz=FuzzParseExpressions -fuzztime={{.FUZZ_TIME}} ./pkg/zapscript/parser/... - - go test -run "^$" -fuzz=FuzzEvalExpressions -fuzztime={{.FUZZ_TIME}} ./pkg/zapscript/parser/... # NDEF parser fuzz tests - go test -run "^$" -fuzz=FuzzParseToText -fuzztime={{.FUZZ_TIME}} ./pkg/readers/shared/ndef/... - go test -run "^$" -fuzz=FuzzValidateNDEFMessage -fuzztime={{.FUZZ_TIME}} ./pkg/readers/shared/ndef/... @@ -124,6 +120,32 @@ tasks: cmds: - go-mutesting {{.CLI_ARGS}} + # Benchmark workflow: + # 1. task bench-baseline (generates testdata/benchmarks/baseline.txt, commit this) + # 2. make changes + # 3. task bench-compare (compares current vs committed baseline via benchstat) + # Quick runs: task bench (count=3) or task bench-db (database only) + bench: + desc: Run all benchmarks + cmds: + - go test -run='^$' -bench=. -benchmem -count=3 -timeout=30m ./... + + bench-db: + desc: Run database benchmarks only + cmds: + - go test -run='^$' -bench=. -benchmem -count=3 -timeout=20m ./pkg/database/... + + bench-baseline: + desc: Generate benchmark baseline (commit the output) + cmds: + - bash -o pipefail -c 'go test -run="^$$" -bench=. -benchmem -count=6 -timeout=45m ./... | grep -E "^(Benchmark|goos:|goarch:|pkg:|cpu:)" | tee testdata/benchmarks/baseline.txt' + + bench-compare: + desc: Compare current benchmarks against baseline (requires benchstat, install with go install golang.org/x/perf/cmd/benchstat@latest) + cmds: + - bash -o pipefail -c 'go test -run="^$$" -bench=. -benchmem -count=6 -timeout=45m ./... | grep -E "^(Benchmark|goos:|goarch:|pkg:|cpu:)" > /tmp/zaparoo-bench-current.txt' + - benchstat testdata/benchmarks/baseline.txt /tmp/zaparoo-bench-current.txt + get-logs: desc: Download and decode logs from Zaparoo API vars: diff --git a/docs/optimization-targets.md b/docs/optimization-targets.md new file mode 100644 index 00000000..8bb9c22f --- /dev/null +++ b/docs/optimization-targets.md @@ -0,0 +1,79 @@ +# Optimization Targets + +Measurable performance targets for zaparoo-core. Background agents should use these as benchmarks for optimization work. + +## Critical Path: Token Scan to Launch + +Target: < 100ms from NFC scan to launcher invocation (software path, excluding launcher startup) + +Based on human perception research (Miller 1968, Nielsen): < 100ms feels instantaneous to the user. Physical token tap adds 100-300ms of perceived latency attributed to the physical action, giving additional forgiveness. + +Components: NDEF parse -> mapping match -> ZapScript parse -> command dispatch -> title resolve -> launch + +## Slug Search Cache - Search + +Target at 500k entries: < 100ms per search query (single word) +Currently: linear scan with bytes.Contains(), O(n) per query +MiSTer baseline at 500k: 281ms — nearly 3x over target. #1 optimization priority. + +Search is on the critical tap path via launch.search ZapScript, so the same 100ms perceptual instant threshold applies. + +## Slug Search Cache - Build + +Target at 500k titles: < 5s cache construction from DB +MiSTer baseline at 500k: 1.96s — well within target. Build is a startup-only operation. + +## Slug Search Cache - Memory + +Target at 500k titles: < 30MB heap + +Production measurement: 129k titles = 5.2MB (42 bytes/entry). Cache indexes titles, not media paths — 239k media files deduplicate to 129k titles (1.85:1 ratio). Extrapolated: 500k titles ≈ 20MB. To reach 500k titles requires ~925k media files. + +Note: the B/op metric in benchmarks includes build-time temporaries (~51MB at 500k) and overstates retained size. The `MB` custom metric measures retained cache size via `cache.Size()`. + +## Fuzzy Matching + +Target: < 100ms per query (same perceptual instant threshold — fuzzy is on the tap path as a fallback) +Currently: O(n*m) Jaro-Winkler, length pre-filter eliminates ~70-80% + +Best case (match found) at production 129k titles: ~44ms — within target. Exceeds 100ms above ~296k titles. +Worst case (no match, full scan) at production 129k titles: ~331ms — needs algorithmic improvement (inverted index or candidate limit) to hit 100ms. + +## Media Indexing + +Target: Full re-index of 239k media in < 15 minutes on MiSTer (real-world, including filesystem walk and overhead) +Benchmark target: < 21s per 10k in IndexingPipeline (MiSTer). Current: 32s per 10k — needs 1.5x speedup. +Components: fastwalk -> filename parsing -> slugification -> tag extraction -> DB batch insert + +Real-world overhead is ~1.8x the benchmark pipeline (filesystem walking, SD card I/O, WAL checkpointing, cache rebuild). + +## Memory Budget (Total) + +Target idle RSS: < 50MB (MiSTer, 129k titles / 239k media) +Current idle RSS: 101MB — 21% of MiSTer's 492MB RAM. Slug cache is only 5MB; the remaining ~96MB is Go runtime, SQLite page cache, and unreleased post-indexing memory (RSS = peak RSS). + +Optimization levers: GOGC/GOMEMLIMIT tuning, SQLite page cache sizing, Go memory scavenger behavior, heap profiling at idle. + +## Title Resolution Cache + +The resolution cache (`SlugResolutionCache`) only stores the `mediaDBID` and strategy name — not the full result object. A "cache hit" still executes 2 heavy DB queries via `GetMediaByDBID` (a 3-table join + a UNION with 6 table joins) to reconstruct the result. MiSTer baseline shows 12ms per cache hit (190x slower than x86) because DB reads dominate. + +Optimization opportunity: cache the full result object to make cache hits a pure memory lookup (~41x ratio instead of ~190x). + +## x86-to-MiSTer Multiplier Bands + +Derived from baseline comparison across 70+ benchmarks. These enable CI prediction of MiSTer performance from x86 benchmark runs. + +MiSTer hardware: DE10-Nano, ARM Cortex-A9 ~800MHz, 492MB RAM, SD card storage. Effectively single-core — the second core runs the FPGA main process at 100%. + +| Band | Ratio | What it covers | Spread | +|------|-------|----------------|--------| +| Pure CPU | 41x | Text processing, parsing, slugification | 33-65x | +| Search/match | 35x | Slug search, fuzzy matching, Jaro-Winkler | 24-43x | +| DB reads | 138x | Cache build from DB, query-heavy paths | 132-145x | +| DB writes (in-transaction) | 240x | Batch inserts, transaction cycles, flushes | 183-274x | +| DB writes (with commits/fsync) | 1220x | Full indexing pipeline with fsync to SD card | 1220-1222x | + +To derive CI thresholds from MiSTer targets: divide the MiSTer target by the appropriate multiplier. For example, a 100ms MiSTer search target implies a 2.9ms x86 CI threshold (100ms / 35x). + +Concurrent benchmarks are not CI-predictable due to core count differences (MiSTer: 1 core, x86: 16 threads). diff --git a/docs/prompts/dependency-audit.md b/docs/prompts/dependency-audit.md new file mode 100644 index 00000000..96be4774 --- /dev/null +++ b/docs/prompts/dependency-audit.md @@ -0,0 +1,17 @@ +Audit all direct dependencies for security, freshness, and maintenance status. + +1. List all direct dependencies from go.mod +2. For each dependency, check: + - Latest available version vs current version + - Last commit date on the main repository + - Any known CVEs not yet in the Go vulnerability database + - Whether the module has been retracted or deprecated +3. Flag dependencies with: + - No commits in 2+ years (potentially abandoned) + - Major version behind latest + - Known security advisories +4. Run `task vulncheck` and report any findings +5. Check `go mod tidy` for drift: `go mod tidy && git diff --exit-code go.mod go.sum` +6. Review indirect dependencies for any that should be pinned directly + +Report findings as a table: dependency, current version, latest version, last activity, status (ok/warn/critical). diff --git a/docs/prompts/optimization-scan.md b/docs/prompts/optimization-scan.md new file mode 100644 index 00000000..01caf877 --- /dev/null +++ b/docs/prompts/optimization-scan.md @@ -0,0 +1,107 @@ +Autonomous optimization loop for zaparoo-core. Read targets, pick the highest-priority gap, make a single change, measure, and keep or discard based on evidence. + +## Setup + +1. Read `docs/optimization-targets.md` to understand current targets, baselines, and x86-to-MiSTer multiplier bands +2. Read `CLAUDE.md` for project rules and background agent constraints +3. Run `task bench-compare` to see current performance vs baseline +4. Identify which optimization target has the largest gap between current and target + +## Priority Order + +If multiple targets are behind, work on the highest priority first: + +1. **Slug search** — 3x over target at 500k. See `docs/prompts/optimize-slug-search.md` +2. **Fuzzy matching** — worst case 3x over target. See `docs/prompts/optimize-fuzzy-matching.md` +3. **Media indexing** — 1.5x over target. See `docs/prompts/optimize-indexing.md` +4. **Memory** — 2x over target. See `docs/prompts/optimize-memory.md` + +Read the target-specific prompt for the area you're optimizing. It contains the exact files, functions, benchmarks, and constraints. + +## Experiment Loop + +For each optimization attempt: + +### 1. Read the Code +Read the specific source files referenced in the target prompt. Understand the current implementation before proposing changes. + +### 2. Propose a Single Change +One optimization per experiment. Keep changes small and focused. Don't combine multiple ideas — test them individually so you can attribute improvements accurately. + +### 3. Measure + +Run the specific benchmarks for the component being optimized: + +```bash +go test -run='^$' -bench='BenchmarkAffected' -benchmem -count=6 -timeout=30m ./pkg/specific/ \ + | grep -E '^(Benchmark|goos:|goarch:|pkg:|cpu:)' > /tmp/bench-current.txt +``` + +Compare against baseline: + +```bash +benchstat testdata/benchmarks/baseline.txt /tmp/bench-current.txt +``` + +### 4. Decide: Keep or Discard + +**Keep if ALL of**: +- Improvement >= 10% in the target metric (ns/op for latency, B/op for memory) +- Statistical significance: p < 0.05 in benchstat output +- No regressions > 5% in other benchmarks for the same package + +**Discard if ANY of**: +- Improvement < 10% +- p >= 0.05 (not statistically significant) +- Causes regressions in other benchmarks +- Increases code complexity without proportional gain + +If discarding: revert the change (`git checkout -- .`) and try a different approach. + +### 5. Verify + +If keeping the change: +- Run `task lint-fix` — must pass clean +- Run `task test` — all tests must pass +- Run the full package benchmarks (not just the targeted one) to check for regressions + +### 6. Create PR + +Create a PR with: +- Title: `perf(): ` +- Body must include: + - The benchstat comparison output (before/after) + - Predicted MiSTer impact using the appropriate multiplier band from `docs/optimization-targets.md` + - Which optimization target this addresses and the remaining gap + +## Scope Constraints + +- Only modify source files in the component being optimized +- Never change benchmark code — benchmarks measure, they don't get optimized +- Never change test assertions to make tests pass — fix the code, not the tests +- Never add `nolint` directives without justification +- One optimization per commit, one concern per PR +- Always run in a worktree or branch — never modify main directly + +## MiSTer Performance Prediction + +After measuring x86 improvement, predict MiSTer impact using the multiplier bands: + +| Band | Ratio | Applies to | +|------|-------|------------| +| Pure CPU | 41x | Text processing, parsing, slugification | +| Search/match | 35x | Slug search, fuzzy matching | +| DB reads | 138x | Cache build, query-heavy paths | +| DB writes (in-transaction) | 240x | Batch inserts, transaction cycles | +| DB writes (with fsync) | 1220x | Full indexing pipeline on SD card | + +Example: If x86 slug search improves from 8ms to 2ms, predicted MiSTer improvement is 280ms to 70ms (using 35x search band). + +Concurrent benchmarks are NOT predictable across platforms due to core count differences (MiSTer: 1 core, x86: 16 threads). + +## Reference + +- Optimization targets and baselines: `docs/optimization-targets.md` +- x86 baseline: `testdata/benchmarks/baseline.txt` +- MiSTer baseline: `testdata/benchmarks/baseline-mister.txt` +- Target-specific prompts: `docs/prompts/optimize-*.md` diff --git a/docs/prompts/optimize-fuzzy-matching.md b/docs/prompts/optimize-fuzzy-matching.md new file mode 100644 index 00000000..9fb5c02f --- /dev/null +++ b/docs/prompts/optimize-fuzzy-matching.md @@ -0,0 +1,64 @@ +Optimize fuzzy matching performance. Priority #2 — worst-case full scan exceeds target. + +## Target + +MiSTer target: < 100ms per query (on the tap path as search fallback) +MiSTer baseline (best case, match found): ~44ms at 129k titles — within target +MiSTer baseline (worst case, no match): ~331ms at 129k titles — 3x over target +x86 CI threshold: < 2.9ms (100ms / 35x search multiplier) +Gap: Worst case is ~3x over target. Exceeds 100ms above ~296k titles even in best case. + +## The Problem + +`FindFuzzyMatches()` in `pkg/database/matcher/fuzzy.go` compares every candidate against the query using Jaro-Winkler similarity (via `edlib`). The algorithm is O(n*m) where n is the number of candidates and m is the string comparison cost. + +A length pre-filter eliminates ~70-80% of candidates by rejecting entries whose slug length differs too much from the query. But the remaining 20-30% still get full Jaro-Winkler comparison. When no match is found (worst case), every non-filtered candidate is compared and all scores fall below threshold — full scan with no early exit. + +## Key Files + +| File | What | +|------|------| +| `pkg/database/matcher/fuzzy.go` | `FindFuzzyMatches()`, Jaro-Winkler scoring, length pre-filter | +| `pkg/database/matcher/fuzzy_test.go` | Fuzzy matching benchmarks and tests | +| `pkg/database/mediadb/slug_metadata.go` | `SlugMetadata` struct — precomputed SlugLength, SlugWordCount, TokenSignature | +| `pkg/database/mediadb/slug_search_cache.go` | Cache that provides the candidate list | + +## What to Optimize + +The `FindFuzzyMatches()` function. The goal is to reduce the number of Jaro-Winkler comparisons in the worst case (no match found) without sacrificing match quality. + +## Constraints + +- Match quality must not degrade — if the current implementation finds a match, the optimized version must find the same match (or a better one) +- The function is called as a fallback when exact/prefix search fails, so it's on the tap path +- The `SlugMetadata` struct already stores precomputed fields (SlugLength, SlugWordCount, TokenSignature) — use these for filtering +- Must work with the existing `SlugSearchCache` data structures + +## Algorithmic Ideas + +- **Candidate limiting**: Stop after scanning the first K candidates that pass the length filter. If no match found in K candidates, return empty. Trades recall for speed — acceptable if K is large enough (e.g., 10k candidates) +- **Token signature pre-filter**: `TokenSignature` is a sorted list of word tokens from the slug. Check token overlap before running Jaro-Winkler — if no tokens match, skip the expensive comparison +- **BK-tree**: Build a tree indexed by edit distance. Query traverses only nodes within the distance threshold. O(log n) per query instead of O(n) +- **Inverted token index**: Map tokens to candidate indices. Query tokens look up posting lists, union them, and only run Jaro-Winkler on candidates that share at least one token +- **Word-count bucketing**: Group candidates by SlugWordCount. Only compare candidates with similar word count to the query +- **Early termination**: If a high-confidence match is found (score > 0.95), stop searching + +## Benchmarks + +```bash +# Primary benchmarks +go test -run='^$' -bench='BenchmarkFindFuzzyMatches' -benchmem -count=6 ./pkg/database/matcher/ + +# Full comparison +go test -run='^$' -bench='Benchmark' -benchmem -count=6 ./pkg/database/matcher/ \ + | grep -E '^(Benchmark|goos:|goarch:|pkg:|cpu:)' > /tmp/bench-current.txt +benchstat testdata/benchmarks/baseline.txt /tmp/bench-current.txt +``` + +## Success Criteria + +- Worst case (no match) at 129k titles: < 2.9ms on x86 (currently ~9.5ms) +- Best case (match found) must not regress +- Match quality unchanged — same results for same inputs +- All existing tests pass +- `task lint-fix` passes clean diff --git a/docs/prompts/optimize-indexing.md b/docs/prompts/optimize-indexing.md new file mode 100644 index 00000000..96368597 --- /dev/null +++ b/docs/prompts/optimize-indexing.md @@ -0,0 +1,101 @@ +Optimize the media indexing pipeline. Priority #3 — 1.5x over target. + +## Target + +MiSTer target: < 21s per 10k files in IndexingPipeline benchmark +MiSTer baseline: 32s per 10k files +Real-world target: Full 239k re-index in < 15 minutes (currently ~22-24 min) +Gap: 1.5x over benchmark target + +Real-world overhead is ~1.8x the benchmark pipeline (filesystem walking, SD card I/O, WAL checkpointing, cache rebuild). + +## The Problem + +The indexing pipeline (`NewNamesIndex()` in `pkg/database/mediascanner/mediascanner.go`) processes files through multiple stages: fastwalk, path fragmentation, slugification, tag extraction, and database inserts with 10k-file transaction batches. On MiSTer (ARM Cortex-A9, SD card storage), DB writes with fsync dominate. + +## Pipeline Stages + +Per file, inside `AddMediaPath()` at `pkg/database/mediascanner/indexing_pipeline.go:88-290`: + +1. **GetPathFragments()** (indexing_pipeline.go:787-866) — Path normalization, `tags.ParseTitleFromFilename()`, `slugs.Slugify()`, `tags.ParseFilenameToCanonicalTags()` (~20 compiled regexes) +2. **System lookup/insert** — Map lookup in `ss.SystemIDs`, DB insert if missing +3. **Title lookup/insert** — Map lookup via `database.TitleKey()`, `mediadb.GenerateSlugWithMetadata()` for fuzzy prefilter, DB insert with metadata +4. **Media + tag insertion** — Media record insert, extension tag, filename tag parsing via `ParseFilenameToCanonicalTags()`, MediaTag associations + +Every 10k files: `CommitTransaction()` + `FlushScanStateMaps()` (clears TitleIDs/MediaIDs maps). + +## Key Files + +| File | What | Multiplier Band | +|------|------|-----------------| +| `pkg/database/mediascanner/mediascanner.go` | `NewNamesIndex()` orchestrator, transaction batching, fastwalk | Mixed | +| `pkg/database/mediascanner/indexing_pipeline.go` | `AddMediaPath()`, `GetPathFragments()`, `FlushScanStateMaps()`, `SeedCanonicalTags()` | DB writes: 240x | +| `pkg/database/tags/filename_parser.go` | `ParseFilenameToCanonicalTags()`, ~20 compiled regex patterns | CPU: 41x | +| `pkg/database/slugs/slugify.go` | `Slugify()`, 9-stage Unicode normalization pipeline | CPU: 41x | +| `pkg/database/slugs/normalization.go` | Unicode normalization stages | CPU: 41x | +| `pkg/database/slugs/media_parsing.go` | Media-type-specific parsing dispatchers | CPU: 41x | +| `pkg/database/mediascanner/mediascanner_bench_test.go` | All indexing benchmarks | - | + +## Key Hotspots + +1. **DB transaction commits + fsync** (1220x on MiSTer SD card) — Each 10k-file batch triggers a commit with fsync. This is the dominant cost on MiSTer. +2. **Slugification** (41x CPU multiplier) — 9-stage Unicode pipeline per title: width normalization, NFKD decomposition, article stripping, character filtering. Called once per unique title. +3. **Regex tag parsing** (41x CPU multiplier) — 20 compiled patterns per filename in `ParseFilenameToCanonicalTags()`. Called once per file. +4. **Map operations** — `FlushScanStateMaps()` clears TitleIDs/MediaIDs every 10k files. Map clear is O(n) in Go. +5. **GenerateSlugWithMetadata()** — Called per unique title during insert. Generates SlugLength, SlugWordCount, TokenSignature, SecondarySlug. + +## Constraints + +- **Do NOT change**: Resume/selective indexing logic, `maxSystemsPerTransaction` limit, `PopulateScanState*` variants, `SeedCanonicalTags()` — these are correctness-critical +- Transaction batching at 10k files is a tuning parameter — can be adjusted +- The pipeline must remain single-threaded for DB writes (SQLite limitation with `SetMaxOpenConns(1)`) +- CPU-bound stages (parsing, slugification) could potentially be parallelized or pipelined ahead of DB writes + +## x86 Benchmark Targets + +The x86 benchmarks use in-memory SQLite (no fsync), so the 1220x "DB writes with fsync" multiplier doesn't apply. Use the appropriate multiplier: + +| Benchmark | What it measures | Multiplier | x86 Target | +|-----------|-----------------|------------|------------| +| `BenchmarkAddMediaPath_MockDB` | CPU pipeline only (no DB) | 41x (CPU) | ~512ms/10k | +| `BenchmarkAddMediaPath_RealDB` | Pipeline + in-memory SQLite | 240x (DB writes in-txn) | ~88ms/10k | +| `BenchmarkIndexingPipeline_EndToEnd` | Full pipeline with commits | 240x (DB writes in-txn) | ~88ms/10k | +| `BenchmarkGetPathFragments_Batch` | Parsing + slugification only | 41x (CPU) | ~512ms/10k | +| `BenchmarkFlushScanStateMaps` | Map clearing | 41x (CPU) | Based on entry count | + +## Optimization Ideas + +- **Increase batch size**: Larger batches mean fewer fsync operations. Test 20k or 50k file batches — fewer commits = less SD card I/O +- **Pipeline parsing ahead of DB writes**: `GetPathFragments()` is CPU-bound and independent of DB state. Could process filenames in a goroutine pool, feeding results to the single DB writer +- **Memoize slugification**: Identical titles generate identical slugs. If multiple media files map to the same title, cache the slug result +- **Reduce regex passes**: 20 patterns per filename may have redundant matches. Profile which patterns actually match and consider short-circuit ordering +- **Batch SQL statements**: Prepare INSERT statements once per transaction batch instead of per-file +- **Optimize GenerateSlugWithMetadata()**: Called per unique title — profile whether TokenSignature generation (sort.Strings) is a hotspot + +## Benchmarks + +```bash +# All indexing benchmarks +go test -run='^$' -bench='Benchmark(AddMediaPath|IndexingPipeline|GetPathFragments_Batch|FlushScanStateMaps)' \ + -benchmem -count=6 -timeout=30m ./pkg/database/mediascanner/ + +# Slugification (called during indexing) +go test -run='^$' -bench='BenchmarkSlugify' -benchmem -count=6 ./pkg/database/slugs/ + +# Tag parsing (called during indexing) +go test -run='^$' -bench='Benchmark' -benchmem -count=6 ./pkg/database/tags/ + +# Full comparison +go test -run='^$' -bench='Benchmark' -benchmem -count=6 -timeout=30m ./pkg/database/mediascanner/ \ + | grep -E '^(Benchmark|goos:|goarch:|pkg:|cpu:)' > /tmp/bench-current.txt +benchstat testdata/benchmarks/baseline.txt /tmp/bench-current.txt +``` + +## Success Criteria + +- `BenchmarkIndexingPipeline_EndToEnd/10k_1sys`: measurable improvement (> 10%) on x86 +- `BenchmarkAddMediaPath_RealDB/10k`: measurable improvement (> 10%) on x86 +- No regressions in `BenchmarkGetPathFragments` (per-file parsing) +- All existing tests pass +- `task lint-fix` passes clean +- Predicted MiSTer improvement brings 10k pipeline closer to 21s target diff --git a/docs/prompts/optimize-memory.md b/docs/prompts/optimize-memory.md new file mode 100644 index 00000000..b6aee03e --- /dev/null +++ b/docs/prompts/optimize-memory.md @@ -0,0 +1,105 @@ +Investigate and reduce idle memory usage. Priority #4 — investigation-first. + +## Target + +MiSTer target: < 50MB idle RSS (492MB total RAM, zaparoo shares with FPGA core + Linux) +MiSTer current: 101MB idle RSS (21% of total RAM) +Gap: 2x over target + +## The Problem + +The slug search cache is only 5.2MB (129k titles, 42 bytes/entry). The remaining ~96MB is: +- Go runtime overhead (GC metadata, goroutine stacks, runtime structures) +- SQLite page cache (default sizing may be too generous for embedded use) +- Unreleased post-indexing memory (RSS = peak RSS; Go's memory scavenger may not return pages to OS) +- Possible heap fragmentation from indexing's allocate-heavy pattern + +This is primarily an **investigation task**, not a code optimization. The agent should produce a report with findings and recommendations before making code changes. + +## Investigation Steps + +### 1. Heap Profile at Idle + +On the MiSTer test device (10.0.0.107), after indexing completes and the system is idle: + +- Check if pprof is available (it may not be in production builds) +- If not available via pprof, use `runtime.ReadMemStats()` to report: HeapAlloc, HeapSys, HeapIdle, HeapReleased, StackSys, MSpanSys, MCacheSys, GCSys +- Compare HeapAlloc (live objects) vs HeapSys (total heap reserved) vs RSS — the gap between HeapAlloc and RSS is the optimization opportunity + +### 2. GOGC / GOMEMLIMIT Tuning + +Go 1.19+ supports `GOMEMLIMIT` which caps total Go memory. Combined with GOGC: + +- `GOGC=100` (default): GC triggers when heap doubles. After indexing allocates ~50MB of temporaries, the GC target stays high. +- `GOMEMLIMIT=40MiB`: Hard cap that forces more aggressive GC. May increase CPU cost but reduces RSS. +- Test combinations: `GOGC=50 GOMEMLIMIT=40MiB`, `GOGC=100 GOMEMLIMIT=50MiB`, etc. +- Measure: RSS at idle, GC pause times, indexing throughput impact + +### 3. SQLite Page Cache + +Check the SQLite page cache configuration in the MediaDB and UserDB: + +- `PRAGMA cache_size` — default is -2000 (2MB). On MiSTer this may be too large. +- `PRAGMA mmap_size` — if enabled, memory-mapped I/O adds to RSS +- Consider reducing cache_size for MiSTer builds or making it configurable + +### 4. Memory Scavenger Behavior + +Go returns memory to the OS via the scavenger, but RSS may not drop if: +- Pages are still mapped but not released (`HeapIdle - HeapReleased`) +- The scavenger hasn't run recently (default interval is 2.5 minutes) +- `debug.FreeOSMemory()` after indexing could force immediate release + +### 5. Post-Indexing Cleanup + +The indexing pipeline allocates large temporary structures (`ScanState` maps, filename buffers). After indexing: +- Are these references dropped so GC can collect them? +- Is `FlushScanStateMaps()` called at the end? +- Could explicit `runtime.GC()` + `debug.FreeOSMemory()` after indexing help? + +## Key Files + +| File | What | +|------|------| +| `pkg/database/mediadb/mediadb.go` | MediaDB connection setup, SQLite pragmas | +| `pkg/database/userdb/userdb.go` | UserDB connection setup | +| `pkg/database/mediascanner/mediascanner.go` | `NewNamesIndex()` — allocates ScanState, drives indexing | +| `pkg/database/mediascanner/indexing_pipeline.go` | `FlushScanStateMaps()` — clears temporary maps | +| `pkg/database/mediadb/slug_search_cache.go` | Cache struct and `Size()` method | +| `cmd/mister/main.go` | MiSTer entry point — where GOGC/GOMEMLIMIT could be set | + +## Benchmarks + +Memory benchmarks measure cache size, not total RSS: + +```bash +# Cache memory footprint +go test -run='^$' -bench='BenchmarkSlugSearchCacheMemory' -benchmem ./pkg/database/mediadb/ + +# Peak memory during indexing +go test -run='^$' -bench='BenchmarkGetPathFragments_PeakMemory' -benchmem ./pkg/database/mediascanner/ +``` + +For RSS measurement, use the MiSTer device: +```bash +# After zaparoo is running and idle +ssh root@10.0.0.107 'ps aux | grep zaparoo | grep -v grep' +# Or use task get-logs to check memory stats in application logs +task get-logs -- 10.0.0.107:7497 +``` + +## Expected Output + +A report containing: +1. Heap profile breakdown (what's using the 96MB that isn't slug cache) +2. GOGC/GOMEMLIMIT recommendations with measured impact +3. SQLite page cache current size and recommendation +4. Whether post-indexing cleanup is effective +5. Specific code changes recommended (if any), with predicted RSS reduction + +## Success Criteria + +- Identify where the 96MB gap comes from (heap profile breakdown) +- Propose concrete tuning parameters (GOGC, GOMEMLIMIT, cache_size) +- If code changes proposed: < 50MB idle RSS on MiSTer after applying them +- No performance regressions in indexing or search benchmarks diff --git a/docs/prompts/optimize-slug-search.md b/docs/prompts/optimize-slug-search.md new file mode 100644 index 00000000..248eea1e --- /dev/null +++ b/docs/prompts/optimize-slug-search.md @@ -0,0 +1,75 @@ +Optimize slug search cache substring matching. Priority #1 — furthest from target. + +## Target + +MiSTer target: < 100ms per search query at 500k entries +MiSTer baseline: 281ms (single-word query at 500k) +x86 CI threshold: < 2.9ms (100ms / 35x search multiplier) +Gap: ~3x over target + +## The Problem + +`SlugSearchCache.Search()` in `pkg/database/mediadb/slug_search_cache.go:163-207` does a linear scan over ALL entries using `bytes.Contains()`. Every search query touches every entry — O(n) per query regardless of the search term. + +The cache stores slugs as a flat `[]byte` blob (`slugData`) with offset arrays. Each entry also has an optional secondary slug (`secSlugData`). The search uses an AND-of-ORs pattern: `variantGroups` is a list of groups where each group contains byte variants for one query word. All groups must match (AND), and within a group any variant can match (OR). + +The system filter (`systemDBIDs`) provides early exit for non-matching systems, but the slug comparison is still the bottleneck. + +## Key Files + +| File | What | +|------|------| +| `pkg/database/mediadb/slug_search_cache.go` | Cache struct, `Search()`, `buildSlugSearchCache()`, entry accessors | +| `pkg/database/mediadb/slug_search_cache_test.go` | All search benchmarks and tests | +| `pkg/database/mediadb/slug_metadata.go` | `GenerateSlugWithMetadata()` — builds metadata during indexing | +| `pkg/database/slugs/slugify.go` | How slugs are generated (determines what's in the cache) | + +## What to Optimize + +The `Search()` method (lines 163-207). The goal is to replace the linear `bytes.Contains()` scan with a sub-linear data structure. + +**The cache build (`buildSlugSearchCache`) can be slower** if it enables faster search. The cache is built once at startup and searched many times. Trading build time and memory for search speed is the right tradeoff. + +## Constraints + +- Cache is read-only after build — no locks needed for concurrent reads +- `Search()` must return the same results as the current implementation (all matching `titleDBIDs`) +- The AND-of-ORs `variantGroups` pattern must be preserved — callers depend on it +- System filter must still work +- `ExactSlugMatch`, `PrefixSlugMatch`, `ExactSlugMatchAny`, `RandomEntry` are separate methods — don't break them. They're already fast enough. +- Memory budget: cache at 500k is currently ~20MB. An index can use additional memory but should stay reasonable (< 50MB total for 500k entries including the index) + +## Algorithmic Ideas + +- **Inverted index**: Map each unique word (or n-gram) to a list of entry indices. Query words look up their posting lists and intersect them. Turns AND-of-ORs into set intersections. +- **Trigram index**: Map each 3-character sequence to entry indices. Substring search becomes: find trigrams in query, intersect posting lists, verify candidates with `bytes.Contains()`. Well-suited for substring matching. +- **Suffix array**: Sorted array of all suffixes with binary search. Good for substring queries but complex to build for multiple entries. +- **Hybrid**: Use an inverted word index for whole-word queries (most common case) with trigram fallback for partial matches. + +The current `variantGroups` structure suggests callers already split queries into words — an inverted word index may be the most natural fit. + +## Benchmarks + +```bash +# Primary benchmark — this is what needs to improve +go test -run='^$' -bench='BenchmarkSlugSearchCacheSearch' -benchmem -count=6 ./pkg/database/mediadb/ + +# Check build time doesn't regress catastrophically +go test -run='^$' -bench='BenchmarkSlugSearchCacheBuild' -benchmem -count=6 ./pkg/database/mediadb/ + +# Check memory footprint +go test -run='^$' -bench='BenchmarkSlugSearchCacheMemory' -benchmem ./pkg/database/mediadb/ + +# Full comparison +go test -run='^$' -bench='BenchmarkSlugSearchCache' -benchmem -count=6 ./pkg/database/mediadb/ \ + | grep -E '^(Benchmark|goos:|goarch:|pkg:|cpu:)' > /tmp/bench-current.txt +benchstat testdata/benchmarks/baseline.txt /tmp/bench-current.txt +``` + +## Success Criteria + +- Search at 500k: < 2.9ms on x86 (currently ~8ms) +- No regression in build time > 50% (build is a startup-only cost, some increase is acceptable) +- Memory increase < 2x (< 50MB total at 500k) +- All existing tests pass +- `task lint-fix` passes clean diff --git a/docs/prompts/security-review.md b/docs/prompts/security-review.md new file mode 100644 index 00000000..8ab781a6 --- /dev/null +++ b/docs/prompts/security-review.md @@ -0,0 +1,26 @@ +Perform a security audit of zaparoo-core focusing on areas static analysis misses. + +1. **Execute command allowlist audit**: Trace all paths to `exec.CommandContext` in `pkg/zapscript/utils.go`. Verify `IsExecuteAllowed()` gate is always hit. Confirm no bypass paths exist. + +2. **Auth middleware coverage**: Trace the chi router setup in `pkg/api/server.go`. Verify no API endpoints skip authentication. Check WebSocket auth handler covers upgrade path. + +3. **WebSocket message size limits**: Verify melody config in `pkg/api/server.go` prevents memory exhaustion from oversized messages. Check MaxMessageSize and WriteBufferSize settings. + +4. **NDEF parser boundary analysis**: Run extended fuzzing on the 5 fuzz functions in `pkg/readers/shared/ndef/parser_fuzz_test.go`: + ``` + go test -run "^$" -fuzz=FuzzParseToText -fuzztime=5m ./pkg/readers/shared/ndef/ + go test -run "^$" -fuzz=FuzzValidateNDEFMessage -fuzztime=5m ./pkg/readers/shared/ndef/ + go test -run "^$" -fuzz=FuzzExtractTLVPayload -fuzztime=5m ./pkg/readers/shared/ndef/ + go test -run "^$" -fuzz=FuzzParseTextPayload -fuzztime=5m ./pkg/readers/shared/ndef/ + go test -run "^$" -fuzz=FuzzParseURIPayload -fuzztime=5m ./pkg/readers/shared/ndef/ + ``` + +5. **nolint:gosec directive audit**: Find all `nolint:gosec` directives and verify each is still justified. Flag any that suppress warnings about user-controlled input. + +6. **Mapping regex safety**: Verify `pkg/database/userdb/mappings.go` only uses Go's `regexp` package (linear-time, safe from ReDoS). Confirm no switch to a different regex engine. + +7. **Config path handling**: Check TOML config file path handling in `pkg/config/` can't be used to escape expected directories via path traversal. + +8. Run `task vulncheck` for known CVEs. + +Report each finding with: severity, affected file(s), evidence, and recommended fix. diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 21fed5c9..4b470fa9 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1118,3 +1118,25 @@ func TestSave_OmitsGamepadEnabledWhenNil(t *testing.T) { assert.NotContains(t, content, "gamepad_enabled", "gamepad_enabled should not be in config when nil") assert.NotContains(t, content, "[input]", "input section should not be in config when all fields nil") } + +func BenchmarkConfig_Read(b *testing.B) { + b.ReportAllocs() + cfg := &Instance{vals: BaseDefaults} + b.ResetTimer() + for b.Loop() { + _ = cfg.IsExecuteAllowed("some-command") + } +} + +func BenchmarkConfig_Load(b *testing.B) { + b.ReportAllocs() + tempDir := b.TempDir() + cfg, err := NewConfig(tempDir, BaseDefaults) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for b.Loop() { + _ = cfg.Load() + } +} diff --git a/pkg/config/main_test.go b/pkg/config/main_test.go new file mode 100644 index 00000000..b108486e --- /dev/null +++ b/pkg/config/main_test.go @@ -0,0 +1,31 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package config + +import ( + "testing" + + "github.com/rs/zerolog" +) + +func TestMain(m *testing.M) { + zerolog.SetGlobalLevel(zerolog.Disabled) + m.Run() +} diff --git a/pkg/database/matcher/fuzzy_test.go b/pkg/database/matcher/fuzzy_test.go index b76b73e2..7207b505 100644 --- a/pkg/database/matcher/fuzzy_test.go +++ b/pkg/database/matcher/fuzzy_test.go @@ -20,8 +20,13 @@ package matcher import ( + "fmt" + "math/rand" + "strings" "testing" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/slugs" + "github.com/hbollon/go-edlib" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -482,3 +487,122 @@ func TestFindFuzzyMatches_MultipleSimilarCandidates(t *testing.T) { t.Logf("Found %d matches", len(matches)) }) } + +// buildSyntheticCandidates generates n deterministic game-title-like slugs +// using a fixed seed for reproducible benchmarks. +func buildSyntheticCandidates(n int) []string { + words := []string{ + "super", "mario", "zelda", "sonic", "metroid", "castlevania", + "mega", "man", "final", "fantasy", "dragon", "quest", "street", + "fighter", "mortal", "kombat", "donkey", "kong", "kirby", "star", + "fox", "fire", "emblem", "pokemon", "contra", "ninja", "gaiden", + } + + //nolint:gosec // Deterministic seed for reproducible benchmarks + rng := rand.New(rand.NewSource(42)) + candidates := make([]string, n) + + for i := range n { + // Build slug from 2-4 random words + wordCount := 2 + rng.Intn(3) + parts := make([]string, wordCount) + for w := range wordCount { + parts[w] = words[rng.Intn(len(words))] + } + // Append unique suffix to avoid duplicates + candidates[i] = fmt.Sprintf("%s-%d", strings.Join(parts, "-"), i) + } + + return candidates +} + +func BenchmarkFindFuzzyMatches_500k(b *testing.B) { + b.ReportAllocs() + candidates := buildSyntheticCandidates(500_000) + query := "supermariobros" + b.ResetTimer() + for b.Loop() { + FindFuzzyMatches(query, candidates, 2, 0.85) + } +} + +func BenchmarkFindFuzzyMatches_1M(b *testing.B) { + b.ReportAllocs() + candidates := buildSyntheticCandidates(1_000_000) + query := "supermariobros" + b.ResetTimer() + for b.Loop() { + FindFuzzyMatches(query, candidates, 2, 0.85) + } +} + +func BenchmarkFindFuzzyMatches_LengthPreFilter(b *testing.B) { + candidates := buildSyntheticCandidates(500_000) + query := "supermariobros" + + b.Run("maxDistance=3", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for b.Loop() { + FindFuzzyMatches(query, candidates, 3, 0.85) + } + }) + + b.Run("maxDistance=10", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for b.Loop() { + FindFuzzyMatches(query, candidates, 10, 0.85) + } + }) +} + +func BenchmarkFindFuzzyMatches_NoMatch_500k(b *testing.B) { + b.ReportAllocs() + candidates := buildSyntheticCandidates(500_000) + query := "zzzzzzzzzznotarealname" + b.ResetTimer() + for b.Loop() { + FindFuzzyMatches(query, candidates, 2, 0.85) + } +} + +func BenchmarkJaroWinkler_Similarity(b *testing.B) { + b.ReportAllocs() + s1 := "supermariobros" + s2 := "supermraiobrso" + b.ResetTimer() + for b.Loop() { + edlib.JaroWinklerSimilarity(s1, s2) + } +} + +func BenchmarkGenerateTokenSignature_500k(b *testing.B) { + b.ReportAllocs() + + words := []string{ + "Super", "Mario", "Zelda", "Sonic", "Metroid", "Castlevania", + "Mega", "Man", "Final", "Fantasy", "Dragon", "Quest", "Street", + "Fighter", "Mortal", "Kombat", "Donkey", "Kong", "Kirby", "Star", + "Fox", "Fire", "Emblem", "Pokemon", "Contra", "Ninja", "Gaiden", + } + + //nolint:gosec // Deterministic seed for reproducible benchmarks + rng := rand.New(rand.NewSource(42)) + titles := make([]string, 500_000) + for i := range 500_000 { + wordCount := 2 + rng.Intn(3) + parts := make([]string, wordCount) + for w := range wordCount { + parts[w] = words[rng.Intn(len(words))] + } + titles[i] = strings.Join(parts, " ") + } + + b.ResetTimer() + for b.Loop() { + for _, title := range titles { + GenerateTokenSignature(slugs.MediaTypeGame, title) + } + } +} diff --git a/pkg/database/matcher/main_test.go b/pkg/database/matcher/main_test.go new file mode 100644 index 00000000..2bd093ee --- /dev/null +++ b/pkg/database/matcher/main_test.go @@ -0,0 +1,31 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package matcher + +import ( + "testing" + + "github.com/rs/zerolog" +) + +func TestMain(m *testing.M) { + zerolog.SetGlobalLevel(zerolog.Disabled) + m.Run() +} diff --git a/pkg/database/mediadb/batch_inserter_bench_test.go b/pkg/database/mediadb/batch_inserter_bench_test.go new file mode 100644 index 00000000..a0284fff --- /dev/null +++ b/pkg/database/mediadb/batch_inserter_bench_test.go @@ -0,0 +1,242 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package mediadb + +import ( + "context" + "database/sql" + "fmt" + "path/filepath" + "testing" + + "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/mocks" + _ "github.com/mattn/go-sqlite3" +) + +// newBenchSQLite creates a temp-file SQLite database with the production schema +// (via SetSQLForTesting which runs real migrations) and returns the raw *sql.DB +// for direct batch inserter testing. +func newBenchSQLite(b *testing.B) (sqlDB *sql.DB, cleanup func()) { + b.Helper() + dbPath := filepath.Join(b.TempDir(), "bench.db") + sqlDB, err := sql.Open("sqlite3", dbPath+"?_foreign_keys=ON") + if err != nil { + b.Fatal(err) + } + + // Use SetSQLForTesting to run real migrations and get the production schema + db := &MediaDB{} + ctx := context.Background() + mockPlatform := mocks.NewMockPlatform() + mockPlatform.On("ID").Return("test-platform") + if err := db.SetSQLForTesting(ctx, sqlDB, mockPlatform); err != nil { + b.Fatal(err) + } + // Close the MediaDB wrapper but keep the raw *sql.DB open for direct use + db.sql = nil // Prevent Close() from closing the underlying connection + + return sqlDB, func() { _ = sqlDB.Close() } +} + +// seedBenchData inserts a system and tag type needed by batch inserter benchmarks. +func seedBenchData(ctx context.Context, b *testing.B, tx *sql.Tx) { + b.Helper() + if _, err := tx.ExecContext(ctx, + "INSERT INTO Systems (DBID, SystemID, Name) VALUES (1, 'nes', 'NES')"); err != nil { + b.Fatal(err) + } + if _, err := tx.ExecContext(ctx, + "INSERT INTO TagTypes (DBID, Type) VALUES (1, 'extension')"); err != nil { + b.Fatal(err) + } +} + +var titleCols = []string{ + "DBID", "SystemDBID", "Slug", "Name", + "SlugLength", "SlugWordCount", "SecondarySlug", +} + +func BenchmarkBatchInserter_Insert(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "10k", n: 10_000}, + {name: "50k", n: 50_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + db, cleanup := newBenchSQLite(b) + ctx := context.Background() + tx, err := db.BeginTx(ctx, nil) + if err != nil { + b.Fatal(err) + } + + // Seed a system and tag type + seedBenchData(ctx, b, tx) + + // Create batch inserters with production dependency graph + biTitles, err := NewBatchInserterWithOptions( + ctx, tx, "MediaTitles", titleCols, 5000, false) + if err != nil { + b.Fatal(err) + } + biMedia, err := NewBatchInserterWithOptions(ctx, tx, "Media", + []string{"DBID", "MediaTitleDBID", "SystemDBID", "Path"}, 5000, false) + if err != nil { + b.Fatal(err) + } + biTags, err := NewBatchInserterWithOptions(ctx, tx, "Tags", + []string{"DBID", "TypeDBID", "Tag"}, 5000, false) + if err != nil { + b.Fatal(err) + } + biMediaTags, err := NewBatchInserterWithOptions(ctx, tx, "MediaTags", + []string{"MediaDBID", "TagDBID"}, 5000, true) + if err != nil { + b.Fatal(err) + } + + biMedia.SetDependencies(biTitles) + biMediaTags.SetDependencies(biMedia, biTags) + + // Insert titles, media, tags, and links + for i := range sz.n { + id := int64(i + 1) + slug := fmt.Sprintf("game-%d", i) + + _ = biTitles.Add(id, int64(1), slug, slug, len(slug), 1, nil) + _ = biMedia.Add(id, id, int64(1), fmt.Sprintf("/roms/nes/%s.nes", slug)) + + if i < 100 { // First 100 unique tags + _ = biTags.Add(id, int64(1), fmt.Sprintf("ext-%d", i)) + } + _ = biMediaTags.Add(id, int64((i%100)+1)) + } + + _ = biTitles.Close() + _ = biMedia.Close() + _ = biTags.Close() + _ = biMediaTags.Close() + _ = tx.Commit() + cleanup() + } + }) + } +} + +func BenchmarkBatchInserter_FlushCost(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "100rows", n: 100}, + {name: "1000rows", n: 1000}, + {name: "5000rows", n: 5000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + db, cleanup := newBenchSQLite(b) + ctx := context.Background() + tx, err := db.BeginTx(ctx, nil) + if err != nil { + b.Fatal(err) + } + + _, err = tx.ExecContext(ctx, + "INSERT INTO Systems (DBID, SystemID, Name) VALUES (1, 'nes', 'NES')") + if err != nil { + b.Fatal(err) + } + + // batch size > n so no auto-flush + bi, err := NewBatchInserterWithOptions( + ctx, tx, "MediaTitles", titleCols, sz.n+1, false) + if err != nil { + b.Fatal(err) + } + + for i := range sz.n { + slug := fmt.Sprintf("game-%d", i) + _ = bi.Add(int64(i+1), int64(1), slug, slug, len(slug), 1, nil) + } + + // Measure: the actual flush + _ = bi.Flush() + _ = tx.Commit() + cleanup() + } + }) + } +} + +func BenchmarkTransactionCycle_CommitCost(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "1k", n: 1_000}, + {name: "5k", n: 5_000}, + {name: "10k", n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + db, cleanup := newBenchSQLite(b) + ctx := context.Background() + tx, err := db.BeginTx(ctx, nil) + if err != nil { + b.Fatal(err) + } + + _, err = tx.ExecContext(ctx, + "INSERT INTO Systems (DBID, SystemID, Name) VALUES (1, 'nes', 'NES')") + if err != nil { + b.Fatal(err) + } + + bi, err := NewBatchInserterWithOptions( + ctx, tx, "MediaTitles", titleCols, 5000, false) + if err != nil { + b.Fatal(err) + } + + for i := range sz.n { + slug := fmt.Sprintf("game-%d", i) + _ = bi.Add(int64(i+1), int64(1), slug, slug, len(slug), 1, nil) + } + + // Measure: flush + commit + _ = bi.Close() + _ = tx.Commit() + cleanup() + } + }) + } +} diff --git a/pkg/database/mediadb/main_test.go b/pkg/database/mediadb/main_test.go new file mode 100644 index 00000000..01611b7f --- /dev/null +++ b/pkg/database/mediadb/main_test.go @@ -0,0 +1,31 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package mediadb + +import ( + "testing" + + "github.com/rs/zerolog" +) + +func TestMain(m *testing.M) { + zerolog.SetGlobalLevel(zerolog.Disabled) + m.Run() +} diff --git a/pkg/database/mediadb/slug_metadata_test.go b/pkg/database/mediadb/slug_metadata_test.go index 389dff82..077e42ee 100644 --- a/pkg/database/mediadb/slug_metadata_test.go +++ b/pkg/database/mediadb/slug_metadata_test.go @@ -26,6 +26,29 @@ import ( "github.com/stretchr/testify/assert" ) +func BenchmarkGenerateSlugWithMetadata(b *testing.B) { + cases := []struct { + name string + input string + }{ + {"Latin_simple", "Super Mario Bros"}, + {"Latin_complex", "The Legend of Zelda: Ocarina of Time (USA) [!]"}, + {"CJK_Japanese", "ゼルダの伝説 時のオカリナ"}, //nolint:gosmopolitan // CJK benchmark + {"CJK_Chinese", "最终幻想七 重制版"}, //nolint:gosmopolitan // CJK benchmark + {"With_secondary", "The Legend of Zelda: Ocarina of Time"}, + {"Long_ROM", "Shin Megami Tensei - Digital Devil Saga 2 - Avatar Tuner (Japan) (Rev 1) (Disc 1 of 2)"}, + } + + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + GenerateSlugWithMetadata(slugs.MediaTypeGame, tc.input) + } + }) + } +} + func TestSlugMetadata(t *testing.T) { t.Parallel() diff --git a/pkg/database/mediadb/slug_search_cache.go b/pkg/database/mediadb/slug_search_cache.go index ce4f138e..25e1fdf2 100644 --- a/pkg/database/mediadb/slug_search_cache.go +++ b/pkg/database/mediadb/slug_search_cache.go @@ -56,6 +56,16 @@ type SlugSearchCache struct { entryCount int } +// Size returns the approximate memory footprint of the cache in bytes. +func (c *SlugSearchCache) Size() int { + if c == nil { + return 0 + } + return len(c.slugData) + len(c.secSlugData) + + len(c.slugOffsets)*4 + len(c.secSlugOffsets)*4 + + len(c.titleDBIDs)*8 + len(c.systemDBIDs)*8 +} + // buildSlugSearchCache reads all slug data from the database into an in-memory cache. func buildSlugSearchCache(ctx context.Context, db *sql.DB) (*SlugSearchCache, error) { // Build system lookup maps @@ -385,12 +395,9 @@ func (db *MediaDB) RebuildSlugSearchCache() error { return fmt.Errorf("failed to build slug search cache: %w", err) } db.slugSearchCache.Store(cache) - sizeBytes := len(cache.slugData) + len(cache.secSlugData) + - len(cache.slugOffsets)*4 + len(cache.secSlugOffsets)*4 + - len(cache.titleDBIDs)*8 + len(cache.systemDBIDs)*8 log.Info(). Int("entries", cache.entryCount). - Str("size", formatBytes(sizeBytes)). + Str("size", formatBytes(cache.Size())). Msg("slug search cache built") return nil } diff --git a/pkg/database/mediadb/slug_search_cache_test.go b/pkg/database/mediadb/slug_search_cache_test.go index 7254de85..a920543a 100644 --- a/pkg/database/mediadb/slug_search_cache_test.go +++ b/pkg/database/mediadb/slug_search_cache_test.go @@ -21,8 +21,8 @@ package mediadb import ( "context" + "database/sql" "fmt" - "runtime" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -395,15 +395,6 @@ func BenchmarkSlugSearchCacheSearch_250k(b *testing.B) { } } -func BenchmarkSlugSearchCacheSearch_1M(b *testing.B) { - cache := buildSyntheticCache(1_000_000) - query := [][][]byte{{[]byte("mario")}, {[]byte("super")}} - b.ResetTimer() - for b.Loop() { - cache.Search(nil, query) - } -} - // --- ExactSlugMatch tests --- func TestSlugSearchCache_ExactSlugMatch(t *testing.T) { @@ -615,6 +606,7 @@ func TestSlugSearchCache_RandomEntry_NilCache(t *testing.T) { } func BenchmarkSlugSearchCacheBuild(b *testing.B) { + b.ReportAllocs() // Benchmark cache struct population speed (excludes SQL) for b.Loop() { buildSyntheticCache(250_000) @@ -622,12 +614,217 @@ func BenchmarkSlugSearchCacheBuild(b *testing.B) { } func BenchmarkSlugSearchCacheMemory(b *testing.B) { - var before, after runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&before) cache := buildSyntheticCache(250_000) - runtime.GC() - runtime.ReadMemStats(&after) - _ = cache - b.ReportMetric(float64(after.HeapAlloc-before.HeapAlloc)/(1024*1024), "MB") + b.ReportMetric(float64(cache.Size())/(1024*1024), "MB") +} + +func BenchmarkSlugSearchCacheSearch_500k(b *testing.B) { + b.ReportAllocs() + cache := buildSyntheticCache(500_000) + query := [][][]byte{{[]byte("mario")}, {[]byte("super")}} + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } +} + +func BenchmarkSlugSearchCacheBuild_500k(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + buildSyntheticCache(500_000) + } +} + +func BenchmarkSlugSearchCacheMemory_500k(b *testing.B) { + cache := buildSyntheticCache(500_000) + b.ReportMetric(float64(cache.Size())/(1024*1024), "MB") +} + +func BenchmarkSlugSearchCacheSearch_QueryComplexity(b *testing.B) { + cache := buildSyntheticCache(500_000) + + b.Run("SingleWord", func(b *testing.B) { + b.ReportAllocs() + query := [][][]byte{{[]byte("mario")}} + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } + }) + + b.Run("TwoWords", func(b *testing.B) { + b.ReportAllocs() + query := [][][]byte{{[]byte("mario")}, {[]byte("super")}} + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } + }) + + b.Run("ThreeWords", func(b *testing.B) { + b.ReportAllocs() + query := [][][]byte{{[]byte("mario")}, {[]byte("super")}, {[]byte("3")}} + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } + }) + + b.Run("CommonSubstring", func(b *testing.B) { + b.ReportAllocs() + query := [][][]byte{{[]byte("the")}} + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } + }) +} + +func BenchmarkSlugSearchCacheSearch_SystemFiltered_500k(b *testing.B) { + cache := buildSyntheticCache(500_000) + query := [][][]byte{{[]byte("mario")}, {[]byte("super")}} + + b.Run("NoFilter", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } + }) + + b.Run("SingleSystem", func(b *testing.B) { + b.ReportAllocs() + systemFilter := []int64{1} + b.ResetTimer() + for b.Loop() { + cache.Search(systemFilter, query) + } + }) + + b.Run("ThreeSystems", func(b *testing.B) { + b.ReportAllocs() + systemFilter := []int64{1, 2, 3} + b.ResetTimer() + for b.Loop() { + cache.Search(systemFilter, query) + } + }) +} + +func BenchmarkSlugSearchCacheSearch_NoMatch_500k(b *testing.B) { + b.ReportAllocs() + cache := buildSyntheticCache(500_000) + query := [][][]byte{{[]byte("zzzznotarealentry")}} + b.ResetTimer() + for b.Loop() { + cache.Search(nil, query) + } +} + +func BenchmarkSlugSearchCacheSearch_Concurrent_500k(b *testing.B) { + b.ReportAllocs() + cache := buildSyntheticCache(500_000) + query := [][][]byte{{[]byte("mario")}, {[]byte("super")}} + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cache.Search(nil, query) + } + }) +} + +// populateBenchTitles inserts systems and titles into a raw *sql.DB for cache +// build benchmarks. Uses batch inserters with the production schema. +func populateBenchTitles(ctx context.Context, b *testing.B, sqlDB *sql.DB, n int) { + b.Helper() + + tx, err := sqlDB.BeginTx(ctx, nil) + if err != nil { + b.Fatal(err) + } + + // Insert 5 systems + systems := []struct { + code string + name string + id int64 + }{ + {id: 1, code: "nes", name: "NES"}, + {id: 2, code: "snes", name: "SNES"}, + {id: 3, code: "genesis", name: "Genesis"}, + {id: 4, code: "psx", name: "PSX"}, + {id: 5, code: "n64", name: "N64"}, + } + for _, sys := range systems { + if _, execErr := tx.ExecContext(ctx, + "INSERT INTO Systems (DBID, SystemID, Name) VALUES (?, ?, ?)", + sys.id, sys.code, sys.name); execErr != nil { + b.Fatal(execErr) + } + } + + // Insert titles via batch inserter + bi, err := NewBatchInserterWithOptions( + ctx, tx, "MediaTitles", titleCols, 5000, false) + if err != nil { + b.Fatal(err) + } + + words := []string{ + "super", "mario", "zelda", "sonic", "metroid", "castlevania", + "mega", "man", "final", "fantasy", "dragon", "quest", + "street", "fighter", "mortal", "kombat", "donkey", "kong", + } + + for i := range n { + id := int64(i + 1) + sysDBID := int64((i % 5) + 1) + // Build slug from word combos + unique suffix + w1 := words[i%len(words)] + w2 := words[(i*7)%len(words)] + slug := fmt.Sprintf("%s-%s-%d", w1, w2, i) + wordCount := 3 + var secSlug any + if i%5 == 0 { + secSlug = fmt.Sprintf("%s-%d", w1, i) + } + if err := bi.Add(id, sysDBID, slug, slug, len(slug), wordCount, secSlug); err != nil { + b.Fatal(err) + } + } + + if err := bi.Close(); err != nil { + b.Fatal(err) + } + if err := tx.Commit(); err != nil { + b.Fatal(err) + } +} + +func BenchmarkSlugSearchCacheBuild_FromDB(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "10k", n: 10_000}, + {name: "50k", n: 50_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + sqlDB, cleanup := newBenchSQLite(b) + ctx := context.Background() + populateBenchTitles(ctx, b, sqlDB, sz.n) + b.ResetTimer() + for b.Loop() { + _, err := buildSlugSearchCache(ctx, sqlDB) + if err != nil { + b.Fatal(err) + } + } + b.StopTimer() + cleanup() + }) + } } diff --git a/pkg/database/mediascanner/main_test.go b/pkg/database/mediascanner/main_test.go new file mode 100644 index 00000000..e831e981 --- /dev/null +++ b/pkg/database/mediascanner/main_test.go @@ -0,0 +1,31 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package mediascanner + +import ( + "testing" + + "github.com/rs/zerolog" +) + +func TestMain(m *testing.M) { + zerolog.SetGlobalLevel(zerolog.Disabled) + m.Run() +} diff --git a/pkg/database/mediascanner/mediascanner_bench_test.go b/pkg/database/mediascanner/mediascanner_bench_test.go new file mode 100644 index 00000000..2f4c1c1b --- /dev/null +++ b/pkg/database/mediascanner/mediascanner_bench_test.go @@ -0,0 +1,422 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package mediascanner + +import ( + "fmt" + "math/rand" + "runtime" + "testing" + + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/helpers" + "github.com/stretchr/testify/mock" +) + +func BenchmarkGetPathFragments(b *testing.B) { + cases := []struct { + name string + path string + }{ + {"Simple", "/roms/NES/Super Mario Bros.nes"}, + {"Complex", "/roms/SNES/Final Fantasy VI (USA, Europe) (Rev A) [!].sfc"}, + { + "Long_path", + "/media/storage/games/retro/roms/Nintendo 64/" + + "The Legend of Zelda - Ocarina of Time (USA) (Rev 1.2) [!].z64", + }, + {"Scene_release", "/media/movies/The.Dark.Knight.2008.1080p.BluRay.x264-GROUP.mkv"}, + {"URI_scheme", "kodi-movie://12345/Movie Title"}, + {"NoExt", "/roms/NES/Super Mario Bros"}, + {"CJK", "/roms/SNES/ゼルダの伝説 (Japan).sfc"}, //nolint:gosmopolitan // CJK benchmark + } + + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + params := PathFragmentParams{ + Config: nil, + Path: tc.path, + SystemID: "NES", + NoExt: tc.name == "NoExt", + } + for b.Loop() { + GetPathFragments(params) + } + }) + } +} + +// buildSyntheticFilenames generates deterministic ROM-like filenames for benchmarking. +func buildSyntheticFilenames(n int) []string { + prefixes := []string{ + "Super", "Mega", "Ultra", "Final", "Grand", "Dark", "Crystal", + "Shadow", "Iron", "Bright", "Neo", "Hyper", "Royal", "Star", + } + middles := []string{ + "Mario", "Fighter", "Quest", "Fantasy", "Dragon", "Knight", + "Warrior", "Battle", "Storm", "Legend", "World", "Racer", + } + suffixes := []string{ + "Bros", "Adventure", "Saga", "Chronicles", "Wars", "Legacy", + "Origins", "Legends", "Rising", "Revolution", "Arena", "Force", + } + regions := []string{ + "(USA)", "(Europe)", "(Japan)", "(USA, Europe)", "(World)", + } + extensions := []string{".nes", ".sfc", ".md", ".gba", ".z64", ".iso"} + + rng := rand.New(rand.NewSource(42)) //nolint:gosec // Deterministic seed for reproducible benchmarks + filenames := make([]string, n) + for i := range filenames { + filenames[i] = fmt.Sprintf("/roms/system/%s %s %s %d %s%s", + prefixes[rng.Intn(len(prefixes))], + middles[rng.Intn(len(middles))], + suffixes[rng.Intn(len(suffixes))], + rng.Intn(99)+1, + regions[rng.Intn(len(regions))], + extensions[rng.Intn(len(extensions))], + ) + } + return filenames +} + +func BenchmarkGetPathFragments_Batch(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {"10k", 10_000}, + {"50k", 50_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + filenames := buildSyntheticFilenames(sz.n) + b.ResetTimer() + for b.Loop() { + for _, fn := range filenames { + GetPathFragments(PathFragmentParams{ + Config: nil, + Path: fn, + SystemID: "NES", + }) + } + } + }) + } +} + +func BenchmarkFlushScanStateMaps(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {"10k", 10_000}, + {"50k", 50_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + b.StopTimer() + ss := &database.ScanState{ + SystemIDs: make(map[string]int, 100), + TitleIDs: make(map[string]int, sz.n), + MediaIDs: make(map[string]int, sz.n), + TagTypeIDs: make(map[string]int, 50), + TagIDs: make(map[string]int, 500), + } + for i := range sz.n { + ss.TitleIDs[fmt.Sprintf("title-%d", i)] = i + ss.MediaIDs[fmt.Sprintf("media-%d", i)] = i + } + b.StartTimer() + FlushScanStateMaps(ss) + runtime.KeepAlive(ss) + } + }) + } +} + +// buildSyntheticFilenamesMultiSystem distributes n filenames across multiple +// systems with a Zipf-like distribution to mimic real-world collections. +func buildSyntheticFilenamesMultiSystem(n int, systems []string) map[string][]string { + if len(systems) == 0 { + return nil + } + + prefixes := []string{ + "Super", "Mega", "Ultra", "Final", "Grand", "Dark", "Crystal", + "Shadow", "Iron", "Bright", "Neo", "Hyper", "Royal", "Star", + } + middles := []string{ + "Mario", "Fighter", "Quest", "Fantasy", "Dragon", "Knight", + "Warrior", "Battle", "Storm", "Legend", "World", "Racer", + } + suffixes := []string{ + "Bros", "Adventure", "Saga", "Chronicles", "Wars", "Legacy", + "Origins", "Legends", "Rising", "Revolution", "Arena", "Force", + } + regions := []string{ + "(USA)", "(Europe)", "(Japan)", "(USA, Europe)", "(World)", + } + extensions := []string{".nes", ".sfc", ".md", ".gba", ".z64", ".iso"} + + // Distribute with decreasing weight: first system gets most files + //nolint:gosec // Deterministic seed for reproducible benchmarks + rng := rand.New(rand.NewSource(42)) + weights := make([]float64, len(systems)) + total := 0.0 + for i := range systems { + w := 1.0 / float64(i+1) // Inverse-rank weighted: 1, 0.5, 0.33, 0.25, ... + weights[i] = w + total += w + } + + result := make(map[string][]string, len(systems)) + remaining := n + for i, sys := range systems { + count := int(float64(n) * weights[i] / total) + if i == len(systems)-1 { + count = remaining // Last system gets remainder + } + if count > remaining { + count = remaining + } + remaining -= count + + fns := make([]string, count) + for j := range count { + fns[j] = fmt.Sprintf("/roms/%s/%s %s %s %d %s%s", + sys, + prefixes[rng.Intn(len(prefixes))], + middles[rng.Intn(len(middles))], + suffixes[rng.Intn(len(suffixes))], + rng.Intn(99)+1, + regions[rng.Intn(len(regions))], + extensions[rng.Intn(len(extensions))], + ) + } + result[sys] = fns + } + return result +} + +// newScanState creates a fresh ScanState for benchmarking. +func newScanState() *database.ScanState { + return &database.ScanState{ + SystemIDs: make(map[string]int), + TitleIDs: make(map[string]int), + MediaIDs: make(map[string]int), + TagTypeIDs: make(map[string]int), + TagIDs: make(map[string]int), + } +} + +// setupMockMediaDB creates a MockMediaDBI with all Insert/Find methods stubbed +// for benchmarking AddMediaPath without real SQLite. +func setupMockMediaDB() *helpers.MockMediaDBI { + mockDB := helpers.NewMockMediaDBI() + mockDB.On("InsertSystem", mock.Anything).Return(database.System{}, nil) + mockDB.On("InsertMediaTitle", mock.Anything).Return(database.MediaTitle{}, nil) + mockDB.On("InsertMedia", mock.Anything).Return(database.Media{}, nil) + mockDB.On("InsertTag", mock.Anything).Return(database.Tag{}, nil) + mockDB.On("InsertTagType", mock.Anything).Return(database.TagType{}, nil) + mockDB.On("InsertMediaTag", mock.Anything).Return(database.MediaTag{}, nil) + mockDB.On("FindTagType", mock.Anything).Return(database.TagType{DBID: 1}, nil) + mockDB.On("BeginTransaction", mock.Anything).Return(nil) + mockDB.On("CommitTransaction").Return(nil) + return mockDB +} + +// seedMockScanState populates a ScanState with tag types and tags +// matching what SeedCanonicalTags would produce, but without hitting a real DB. +func seedMockScanState(ss *database.ScanState) { + // Seed the tag types that AddMediaPath looks up + ss.TagTypesIndex = 2 + ss.TagTypeIDs["extension"] = 1 + ss.TagTypeIDs["unknown"] = 2 + ss.TagsIndex = 1 + ss.TagIDs["unknown:unknown"] = 1 +} + +func BenchmarkAddMediaPath_MockDB(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "1k", n: 1_000}, + {name: "10k", n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + mockDB := setupMockMediaDB() + filenames := buildSyntheticFilenames(sz.n) + b.ResetTimer() + for b.Loop() { + ss := newScanState() + seedMockScanState(ss) + for i, fn := range filenames { + _, _, err := AddMediaPath(mockDB, ss, "nes", fn, false, false, nil) + if i == 0 && err != nil { + b.Fatal(err) + } + // Match production pattern: flush every 10k files + if sz.n > 10_000 && (i+1)%10_000 == 0 { + FlushScanStateMaps(ss) + } + } + } + }) + } +} + +func BenchmarkAddMediaPath_RealDB(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "1k", n: 1_000}, + {name: "10k", n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + filenames := buildSyntheticFilenames(sz.n) + + // Each iteration needs a fresh DB. Setup cost is included in + // timing but is constant (~20-50ms) and doesn't affect comparisons. + for b.Loop() { + db, cleanup := helpers.NewInMemoryMediaDB(b) + ss := newScanState() + _ = SeedCanonicalTags(db, ss) + _ = db.BeginTransaction(true) + + // Measured: insert all files with production commit pattern + for i, fn := range filenames { + _, _, err := AddMediaPath(db, ss, "nes", fn, false, false, nil) + if i == 0 && err != nil { + b.Fatal(err) + } + if sz.n > 10_000 && (i+1)%10_000 == 0 { + _ = db.CommitTransaction() + FlushScanStateMaps(ss) + _ = db.BeginTransaction(true) + } + } + + _ = db.CommitTransaction() + cleanup() + } + }) + } +} + +func BenchmarkIndexingPipeline_EndToEnd(b *testing.B) { + systems := []string{"nes", "snes", "gba", "n64", "psx", "genesis", "megadrive", "gamegear", "mastersystem", "gb"} + + type endToEndCase struct { + name string + systems []string + n int + } + sizes := []endToEndCase{ + {name: "10k_1sys", systems: systems[:1], n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + filesBySystem := buildSyntheticFilenamesMultiSystem(sz.n, sz.systems) + for b.Loop() { + db, cleanup := helpers.NewInMemoryMediaDB(b) + ss := newScanState() + _ = SeedCanonicalTags(db, ss) + + filesInBatch := 0 + batchStarted := false + + for _, sys := range sz.systems { + fns := filesBySystem[sys] + for _, fn := range fns { + if !batchStarted { + _ = db.BeginTransaction(true) + batchStarted = true + } + + _, _, err := AddMediaPath(db, ss, sys, fn, false, false, nil) + if filesInBatch == 0 && err != nil { + b.Fatal(err) + } + filesInBatch++ + + if filesInBatch >= 10_000 { + _ = db.CommitTransaction() + FlushScanStateMaps(ss) + filesInBatch = 0 + batchStarted = false + } + } + } + + if batchStarted { + _ = db.CommitTransaction() + } + + cleanup() + } + }) + } +} + +func BenchmarkGetPathFragments_PeakMemory(b *testing.B) { + filenames := buildSyntheticFilenames(50_000) + + // Force GC and get baseline before allocating results + runtime.GC() + runtime.GC() + var before runtime.MemStats + runtime.ReadMemStats(&before) + + results := make([]MediaPathFragments, len(filenames)) + for i, fn := range filenames { + results[i] = GetPathFragments(PathFragmentParams{ + Config: nil, + Path: fn, + SystemID: "NES", + }) + } + + var after runtime.MemStats + runtime.ReadMemStats(&after) + + // Keep results alive for measurement + runtime.KeepAlive(results) + + if after.TotalAlloc > before.TotalAlloc { + b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/(1024*1024), "total-MB") + } +} diff --git a/pkg/database/slugs/slugify_test.go b/pkg/database/slugs/slugify_test.go index e8e4d225..dd76e3bd 100644 --- a/pkg/database/slugs/slugify_test.go +++ b/pkg/database/slugs/slugify_test.go @@ -20,6 +20,8 @@ package slugs import ( + "fmt" + "math/rand" "testing" "github.com/stretchr/testify/assert" @@ -933,6 +935,146 @@ func BenchmarkSlugifyBasic(b *testing.B) { } } +func BenchmarkSlugify_LongTitle(b *testing.B) { + //nolint:lll // Long titles are intentional — benchmarking worst-case slug generation + titles := []struct { + name string + input string + }{ + { + "ROM_with_tags", + "Super Ultra Mega Championship Edition Turbo Hyper Fighting - " + + "The Definitive Collection (USA, Europe) (Rev A) (v1.2.3) [!] [T+Eng1.0]", + }, + { + "Japanese_RPG", + "Shin Megami Tensei - Digital Devil Saga 2 - " + + "Avatar Tuner (Japan) (Rev 1) (Disc 1 of 2)", + }, + { + "Scene_release", + "The.Extremely.Long.Movie.Title.Directors.Cut." + + "Extended.Edition.2024.2160p.UHD.BluRay.x265.HDR10.DTS-HD.MA.7.1-GROUP", + }, + } + + for _, tt := range titles { + b.Run(tt.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + Slugify(MediaTypeGame, tt.input) + } + }) + } +} + +func BenchmarkSlugify_CJK(b *testing.B) { + titles := []struct { + name string + input string + }{ + {"Japanese", "ゼルダの伝説 時のオカリナ"}, //nolint:gosmopolitan // CJK benchmark + {"Chinese", "最终幻想七 重制版"}, //nolint:gosmopolitan // CJK benchmark + {"Korean", "파이널 판타지 VII 리메이크"}, //nolint:gosmopolitan // CJK benchmark + {"Mixed_CJK_Latin", "ファイナルファンタジー VII Remake (Japan)"}, //nolint:gosmopolitan // CJK benchmark + } + + for _, tt := range titles { + b.Run(tt.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + Slugify(MediaTypeGame, tt.input) + } + }) + } +} + +// buildSyntheticTitles generates deterministic game-title-like strings for benchmarking. +func buildSyntheticTitles(n int) []string { + prefixes := []string{ + "Super", "Mega", "Ultra", "Hyper", "Neo", "Final", "Grand", + "Royal", "Dark", "Bright", "Iron", "Crystal", "Shadow", + } + middles := []string{ + "Mario", "Fighter", "Quest", "Fantasy", "Dragon", "Knight", + "Warrior", "Battle", "Storm", "Legend", "World", "Star", + } + suffixes := []string{ + "Bros", "Adventure", "Saga", "Chronicles", "Wars", "Legacy", + "Origins", "Legends", "Rising", "Revolution", "Arena", "Force", + } + regions := []string{ + "(USA)", "(Europe)", "(Japan)", "(USA, Europe)", "(World)", + } + + rng := rand.New(rand.NewSource(42)) //nolint:gosec // Deterministic seed for reproducible benchmarks + titles := make([]string, n) + for i := range titles { + title := fmt.Sprintf("%s %s %s %d %s", + prefixes[rng.Intn(len(prefixes))], + middles[rng.Intn(len(middles))], + suffixes[rng.Intn(len(suffixes))], + rng.Intn(99)+1, + regions[rng.Intn(len(regions))], + ) + titles[i] = title + } + return titles +} + +func BenchmarkSlugify_Batch_500k(b *testing.B) { + b.ReportAllocs() + titles := buildSyntheticTitles(500_000) + b.ResetTimer() + for b.Loop() { + for _, t := range titles { + Slugify(MediaTypeGame, t) + } + } +} + +func BenchmarkNormalizeToWords(b *testing.B) { + titles := []struct { + name string + input string + }{ + {"Simple", "Super Mario Bros"}, + {"With_metadata", "The Legend of Zelda: Ocarina of Time (USA) [!]"}, + {"Roman_numerals", "Final Fantasy VII Part II"}, + {"Ampersand", "Sonic & Knuckles"}, + {"Long_title", "Shin Megami Tensei - Digital Devil Saga 2 - Avatar Tuner (Japan) (Rev 1)"}, + } + + for _, tt := range titles { + b.Run(tt.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + NormalizeToWords(tt.input) + } + }) + } +} + +func BenchmarkSlugifyWithTokens(b *testing.B) { + titles := []struct { + name string + input string + }{ + {"Simple", "Super Mario Bros"}, + {"Complex", "The Legend of Zelda: Ocarina of Time (USA) [!]"}, + {"CJK", "ゼルダの伝説 時のオカリナ"}, //nolint:gosmopolitan // CJK benchmark + } + + for _, tt := range titles { + b.Run(tt.name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + SlugifyWithTokens(MediaTypeGame, tt.input) + } + }) + } +} + func TestNormalizeToWords(t *testing.T) { t.Parallel() diff --git a/pkg/database/tags/filename_parser_test.go b/pkg/database/tags/filename_parser_test.go index 28b7a7ca..20b45fd1 100644 --- a/pkg/database/tags/filename_parser_test.go +++ b/pkg/database/tags/filename_parser_test.go @@ -20,6 +20,7 @@ package tags import ( + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -1747,3 +1748,61 @@ func TestParseFilenameToCanonicalTags_MixedTagTypes(t *testing.T) { }) } } + +func BenchmarkParseFilenameToCanonicalTags(b *testing.B) { + b.Run("Simple", func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + ParseFilenameToCanonicalTags("Game (USA) [!].zip") + } + }) + + b.Run("Complex", func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + ParseFilenameToCanonicalTags("Game Title (USA, Europe) (En,Fr,De) (Rev A) (v1.2) [!] [h1].zip") + } + }) + + b.Run("Scene_release", func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + ParseFilenameToCanonicalTags("The.Dark.Knight.2008.1080p.BluRay.x264-GROUP.mkv") + } + }) +} + +func benchGenerateNumberedFilenames(n int) []string { + filenames := make([]string, n) + for i := range n { + num := i + 1 + if num < 10 { + filenames[i] = "0" + strconv.Itoa(num) + " - Game.zip" + } else { + filenames[i] = strconv.Itoa(num) + " - Game.zip" + } + } + return filenames +} + +func BenchmarkDetectNumberingPattern(b *testing.B) { + for _, scale := range []int{100, 1000, 10000} { + filenames := benchGenerateNumberedFilenames(scale) + b.Run(strconv.Itoa(scale), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + for _, fn := range filenames { + ParseFilenameToCanonicalTags(fn) + } + } + }) + } +} + +func BenchmarkFilenameParser_ExtractSpecialPatterns(b *testing.B) { + filename := "Game Title (Disc 1 of 3) (Rev A) (v1.2) (1998) S02E05.zip" + b.ReportAllocs() + for b.Loop() { + extractSpecialPatterns(filename) + } +} diff --git a/pkg/readers/shared/ndef/ndef_test.go b/pkg/readers/shared/ndef/ndef_test.go index 91c41167..74cf887c 100644 --- a/pkg/readers/shared/ndef/ndef_test.go +++ b/pkg/readers/shared/ndef/ndef_test.go @@ -30,6 +30,51 @@ import ( "github.com/hsanjuan/go-ndef" ) +func BenchmarkParseToText_TextRecord(b *testing.B) { + b.ReportAllocs() + data, err := BuildTextMessage("super mario bros 3") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for b.Loop() { + _, _ = ParseToText(data) + } +} + +func BenchmarkParseToText_URIRecord(b *testing.B) { + b.ReportAllocs() + msg := ndef.NewURIMessage("https://example.com/games/super-mario-bros") + payload, err := msg.Marshal() + if err != nil { + b.Fatal(err) + } + header, err := calculateNDEFHeader(payload) + if err != nil { + b.Fatal(err) + } + data := make([]byte, 0, len(header)+len(payload)+1) + data = append(data, header...) + data = append(data, payload...) + data = append(data, 0xFE) + b.ResetTimer() + for b.Loop() { + _, _ = ParseToText(data) + } +} + +func BenchmarkValidateNDEFMessage(b *testing.B) { + b.ReportAllocs() + data, err := BuildTextMessage("test validation") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for b.Loop() { + _ = ValidateNDEFMessage(data) + } +} + func TestParseToText_TextRecord(t *testing.T) { t.Parallel() diff --git a/pkg/service/main_test.go b/pkg/service/main_test.go index b0d176a0..0e5b10dd 100644 --- a/pkg/service/main_test.go +++ b/pkg/service/main_test.go @@ -22,9 +22,11 @@ package service import ( "testing" + "github.com/rs/zerolog" "go.uber.org/goleak" ) func TestMain(m *testing.M) { + zerolog.SetGlobalLevel(zerolog.Disabled) goleak.VerifyTestMain(m) } diff --git a/pkg/service/mappings_bench_test.go b/pkg/service/mappings_bench_test.go new file mode 100644 index 00000000..1fda1af2 --- /dev/null +++ b/pkg/service/mappings_bench_test.go @@ -0,0 +1,142 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package service + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/userdb" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/service/tokens" +) + +// benchGenerateUID builds a colon-separated hex UID from an integer, +// padded to 12 hex chars like a real NFC UID (e.g. "00:00:00:00:00:2A"). +func benchGenerateUID(i int) string { + hex := strings.ToUpper(strconv.FormatInt(int64(i), 16)) + for len(hex) < 12 { + hex = "0" + hex + } + parts := make([]string, 0, len(hex)/2+1) + for j := 0; j < len(hex); j += 2 { + end := j + 2 + if end > len(hex) { + end = len(hex) + } + parts = append(parts, hex[j:end]) + } + return strings.Join(parts, ":") +} + +func BenchmarkMatchMapping_Exact_50rules(b *testing.B) { + b.ReportAllocs() + mappings := make([]database.Mapping, 50) + for i := range 50 { + uid := benchGenerateUID(i) + mappings[i] = database.Mapping{ + Type: userdb.MappingTypeID, + Match: userdb.MatchTypeExact, + Pattern: uid, + Enabled: true, + } + } + // Target is the last rule (worst case linear scan) + token := tokens.Token{UID: benchGenerateUID(49)} + b.ResetTimer() + for b.Loop() { + for i := range mappings { + if checkMappingUID(&mappings[i], &token) { + break + } + } + } +} + +func BenchmarkMatchMapping_Regex_50rules(b *testing.B) { + b.ReportAllocs() + mappings := make([]database.Mapping, 50) + for i := range 50 { + hex := strings.ToUpper(strconv.FormatInt(int64(i), 16)) + for len(hex) < 4 { + hex = "0" + hex + } + mappings[i] = database.Mapping{ + Type: userdb.MappingTypeID, + Match: userdb.MatchTypeRegex, + Pattern: fmt.Sprintf("(?i)^[0-9a-f]*%s[0-9a-f]*$", hex), + Enabled: true, + } + } + token := tokens.Token{UID: "04:52:7C:A2:6B:5D:80"} + b.ResetTimer() + for b.Loop() { + for i := range mappings { + if checkMappingUID(&mappings[i], &token) { + break + } + } + } +} + +func BenchmarkMatchMapping_Mixed_100rules(b *testing.B) { + b.ReportAllocs() + mappings := make([]database.Mapping, 100) + for i := range 100 { + hex := strings.ToUpper(strconv.FormatInt(int64(i), 16)) + for len(hex) < 8 { + hex = "0" + hex + } + switch i % 3 { + case 0: + mappings[i] = database.Mapping{ + Type: userdb.MappingTypeID, + Match: userdb.MatchTypeExact, + Pattern: hex, + Enabled: true, + } + case 1: + mappings[i] = database.Mapping{ + Type: userdb.MappingTypeID, + Match: userdb.MatchTypePartial, + Pattern: hex[:4], + Enabled: true, + } + default: + mappings[i] = database.Mapping{ + Type: userdb.MappingTypeID, + Match: userdb.MatchTypeRegex, + Pattern: fmt.Sprintf("(?i)%s.*", hex[:4]), + Enabled: true, + } + } + } + token := tokens.Token{UID: "04:52:7C:A2:6B:5D:80"} + b.ResetTimer() + for b.Loop() { + for i := range mappings { + if checkMappingUID(&mappings[i], &token) { + break + } + } + } +} diff --git a/pkg/service/scan_to_launch_bench_test.go b/pkg/service/scan_to_launch_bench_test.go new file mode 100644 index 00000000..054f1f5a --- /dev/null +++ b/pkg/service/scan_to_launch_bench_test.go @@ -0,0 +1,289 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package service + +import ( + "testing" + + gozapscript "github.com/ZaparooProject/go-zapscript" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/config" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediascanner" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/userdb" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/helpers" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/platforms" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/service/playlists" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/service/state" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/service/tokens" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/fixtures" + testhelpers "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/helpers" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/mocks" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/zapscript" + "github.com/stretchr/testify/mock" +) + +// benchPipelineLaunchers provides test launchers for the NES system. +var benchPipelineLaunchers = []platforms.Launcher{{ + ID: "nes-launcher", + SystemID: "NES", + Extensions: []string{".nes"}, + Folders: []string{"/roms/system/"}, +}} + +// pipelineBenchEnv holds shared benchmark environment for scan-to-launch tests. +type pipelineBenchEnv struct { + db *database.Database + cfg *config.Instance + pl *mocks.MockPlatform + lm *state.LauncherManager + cleanup func() + exprEnv gozapscript.ArgExprEnv + gameNames []string +} + +// setupPipelineBench creates the full environment needed for scan-to-launch +// benchmarking: real MediaDB with titles, mock UserDB, mock platform, real config. +func setupPipelineBench(b *testing.B, n int) *pipelineBenchEnv { + b.Helper() + + // Real MediaDB with production schema + mediaDB, mediaCleanup := testhelpers.NewInMemoryMediaDB(b) + + // Seed canonical tags and populate with titles + ss := &database.ScanState{ + SystemIDs: make(map[string]int), + TitleIDs: make(map[string]int), + MediaIDs: make(map[string]int), + TagTypeIDs: make(map[string]int), + TagIDs: make(map[string]int), + } + if err := mediascanner.SeedCanonicalTags(mediaDB, ss); err != nil { + b.Fatal(err) + } + + filenames := fixtures.BuildBenchFilenames(n) + if err := mediaDB.BeginTransaction(true); err != nil { + b.Fatal(err) + } + for i, fn := range filenames { + _, _, err := mediascanner.AddMediaPath(mediaDB, ss, "NES", fn, false, false, nil) + if i == 0 && err != nil { + b.Fatal(err) + } + if (i+1)%10_000 == 0 { + if err := mediaDB.CommitTransaction(); err != nil { + b.Fatal(err) + } + mediascanner.FlushScanStateMaps(ss) + if err := mediaDB.BeginTransaction(true); err != nil { + b.Fatal(err) + } + } + } + if err := mediaDB.CommitTransaction(); err != nil { + b.Fatal(err) + } + if err := mediaDB.RebuildSlugSearchCache(); err != nil { + b.Fatal(err) + } + + // Mock UserDB — only needs GetEnabledMappings and GetZapLinkHost + mockUserDB := testhelpers.NewMockUserDBI() + mockUserDB.On("GetEnabledMappings").Return([]database.Mapping{}, nil) + mockUserDB.On("GetZapLinkHost", mock.Anything).Return(false, false, nil) + + db := &database.Database{ + MediaDB: mediaDB, + UserDB: mockUserDB, + } + + // Real config + cfg, err := testhelpers.NewTestConfig(nil, b.TempDir()) + if err != nil { + b.Fatal(err) + } + + // Mock platform — only LaunchMedia, LookupMapping, ID need stubbing + pl := mocks.NewMockPlatform() + pl.On("LaunchMedia", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + pl.On("LookupMapping", mock.Anything).Return("", false) + pl.On("ID").Return("test-platform") + pl.On("Launchers", mock.Anything).Return(benchPipelineLaunchers) + + // Launcher manager and cache + lm := state.NewLauncherManager() + helpers.GlobalLauncherCache.InitializeFromSlice(benchPipelineLaunchers) + + // Extract game names + const pathPrefix = "/roms/system/" + const ext = ".nes" + gameNames := make([]string, n) + for i, fn := range filenames { + gameNames[i] = fn[len(pathPrefix) : len(fn)-len(ext)] + } + + return &pipelineBenchEnv{ + db: db, + cfg: cfg, + pl: pl, + lm: lm, + exprEnv: gozapscript.ArgExprEnv{Platform: "test-platform"}, + gameNames: gameNames, + cleanup: mediaCleanup, + } +} + +func BenchmarkScanToLaunch_ExactMatch(b *testing.B) { + b.ReportAllocs() + env := setupPipelineBench(b, 10_000) + defer env.cleanup() + + // Token text: @nes/GameName triggers launch.title command + gameName := env.gameNames[0] + tokenText := "@nes/" + gameName + + token := tokens.Token{ + Text: tokenText, + UID: "04:AA:BB:CC:DD:EE:FF", + Source: "bench", + } + + b.ResetTimer() + for b.Loop() { + // 1. Mapping check (real production code) + mappedValue, hasMapping := getMapping(env.cfg, env.db, env.pl, token) + scriptText := token.Text + if hasMapping { + scriptText = mappedValue + } + + // 2. ZapScript parse (real, external go-zapscript module) + parser := gozapscript.NewParser(scriptText) + script, err := parser.ParseScript() + if err != nil { + b.Fatal(err) + } + + // 3. Command execution (real RunCommand -> cmdTitle -> ResolveTitle -> mock launch) + for i, cmd := range script.Cmds { + _, err = zapscript.RunCommand( + env.pl, env.cfg, playlists.PlaylistController{}, token, cmd, + len(script.Cmds), i, env.db, env.lm, &env.exprEnv, + ) + if err != nil { + b.Fatal(err) + } + } + } +} + +func BenchmarkScanToLaunch_DirectPath(b *testing.B) { + b.ReportAllocs() + env := setupPipelineBench(b, 1_000) + defer env.cleanup() + + // Direct file path — no title resolution, just mapping check + parse + launch + token := tokens.Token{ + Text: "/roms/system/Super Mario Bros 1.nes", + UID: "04:AA:BB:CC:DD:EE:FF", + Source: "bench", + } + + b.ResetTimer() + for b.Loop() { + // 1. Mapping check + mappedValue, hasMapping := getMapping(env.cfg, env.db, env.pl, token) + scriptText := token.Text + if hasMapping { + scriptText = mappedValue + } + + // 2. ZapScript parse + parser := gozapscript.NewParser(scriptText) + script, err := parser.ParseScript() + if err != nil { + b.Fatal(err) + } + + // 3. Command execution (cmdLaunch with direct path) + for i, cmd := range script.Cmds { + _, err = zapscript.RunCommand( + env.pl, env.cfg, playlists.PlaylistController{}, token, cmd, + len(script.Cmds), i, env.db, env.lm, &env.exprEnv, + ) + if err != nil { + b.Fatal(err) + } + } + } +} + +func BenchmarkScanToLaunch_WithMapping(b *testing.B) { + b.ReportAllocs() + env := setupPipelineBench(b, 10_000) + defer env.cleanup() + + // Add a mapping rule to UserDB + gameName := env.gameNames[0] + mappings := []database.Mapping{{ + Type: userdb.MappingTypeID, + Match: userdb.MatchTypeExact, + Pattern: "04:aa:bb:cc:dd:ee:ff", + Override: "@nes/" + gameName, + Enabled: true, + }} + // Reset and re-stub with a mapping result + env.db.UserDB.(*testhelpers.MockUserDBI).ExpectedCalls = nil + env.db.UserDB.(*testhelpers.MockUserDBI).On("GetEnabledMappings").Return(mappings, nil) + env.db.UserDB.(*testhelpers.MockUserDBI).On("GetZapLinkHost", mock.Anything).Return(false, false, nil) + + token := tokens.Token{ + Text: "", + UID: "04:AA:BB:CC:DD:EE:FF", + Source: "bench", + } + + b.ResetTimer() + for b.Loop() { + // 1. Mapping check — should match on UID + mappedValue, hasMapping := getMapping(env.cfg, env.db, env.pl, token) + if !hasMapping { + b.Fatal("expected mapping match") + } + + // 2. ZapScript parse of mapped value + parser := gozapscript.NewParser(mappedValue) + script, err := parser.ParseScript() + if err != nil { + b.Fatal(err) + } + + // 3. Command execution + for i, cmd := range script.Cmds { + _, err = zapscript.RunCommand( + env.pl, env.cfg, playlists.PlaylistController{}, token, cmd, + len(script.Cmds), i, env.db, env.lm, &env.exprEnv, + ) + if err != nil { + b.Fatal(err) + } + } + } +} diff --git a/pkg/testing/fixtures/benchmarks.go b/pkg/testing/fixtures/benchmarks.go new file mode 100644 index 00000000..b50d6ad6 --- /dev/null +++ b/pkg/testing/fixtures/benchmarks.go @@ -0,0 +1,55 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package fixtures + +import ( + "fmt" + "math/rand" +) + +// BuildBenchFilenames generates n deterministic ROM-like filenames for +// benchmarking. Uses a fixed seed (42) for reproducibility. No region +// tags are included so that title resolution confidence stays high. +func BuildBenchFilenames(n int) []string { + prefixes := []string{ + "Super", "Mega", "Ultra", "Final", "Grand", "Dark", "Crystal", + "Shadow", "Iron", "Bright", "Neo", "Hyper", "Royal", "Star", + } + middles := []string{ + "Mario", "Fighter", "Quest", "Fantasy", "Dragon", "Knight", + "Warrior", "Battle", "Storm", "Legend", "World", "Racer", + } + suffixes := []string{ + "Bros", "Adventure", "Saga", "Chronicles", "Wars", "Legacy", + "Origins", "Legends", "Rising", "Revolution", "Arena", "Force", + } + + rng := rand.New(rand.NewSource(42)) //nolint:gosec // Deterministic seed for reproducible benchmarks + filenames := make([]string, n) + for i := range filenames { + filenames[i] = fmt.Sprintf("/roms/system/%s %s %s %d.nes", + prefixes[rng.Intn(len(prefixes))], + middles[rng.Intn(len(middles))], + suffixes[rng.Intn(len(suffixes))], + rng.Intn(99)+1, + ) + } + return filenames +} diff --git a/pkg/testing/helpers/inmemory_db.go b/pkg/testing/helpers/inmemory_db.go index 9a907594..d6d6a667 100644 --- a/pkg/testing/helpers/inmemory_db.go +++ b/pkg/testing/helpers/inmemory_db.go @@ -68,18 +68,18 @@ func NewInMemoryUserDB(t *testing.T) (db *userdb.UserDB, cleanup func()) { return db, cleanup } -func NewInMemoryMediaDB(t *testing.T) (db *mediadb.MediaDB, cleanup func()) { - t.Helper() +func NewInMemoryMediaDB(tb testing.TB) (db *mediadb.MediaDB, cleanup func()) { + tb.Helper() // Create temporary directory for test database - tempDir := t.TempDir() + tempDir := tb.TempDir() dbPath := filepath.Join(tempDir, "mediadb_test.db") // Open SQLite database using temp file with foreign keys enabled // This matches the production database configuration and ensures CASCADE deletes work sqlDB, err := sql.Open("sqlite3", dbPath+"?_foreign_keys=ON") if err != nil { - t.Fatalf("Failed to open test database: %v", err) + tb.Fatalf("Failed to open test database: %v", err) } db = &mediadb.MediaDB{} @@ -89,15 +89,15 @@ func NewInMemoryMediaDB(t *testing.T) (db *mediadb.MediaDB, cleanup func()) { err = db.SetSQLForTesting(ctx, sqlDB, mockPlatform) if err != nil { if closeErr := sqlDB.Close(); closeErr != nil { - t.Errorf("Failed to close SQL database after setup error: %v", closeErr) + tb.Errorf("Failed to close SQL database after setup error: %v", closeErr) } - t.Fatalf("Failed to set up MediaDB for testing: %v", err) + tb.Fatalf("Failed to set up MediaDB for testing: %v", err) } db.SetDBPathForTesting(dbPath) cleanup = func() { if err := db.Close(); err != nil { - t.Errorf("Failed to close MediaDB: %v", err) + tb.Errorf("Failed to close MediaDB: %v", err) } } diff --git a/pkg/zapscript/advargs/advargs_test.go b/pkg/zapscript/advargs/advargs_test.go index adf086ae..057811da 100644 --- a/pkg/zapscript/advargs/advargs_test.go +++ b/pkg/zapscript/advargs/advargs_test.go @@ -27,6 +27,44 @@ import ( "github.com/stretchr/testify/require" ) +func BenchmarkParse_GlobalArgs(b *testing.B) { + b.ReportAllocs() + raw := map[string]string{} + for b.Loop() { + var args zapscript.GlobalArgs + _ = Parse(raw, &args, nil) + } +} + +func BenchmarkParse_LaunchArgs(b *testing.B) { + b.ReportAllocs() + ctx := NewParseContext([]string{"steam", "retroarch", "mister"}) + raw := map[string]string{ + "launcher": "steam", + "system": "NES", + "action": "run", + "tags": "genre:rpg,region:usa", + } + for b.Loop() { + var args zapscript.LaunchRandomArgs + _ = Parse(raw, &args, ctx) + } +} + +func BenchmarkParse_LaunchSearchArgs(b *testing.B) { + b.ReportAllocs() + ctx := NewParseContext([]string{"steam", "retroarch", "mister", "dolphin", "pcsx2"}) + raw := map[string]string{ + "launcher": "retroarch", + "action": "details", + "tags": "genre:rpg,region:usa,players:2", + } + for b.Loop() { + var args zapscript.LaunchSearchArgs + _ = Parse(raw, &args, ctx) + } +} + func TestParse_GlobalArgs(t *testing.T) { t.Parallel() diff --git a/pkg/zapscript/titles/main_test.go b/pkg/zapscript/titles/main_test.go new file mode 100644 index 00000000..028d9628 --- /dev/null +++ b/pkg/zapscript/titles/main_test.go @@ -0,0 +1,31 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package titles + +import ( + "testing" + + "github.com/rs/zerolog" +) + +func TestMain(m *testing.M) { + zerolog.SetGlobalLevel(zerolog.Disabled) + m.Run() +} diff --git a/pkg/zapscript/titles/resolve_bench_test.go b/pkg/zapscript/titles/resolve_bench_test.go new file mode 100644 index 00000000..78344409 --- /dev/null +++ b/pkg/zapscript/titles/resolve_bench_test.go @@ -0,0 +1,227 @@ +// Zaparoo Core +// Copyright (c) 2026 The Zaparoo Project Contributors. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Zaparoo Core. +// +// Zaparoo Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zaparoo Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zaparoo Core. If not, see . + +package titles + +import ( + "context" + "testing" + + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediadb" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediascanner" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/database/slugs" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/helpers" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/platforms" + "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/fixtures" + testhelpers "github.com/ZaparooProject/zaparoo-core/v2/pkg/testing/helpers" +) + +// benchLaunchers provides test launchers for the NES system. +var benchLaunchers = []platforms.Launcher{{ + ID: "nes-launcher", + SystemID: "nes", + Extensions: []string{".nes"}, + Folders: []string{"/roms/system/"}, +}} + +// setupResolveBenchDB creates a real MediaDB populated with n titles and returns +// the DB, a list of inserted game names (for query construction), and a cleanup +// function. +func setupResolveBenchDB(b *testing.B, n int) ( + db *mediadb.MediaDB, gameNames []string, cleanup func(), +) { + b.Helper() + + db, cleanup = testhelpers.NewInMemoryMediaDB(b) + + // Seed canonical tags (required before AddMediaPath) + ss := &database.ScanState{ + SystemIDs: make(map[string]int), + TitleIDs: make(map[string]int), + MediaIDs: make(map[string]int), + TagTypeIDs: make(map[string]int), + TagIDs: make(map[string]int), + } + if err := mediascanner.SeedCanonicalTags(db, ss); err != nil { + b.Fatal(err) + } + + // Generate deterministic filenames + filenames := fixtures.BuildBenchFilenames(n) + + // Populate DB with titles via real production AddMediaPath + if err := db.BeginTransaction(true); err != nil { + b.Fatal(err) + } + for i, fn := range filenames { + _, _, err := mediascanner.AddMediaPath(db, ss, "nes", fn, false, false, nil) + if i == 0 && err != nil { + b.Fatal(err) + } + if (i+1)%10_000 == 0 { + if err := db.CommitTransaction(); err != nil { + b.Fatal(err) + } + mediascanner.FlushScanStateMaps(ss) + if err := db.BeginTransaction(true); err != nil { + b.Fatal(err) + } + } + } + if err := db.CommitTransaction(); err != nil { + b.Fatal(err) + } + + // Build slug search cache (required for search operations) + if err := db.RebuildSlugSearchCache(); err != nil { + b.Fatal(err) + } + + // Initialize global launcher cache + helpers.GlobalLauncherCache.InitializeFromSlice(benchLaunchers) + + // Extract game names from filenames for query construction + const pathPrefix = "/roms/system/" + const ext = ".nes" + gameNames = make([]string, n) + for i, fn := range filenames { + gameNames[i] = fn[len(pathPrefix) : len(fn)-len(ext)] + } + + return db, gameNames, cleanup +} + +func BenchmarkResolveTitle_CacheHit(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "10k", n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + db, gameNames, cleanup := setupResolveBenchDB(b, sz.n) + defer cleanup() + + ctx := context.Background() + gameName := gameNames[0] + params := &ResolveParams{ + MediaDB: db, + Cfg: nil, + SystemID: "nes", + GameName: gameName, + MediaType: slugs.MediaTypeGame, + Launchers: benchLaunchers, + } + + // Seed the resolution cache with a first call + _, err := ResolveTitle(ctx, params) + if err != nil { + b.Fatalf("initial resolve failed: %v", err) + } + + b.ResetTimer() + for b.Loop() { + _, _ = ResolveTitle(ctx, params) + } + }) + } +} + +func BenchmarkResolveTitle_ExactMatch(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "10k", n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + db, gameNames, cleanup := setupResolveBenchDB(b, sz.n) + defer cleanup() + + ctx := context.Background() + + // Cycle through different game names to avoid resolution cache hits + b.ResetTimer() + idx := 0 + for b.Loop() { + gameName := gameNames[idx%len(gameNames)] + idx++ + params := &ResolveParams{ + MediaDB: db, + Cfg: nil, + SystemID: "nes", + GameName: gameName, + MediaType: slugs.MediaTypeGame, + Launchers: benchLaunchers, + } + // Clear the resolution cache (not the slug search cache) so each + // iteration exercises the full slug match strategy instead of + // hitting the cached resolution from a previous iteration + _ = db.InvalidateSlugCache(ctx) + _, _ = ResolveTitle(ctx, params) + } + }) + } +} + +func BenchmarkResolveTitle_FuzzyFallback(b *testing.B) { + sizes := []struct { + name string + n int + }{ + {name: "10k", n: 10_000}, + } + + for _, sz := range sizes { + b.Run(sz.name, func(b *testing.B) { + b.ReportAllocs() + db, _, cleanup := setupResolveBenchDB(b, sz.n) + defer cleanup() + + ctx := context.Background() + + // Use a misspelled name that won't match exactly but should + // find a fuzzy match + params := &ResolveParams{ + MediaDB: db, + Cfg: nil, + SystemID: "nes", + GameName: "Supr Maro Brothrs Advnture", + MediaType: slugs.MediaTypeGame, + Launchers: benchLaunchers, + } + + b.ResetTimer() + for b.Loop() { + // Clear resolution cache to force full strategy cascade + _ = db.InvalidateSlugCache(ctx) + // Fuzzy match may or may not succeed; we're measuring the + // cost of falling through strategies + _, _ = ResolveTitle(ctx, params) + } + }) + } +} diff --git a/scripts/tasks/mister.yml b/scripts/tasks/mister.yml index 9374e550..23a55961 100644 --- a/scripts/tasks/mister.yml +++ b/scripts/tasks/mister.yml @@ -37,6 +37,63 @@ tasks: - scp _build/mister_{{.CLI_ARGS}}/zaparoo.sh root@${MISTER_IP}:/media/fat/Scripts/zaparoo.sh - ssh root@${MISTER_IP} /media/fat/Scripts/zaparoo.sh -service restart + bench-build: + desc: Cross-compile benchmark test binaries for MiSTer ARM + cmds: + - task: :zigcc:setup + - task: :docker:run + vars: + IMAGE_NAME: zaparoo/zigcc + BUILD_OS: linux + BUILD_ARCH: arm + NO_STATIC: true + CC: "zig cc -target arm-linux-gnueabihf" + CXX: "zig c++ -target arm-linux-gnueabihf" + EXTRA_LDFLAGS: "-linkmode external" + EXTRA_TAGS: "" + EXTRA_DOCKER_ARGS: >- + -e CGO_ENABLED=1 + -e GOOS=linux + -e GOARCH=arm + -e GO111MODULE=on + -e CGO_CFLAGS="-I/opt/libnfc/arm-linux-gnueabihf/include" + -e CGO_LDFLAGS="-L/opt/libnfc/arm-linux-gnueabihf/lib -L/opt/deps/arm-linux-gnueabihf/lib /opt/libnfc/arm-linux-gnueabihf/lib/libnfc.a /opt/deps/arm-linux-gnueabihf/lib/libusb.a /opt/deps/arm-linux-gnueabihf/lib/libusb-1.0.a -ldl" + EXEC: >- + bash -c 'set -e && mkdir -p _build/bench_arm && + PKGS=$(find ./pkg -name "*_bench_test.go" -o -name "*_test.go" | xargs grep -l "func Benchmark" | xargs -I{} dirname {} | sort -u) && + for pkg in $PKGS; do + name=$(basename $pkg) && + echo "Building $pkg -> ${name}.test" && + go test -c + -tags "netgo,osusergo,sqlite_omit_load_extension" + -ldflags "-linkmode external -s -w" + -o "_build/bench_arm/${name}.test" "$pkg" + ; done && + echo "Done: $(ls _build/bench_arm/*.test | wc -l) binaries"' + + bench-run: + desc: Run benchmark binaries on MiSTer and save baseline + vars: + BENCH_COUNT: '{{default "3" .BENCH_COUNT}}' + BENCH_TIMEOUT: '{{default "60m" .BENCH_TIMEOUT}}' + SSH_OPTS: -o ServerAliveInterval=30 -o ServerAliveCountMax=120 + cmds: + - ssh {{.SSH_OPTS}} root@${MISTER_IP} "mkdir -p /media/fat/zaparoo-bench" + - scp _build/bench_arm/*.test root@${MISTER_IP}:/media/fat/zaparoo-bench/ + - | + (for bin in _build/bench_arm/*.test; do + name=$(basename $bin) + echo "Running $name..." >&2 + ssh {{.SSH_OPTS}} root@${MISTER_IP} "/media/fat/zaparoo-bench/$name -test.run='^\$' -test.bench=. -test.benchmem -test.count={{.BENCH_COUNT}} -test.timeout={{.BENCH_TIMEOUT}}" + done) | grep -E "^(Benchmark.* ns/op|goos:|goarch:|pkg:|cpu:)" | tee testdata/benchmarks/baseline-mister.txt + - ssh {{.SSH_OPTS}} root@${MISTER_IP} "rm -rf /media/fat/zaparoo-bench" + + bench: + desc: Build and run benchmarks on MiSTer + cmds: + - task: bench-build + - task: bench-run + build-ffmpeg-libs: desc: Build FFmpeg static libraries for MiSTer vars: diff --git a/testdata/benchmarks/.gitkeep b/testdata/benchmarks/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/testdata/benchmarks/baseline-mister.txt b/testdata/benchmarks/baseline-mister.txt new file mode 100644 index 00000000..6b4bae40 --- /dev/null +++ b/testdata/benchmarks/baseline-mister.txt @@ -0,0 +1,379 @@ +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/zapscript/advargs +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkParse_GlobalArgs-2 24709 49477 ns/op 256 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-2 24651 49652 ns/op 256 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-2 24524 49447 ns/op 256 B/op 14 allocs/op +BenchmarkParse_LaunchArgs-2 4508 252972 ns/op 1664 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-2 4460 254281 ns/op 1664 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-2 4921 250668 ns/op 1656 B/op 79 allocs/op +BenchmarkParse_LaunchSearchArgs-2 4384 251605 ns/op 1529 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-2 4483 247628 ns/op 1528 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-2 4290 250991 ns/op 1545 B/op 80 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/config +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkConfig_Read-2 2832444 424.1 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-2 2797412 425.0 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-2 2824963 425.7 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Load-2 4237 293712 ns/op 3148 B/op 22 allocs/op +BenchmarkConfig_Load-2 3991 291542 ns/op 3147 B/op 22 allocs/op +BenchmarkConfig_Load-2 4284 290038 ns/op 3147 B/op 22 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/helpers +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkRegexCacheVsStandard/Standard-2 20059 60327 ns/op 1424 B/op 21 allocs/op +BenchmarkRegexCacheVsStandard/Standard-2 19851 59947 ns/op 1424 B/op 21 allocs/op +BenchmarkRegexCacheVsStandard/Standard-2 19622 61175 ns/op 1427 B/op 21 allocs/op +BenchmarkRegexCacheVsStandard/Cached-2 318603 3799 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-2 315958 3813 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-2 312426 3749 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-2 318926 3767 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-2 303874 3760 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-2 315321 3818 ns/op 0 B/op 0 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/platforms/shared/kodi +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkIDExtraction/ManualParsing-2 1000000 1446 ns/op 16 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-2 1000000 1117 ns/op 16 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-2 1000000 1127 ns/op 16 B/op 1 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-2 154081 7893 ns/op 64 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-2 153404 7934 ns/op 64 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-2 154351 7835 ns/op 64 B/op 2 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/matcher +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkFindFuzzyMatches_500k-2 6 180528104 ns/op 2649344 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-2 7 183091597 ns/op 2649344 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-2 7 176033897 ns/op 2649344 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_1M-2 4 336087279 ns/op 4661888 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-2 3 360073871 ns/op 4661888 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-2 4 317997791 ns/op 4661888 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-2 4 366767665 ns/op 6137312 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-2 3 359551968 ns/op 6137312 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-2 3 359969892 ns/op 6137312 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-2 1 1713948629 ns/op 33934912 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-2 1 1799003255 ns/op 33934912 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-2 1 1888149401 ns/op 33934912 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-2 1 1345689285 ns/op 21503120 B/op 228743 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-2 1 1335618954 ns/op 21503056 B/op 228742 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-2 1 1327507525 ns/op 21503056 B/op 228742 allocs/op +BenchmarkJaroWinkler_Similarity-2 212617 5665 ns/op 128 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-2 234482 5701 ns/op 128 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-2 225667 5719 ns/op 128 B/op 2 allocs/op +BenchmarkGenerateTokenSignature_500k-2 1 94718785242 ns/op 443491136 B/op 19422641 allocs/op +BenchmarkGenerateTokenSignature_500k-2 1 94831200178 ns/op 443563224 B/op 19422661 allocs/op +BenchmarkGenerateTokenSignature_500k-2 1 94731148907 ns/op 443563320 B/op 19422659 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediadb +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkBatchInserter_Insert/10k-2 1 13282962150 ns/op 18533160 B/op 141777 allocs/op +BenchmarkBatchInserter_Insert/10k-2 1 13382933924 ns/op 18532360 B/op 141765 allocs/op +BenchmarkBatchInserter_Insert/10k-2 1 13368931603 ns/op 18530488 B/op 141772 allocs/op +BenchmarkBatchInserter_Insert/50k-2 1 70364103304 ns/op 55888584 B/op 702726 allocs/op +BenchmarkBatchInserter_Insert/50k-2 1 70309316808 ns/op 55815344 B/op 702729 allocs/op +BenchmarkBatchInserter_Insert/50k-2 1 70368523202 ns/op 60081008 B/op 702721 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-2 3 486302831 ns/op 5743877 B/op 2962 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-2 3 494064653 ns/op 124394 B/op 2948 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-2 2 516682698 ns/op 124592 B/op 2951 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-2 2 872795775 ns/op 524096 B/op 8941 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-2 2 869548880 ns/op 2607680 B/op 8943 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-2 2 871815828 ns/op 524136 B/op 8941 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-2 1 3453501916 ns/op 3011880 B/op 37002 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-2 1 3403079646 ns/op 3011672 B/op 36998 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-2 1 3445053901 ns/op 3011400 B/op 36995 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-2 2 873018478 ns/op 9179680 B/op 8964 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-2 2 861943334 ns/op 753528 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-2 2 873149030 ns/op 739808 B/op 8940 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-2 1 3373289915 ns/op 3011832 B/op 36999 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-2 1 3503746448 ns/op 11401296 B/op 37009 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-2 1 3489768619 ns/op 11474680 B/op 37029 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-2 1 6448527904 ns/op 18339520 B/op 72101 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-2 1 6381750844 ns/op 9877544 B/op 72071 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-2 1 6457398161 ns/op 14145984 B/op 72103 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-2 6288 191696 ns/op 891 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-2 6184 187655 ns/op 880 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-2 5611 189197 ns/op 880 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-2 2605 472455 ns/op 2072 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-2 2533 475968 ns/op 2086 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-2 2550 480658 ns/op 2086 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-2 5146 237218 ns/op 1527 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-2 5144 239441 ns/op 1527 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-2 5262 238271 ns/op 1520 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-2 6147 198359 ns/op 1323 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-2 5888 199718 ns/op 1312 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-2 6040 199706 ns/op 1312 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-2 2664 470174 ns/op 2003 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-2 2542 469469 ns/op 1976 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-2 2596 463920 ns/op 1976 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-2 1551 782886 ns/op 4486 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-2 1621 779427 ns/op 4417 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-2 1560 776730 ns/op 4463 B/op 104 allocs/op +BenchmarkSlugSearchCacheSearch_50k-2 42 28527451 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-2 42 28463960 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-2 40 28494050 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_250k-2 8 143076654 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-2 8 142030114 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-2 8 142781641 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheBuild-2 1 1018169003 ns/op 26955264 B/op 599363 allocs/op +BenchmarkSlugSearchCacheBuild-2 1 1005127251 ns/op 26955392 B/op 599365 allocs/op +BenchmarkSlugSearchCacheBuild-2 1 1010851248 ns/op 26955136 B/op 599363 allocs/op +BenchmarkSlugSearchCacheMemory-2 4 253100924 ns/op 12.56 MB 6738828 B/op 149841 allocs/op +BenchmarkSlugSearchCacheMemory-2 3 406687288 ns/op 12.56 MB 8985109 B/op 199788 allocs/op +BenchmarkSlugSearchCacheMemory-2 1 1060740368 ns/op 12.56 MB 26955376 B/op 599365 allocs/op +BenchmarkSlugSearchCacheSearch_500k-2 4 289555155 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-2 4 287683083 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-2 4 287436434 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheBuild_500k-2 1 1987141285 ns/op 53871144 B/op 1199115 allocs/op +BenchmarkSlugSearchCacheBuild_500k-2 1 1964392257 ns/op 53871160 B/op 1199115 allocs/op +BenchmarkSlugSearchCacheBuild_500k-2 1 1991098792 ns/op 53871160 B/op 1199115 allocs/op +BenchmarkSlugSearchCacheMemory_500k-2 1 1971939115 ns/op 25.26 MB 53871144 B/op 1199115 allocs/op +BenchmarkSlugSearchCacheMemory_500k-2 1 1979871671 ns/op 25.26 MB 53871208 B/op 1199115 allocs/op +BenchmarkSlugSearchCacheMemory_500k-2 1 1961665910 ns/op 25.26 MB 53871080 B/op 1199115 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-2 4 279422724 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-2 4 284591450 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-2 4 282751031 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-2 4 289009188 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-2 4 290455357 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-2 4 289897310 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-2 4 292557650 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-2 4 291074105 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-2 4 287107012 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-2 4 268605224 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-2 4 268299620 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-2 4 272853094 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-2 4 287818662 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-2 4 287349479 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-2 4 290690863 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-2 8 138697050 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-2 8 138746050 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-2 8 137126312 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-2 4 263222095 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-2 4 260896741 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-2 4 265318318 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-2 6 178374470 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-2 6 179606410 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-2 6 178935054 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-2 5 220508137 ns/op 145441 B/op 7 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-2 5 220062351 ns/op 145441 B/op 7 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-2 5 213609929 ns/op 145441 B/op 7 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-2 2 840635776 ns/op 3697568 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-2 2 848242221 ns/op 3697568 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-2 2 835867540 ns/op 3697568 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-2 1 4238365783 ns/op 7039816 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-2 1 4417826718 ns/op 7039832 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-2 1 4285577084 ns/op 7039832 B/op 319816 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediascanner +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkGetPathFragments/Simple-2 1911 636321 ns/op 3878 B/op 115 allocs/op +BenchmarkGetPathFragments/Simple-2 1860 652315 ns/op 3860 B/op 115 allocs/op +BenchmarkGetPathFragments/Simple-2 1933 642046 ns/op 3841 B/op 115 allocs/op +BenchmarkGetPathFragments/Complex-2 1114 1068018 ns/op 5321 B/op 166 allocs/op +BenchmarkGetPathFragments/Complex-2 1130 1066349 ns/op 5385 B/op 166 allocs/op +BenchmarkGetPathFragments/Complex-2 1125 1067499 ns/op 5321 B/op 166 allocs/op +BenchmarkGetPathFragments/Long_path-2 825 1475156 ns/op 5954 B/op 175 allocs/op +BenchmarkGetPathFragments/Long_path-2 818 1488613 ns/op 5954 B/op 175 allocs/op +BenchmarkGetPathFragments/Long_path-2 807 1471865 ns/op 5953 B/op 175 allocs/op +BenchmarkGetPathFragments/Scene_release-2 1724 701614 ns/op 3897 B/op 118 allocs/op +BenchmarkGetPathFragments/Scene_release-2 1750 693960 ns/op 3917 B/op 118 allocs/op +BenchmarkGetPathFragments/Scene_release-2 1701 693863 ns/op 3918 B/op 118 allocs/op +BenchmarkGetPathFragments/URI_scheme-2 2102 558579 ns/op 3697 B/op 114 allocs/op +BenchmarkGetPathFragments/URI_scheme-2 2133 561208 ns/op 3697 B/op 114 allocs/op +BenchmarkGetPathFragments/URI_scheme-2 2146 558728 ns/op 3714 B/op 114 allocs/op +BenchmarkGetPathFragments/NoExt-2 1897 636688 ns/op 3879 B/op 115 allocs/op +BenchmarkGetPathFragments/NoExt-2 1926 645982 ns/op 3859 B/op 115 allocs/op +BenchmarkGetPathFragments/NoExt-2 1899 644524 ns/op 3860 B/op 115 allocs/op +BenchmarkGetPathFragments/CJK-2 2034 595130 ns/op 4625 B/op 116 allocs/op +BenchmarkGetPathFragments/CJK-2 2102 596104 ns/op 4642 B/op 116 allocs/op +BenchmarkGetPathFragments/CJK-2 2065 597546 ns/op 4625 B/op 116 allocs/op +BenchmarkGetPathFragments_Batch/10k-2 1 9259983931 ns/op 46710424 B/op 1351172 allocs/op +BenchmarkGetPathFragments_Batch/10k-2 1 9245880856 ns/op 46746024 B/op 1351171 allocs/op +BenchmarkGetPathFragments_Batch/10k-2 1 9287454099 ns/op 46745992 B/op 1351170 allocs/op +BenchmarkGetPathFragments_Batch/50k-2 1 47524623948 ns/op 233869360 B/op 6759525 allocs/op +BenchmarkGetPathFragments_Batch/50k-2 1 47791143816 ns/op 234049856 B/op 6759563 allocs/op +BenchmarkGetPathFragments_Batch/50k-2 1 47414805904 ns/op 234012352 B/op 6759527 allocs/op +BenchmarkFlushScanStateMaps/10k-2 1586 741959 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-2 1630 744531 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-2 1633 739910 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-2 157 7469322 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-2 126 9206368 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-2 152 7119622 ns/op 0 B/op 0 allocs/op +BenchmarkAddMediaPath_MockDB/1k-2 1 2405530671 ns/op 13142560 B/op 310072 allocs/op +BenchmarkAddMediaPath_MockDB/1k-2 1 2371268829 ns/op 13141152 B/op 310055 allocs/op +BenchmarkAddMediaPath_MockDB/1k-2 1 2371089926 ns/op 13141456 B/op 310057 allocs/op +BenchmarkAddMediaPath_MockDB/10k-2 1 23844927269 ns/op 133219472 B/op 3084786 allocs/op +BenchmarkAddMediaPath_MockDB/10k-2 1 23545772740 ns/op 133183136 B/op 3084784 allocs/op +BenchmarkAddMediaPath_MockDB/10k-2 1 23603488913 ns/op 133187496 B/op 3084814 allocs/op +BenchmarkAddMediaPath_RealDB/1k-2 1 3868430393 ns/op 21052200 B/op 212852 allocs/op +BenchmarkAddMediaPath_RealDB/1k-2 1 3875524602 ns/op 21069320 B/op 212844 allocs/op +BenchmarkAddMediaPath_RealDB/1k-2 1 3839949547 ns/op 12556000 B/op 212805 allocs/op +BenchmarkAddMediaPath_RealDB/10k-2 1 32156318653 ns/op 80265872 B/op 2008584 allocs/op +BenchmarkAddMediaPath_RealDB/10k-2 1 32133895363 ns/op 84435672 B/op 2014543 allocs/op +BenchmarkAddMediaPath_RealDB/10k-2 1 32213011879 ns/op 80290272 B/op 2002662 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-2 1 31923967812 ns/op 84493624 B/op 2008578 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-2 1 32071674664 ns/op 80196416 B/op 2014584 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-2 1 32413381367 ns/op 80130640 B/op 2014572 allocs/op +BenchmarkGetPathFragments_PeakMemory-2 1 48720592183 ns/op 225.0 total-MB 241058128 B/op 7059139 allocs/op +BenchmarkGetPathFragments_PeakMemory-2 1 49148701573 ns/op 225.1 total-MB 241130440 B/op 7059156 allocs/op +BenchmarkGetPathFragments_PeakMemory-2 1 48896386705 ns/op 225.0 total-MB 241058792 B/op 7059134 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/readers/shared/ndef +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkParseToText_TextRecord-2 923380 1134 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-2 1000000 1114 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-2 901794 1130 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_URIRecord-2 496732 2549 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-2 453600 2572 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-2 528885 2558 ns/op 96 B/op 2 allocs/op +BenchmarkValidateNDEFMessage-2 23283855 48.65 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-2 25006113 48.65 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-2 24964978 48.24 ns/op 0 B/op 0 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/service +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkMatchMapping_Exact_50rules-2 3634 343750 ns/op 2832 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-2 3379 347929 ns/op 2832 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-2 3664 346150 ns/op 2832 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-2 1248 913622 ns/op 3542 B/op 219 allocs/op +BenchmarkMatchMapping_Regex_50rules-2 1336 901381 ns/op 3403 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-2 1328 911598 ns/op 3403 B/op 218 allocs/op +BenchmarkMatchMapping_Mixed_100rules-2 1526 807497 ns/op 5697 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-2 1503 801893 ns/op 5696 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-2 1560 798321 ns/op 5696 B/op 412 allocs/op +BenchmarkScanToLaunch_ExactMatch-2 80 14804053 ns/op 35803 B/op 532 allocs/op +BenchmarkScanToLaunch_ExactMatch-2 79 14404938 ns/op 33055 B/op 432 allocs/op +BenchmarkScanToLaunch_ExactMatch-2 79 14881256 ns/op 32632 B/op 432 allocs/op +BenchmarkScanToLaunch_DirectPath-2 499 2602010 ns/op 30524 B/op 387 allocs/op +BenchmarkScanToLaunch_DirectPath-2 465 2619254 ns/op 30301 B/op 387 allocs/op +BenchmarkScanToLaunch_DirectPath-2 435 2705732 ns/op 30398 B/op 387 allocs/op +BenchmarkScanToLaunch_WithMapping-2 80 14431944 ns/op 30553 B/op 406 allocs/op +BenchmarkScanToLaunch_WithMapping-2 84 14732362 ns/op 30440 B/op 406 allocs/op +BenchmarkScanToLaunch_WithMapping-2 80 14403589 ns/op 30552 B/op 406 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/slugs +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-2 4231 288045 ns/op 1249 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-2 4310 286471 ns/op 1232 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-2 4246 284419 ns/op 1249 B/op 43 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-2 4092 329518 ns/op 10517 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-2 3963 304083 ns/op 10500 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-2 3464 325215 ns/op 10483 B/op 47 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-2 6859 171057 ns/op 842 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-2 7117 171736 ns/op 832 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-2 7171 172913 ns/op 842 B/op 37 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-2 6705 181819 ns/op 869 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-2 6268 178047 ns/op 875 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-2 6423 182128 ns/op 869 B/op 38 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-2 6932 175597 ns/op 800 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-2 6626 176143 ns/op 805 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-2 6936 175744 ns/op 800 B/op 36 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-2 2260 549053 ns/op 3368 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-2 2125 546388 ns/op 3352 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-2 2254 544901 ns/op 3352 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-2 2606 448414 ns/op 2524 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-2 2685 450851 ns/op 2496 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-2 2696 446148 ns/op 2496 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-2 1548 773945 ns/op 4599 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-2 1578 767719 ns/op 4575 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-2 1576 774663 ns/op 4552 B/op 70 allocs/op +BenchmarkSlugify_CJK/Japanese-2 5665 219145 ns/op 1533 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-2 5091 220282 ns/op 1520 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-2 5035 220578 ns/op 1520 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-2 5888 186902 ns/op 1318 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-2 5773 186092 ns/op 1324 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-2 5437 185212 ns/op 1325 B/op 30 allocs/op +BenchmarkSlugify_CJK/Korean-2 3219 377393 ns/op 1664 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-2 3168 375448 ns/op 1664 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-2 3058 380896 ns/op 1664 B/op 35 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-2 2984 398207 ns/op 1716 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-2 3093 395044 ns/op 1716 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-2 3108 397229 ns/op 1727 B/op 37 allocs/op +BenchmarkSlugify_Batch_500k-2 1 107684394583 ns/op 493592360 B/op 19317628 allocs/op +BenchmarkSlugify_Batch_500k-2 1 108033697964 ns/op 493628472 B/op 19317635 allocs/op +BenchmarkSlugify_Batch_500k-2 1 107949470539 ns/op 493736720 B/op 19317660 allocs/op +BenchmarkNormalizeToWords/Simple-2 8052 141124 ns/op 697 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-2 9072 136454 ns/op 688 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-2 8090 137688 ns/op 688 B/op 28 allocs/op +BenchmarkNormalizeToWords/With_metadata-2 4939 220769 ns/op 1040 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-2 5181 218834 ns/op 1047 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-2 5504 220036 ns/op 1040 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-2 6820 183897 ns/op 970 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-2 6452 183920 ns/op 965 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-2 6230 185355 ns/op 960 B/op 33 allocs/op +BenchmarkNormalizeToWords/Ampersand-2 9162 137242 ns/op 704 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-2 7402 138078 ns/op 704 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-2 8745 137560 ns/op 704 B/op 28 allocs/op +BenchmarkNormalizeToWords/Long_title-2 3469 342472 ns/op 2106 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-2 3574 348323 ns/op 2096 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-2 3374 343604 ns/op 2117 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Simple-2 5941 184522 ns/op 870 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-2 6063 183715 ns/op 864 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-2 6234 184157 ns/op 875 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Complex-2 4108 288179 ns/op 1232 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-2 4227 286304 ns/op 1240 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-2 3944 285185 ns/op 1232 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/CJK-2 5583 219318 ns/op 1520 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-2 5167 224269 ns/op 1520 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-2 5086 222586 ns/op 1527 B/op 30 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/systemdefs +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkLookupSystemExact-2 419438 2955 ns/op 72 B/op 2 allocs/op +BenchmarkLookupSystemExact-2 429878 3028 ns/op 72 B/op 2 allocs/op +BenchmarkLookupSystemExact-2 432925 3034 ns/op 72 B/op 2 allocs/op +BenchmarkLookupSystemSlug-2 7701 143822 ns/op 785 B/op 37 allocs/op +BenchmarkLookupSystemSlug-2 8302 144048 ns/op 780 B/op 37 allocs/op +BenchmarkLookupSystemSlug-2 8282 144764 ns/op 789 B/op 37 allocs/op +BenchmarkLookupSystemAlias-2 357169 3360 ns/op 80 B/op 2 allocs/op +BenchmarkLookupSystemAlias-2 363573 3415 ns/op 80 B/op 2 allocs/op +BenchmarkLookupSystemAlias-2 373159 3402 ns/op 80 B/op 2 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/tags +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkParseFilenameToCanonicalTags/Simple-2 8973 135075 ns/op 356 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-2 8784 134263 ns/op 352 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-2 8259 136008 ns/op 352 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-2 2318 469975 ns/op 1895 B/op 90 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-2 2593 469107 ns/op 1894 B/op 90 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-2 2596 474599 ns/op 1894 B/op 90 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-2 5716 191446 ns/op 96 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-2 6112 192091 ns/op 96 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-2 6219 194134 ns/op 96 B/op 2 allocs/op +BenchmarkDetectNumberingPattern/100-2 150 8016117 ns/op 22646 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-2 151 7990199 ns/op 22406 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-2 150 8034468 ns/op 22405 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/1000-2 14 81928804 ns/op 223942 B/op 8993 allocs/op +BenchmarkDetectNumberingPattern/1000-2 14 80276311 ns/op 223936 B/op 8993 allocs/op +BenchmarkDetectNumberingPattern/1000-2 14 81117882 ns/op 226515 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/10000-2 2 865483026 ns/op 1088124 B/op 26996 allocs/op +BenchmarkDetectNumberingPattern/10000-2 2 877676947 ns/op 1124344 B/op 27004 allocs/op +BenchmarkDetectNumberingPattern/10000-2 2 873615902 ns/op 1106288 B/op 27000 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-2 6423 188812 ns/op 832 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-2 6102 189232 ns/op 838 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-2 6333 189485 ns/op 837 B/op 19 allocs/op +goos: linux +goarch: arm +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/zapscript/titles +cpu: ARMv7 Processor rev 0 (v7l) +BenchmarkResolveTitle_CacheHit/10k-2 100 12294302 ns/op 3462 B/op 139 allocs/op +BenchmarkResolveTitle_CacheHit/10k-2 100 11823325 ns/op 3464 B/op 139 allocs/op +BenchmarkResolveTitle_CacheHit/10k-2 100 11429425 ns/op 3466 B/op 139 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-2 44 22807767 ns/op 11983 B/op 256 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-2 54 24167290 ns/op 11686 B/op 256 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-2 45 23225198 ns/op 11944 B/op 256 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-2 1 2286797875 ns/op 6840664 B/op 209077 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-2 1 2242247492 ns/op 6875792 B/op 209075 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-2 1 2280638599 ns/op 6912344 B/op 209086 allocs/op diff --git a/testdata/benchmarks/baseline.txt b/testdata/benchmarks/baseline.txt new file mode 100644 index 00000000..ffe1b74f --- /dev/null +++ b/testdata/benchmarks/baseline.txt @@ -0,0 +1,706 @@ +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/config +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkConfig_Read-16 227176653 5.338 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-16 224147365 5.325 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-16 227894455 5.269 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-16 225218870 5.290 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-16 224065615 5.325 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Read-16 227593274 5.262 ns/op 0 B/op 0 allocs/op +BenchmarkConfig_Load-16 95050 12285 ns/op 4939 B/op 22 allocs/op +BenchmarkConfig_Load-16 98173 12268 ns/op 4939 B/op 22 allocs/op +BenchmarkConfig_Load-16 96198 12426 ns/op 4939 B/op 22 allocs/op +BenchmarkConfig_Load-16 95577 12258 ns/op 4939 B/op 22 allocs/op +BenchmarkConfig_Load-16 97832 12360 ns/op 4939 B/op 22 allocs/op +BenchmarkConfig_Load-16 97525 12278 ns/op 4939 B/op 22 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/matcher +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkFindFuzzyMatches_500k-16 226 5207101 ns/op 4937882 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-16 226 5173699 ns/op 4937784 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-16 226 5259453 ns/op 4937784 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-16 226 5286286 ns/op 4937784 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-16 229 5169008 ns/op 4937784 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_500k-16 229 5212586 ns/op 4937784 B/op 41406 allocs/op +BenchmarkFindFuzzyMatches_1M-16 118 9942974 ns/op 8696648 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-16 120 9930649 ns/op 8696648 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-16 115 10263297 ns/op 8696648 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-16 120 9924721 ns/op 8696648 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-16 120 9844638 ns/op 8696648 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_1M-16 118 9932746 ns/op 8696648 B/op 72852 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-16 90 12511605 ns/op 11139016 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-16 96 12799729 ns/op 11139016 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-16 93 12556427 ns/op 11139018 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-16 98 12720050 ns/op 11139016 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-16 92 12672499 ns/op 11139016 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=3-16 93 12227166 ns/op 11139016 B/op 89832 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-16 15 73968361 ns/op 62712280 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-16 14 74648334 ns/op 62712280 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-16 15 75479407 ns/op 62712287 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-16 14 74208568 ns/op 62712280 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-16 14 74295758 ns/op 62712280 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_LengthPreFilter/maxDistance=10-16 15 74975378 ns/op 62712280 B/op 456750 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-16 24 49354105 ns/op 40628560 B/op 228742 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-16 21 48507571 ns/op 40628560 B/op 228742 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-16 24 48492456 ns/op 40628560 B/op 228742 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-16 21 48515953 ns/op 40628560 B/op 228742 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-16 21 48706715 ns/op 40628560 B/op 228742 allocs/op +BenchmarkFindFuzzyMatches_NoMatch_500k-16 21 48911160 ns/op 40628560 B/op 228742 allocs/op +BenchmarkJaroWinkler_Similarity-16 5699025 210.4 ns/op 224 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-16 5550780 211.4 ns/op 224 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-16 5635578 211.3 ns/op 224 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-16 5737680 213.7 ns/op 224 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-16 5510902 213.6 ns/op 224 B/op 2 allocs/op +BenchmarkJaroWinkler_Similarity-16 5670583 209.9 ns/op 224 B/op 2 allocs/op +BenchmarkGenerateTokenSignature_500k-16 1 2174742222 ns/op 519345144 B/op 19422629 allocs/op +BenchmarkGenerateTokenSignature_500k-16 1 2205740504 ns/op 519642216 B/op 19422682 allocs/op +BenchmarkGenerateTokenSignature_500k-16 1 2182250368 ns/op 519456904 B/op 19422654 allocs/op +BenchmarkGenerateTokenSignature_500k-16 1 2186016541 ns/op 519493944 B/op 19422651 allocs/op +BenchmarkGenerateTokenSignature_500k-16 1 2173758812 ns/op 519456712 B/op 19422652 allocs/op +BenchmarkGenerateTokenSignature_500k-16 1 2182314959 ns/op 519382456 B/op 19422638 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediadb +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkBatchInserter_Insert/10k-16 21 48550064 ns/op 22715717 B/op 141758 allocs/op +BenchmarkBatchInserter_Insert/10k-16 22 47196771 ns/op 22570815 B/op 141756 allocs/op +BenchmarkBatchInserter_Insert/10k-16 22 48572730 ns/op 21426538 B/op 141756 allocs/op +BenchmarkBatchInserter_Insert/10k-16 22 47946739 ns/op 21232635 B/op 141755 allocs/op +BenchmarkBatchInserter_Insert/10k-16 24 46729401 ns/op 20739296 B/op 141752 allocs/op +BenchmarkBatchInserter_Insert/10k-16 22 47526817 ns/op 21042283 B/op 141753 allocs/op +BenchmarkBatchInserter_Insert/50k-16 4 252508888 ns/op 82399508 B/op 702712 allocs/op +BenchmarkBatchInserter_Insert/50k-16 5 244848930 ns/op 81786545 B/op 702711 allocs/op +BenchmarkBatchInserter_Insert/50k-16 5 245654944 ns/op 82612204 B/op 702711 allocs/op +BenchmarkBatchInserter_Insert/50k-16 5 247044053 ns/op 82604729 B/op 702716 allocs/op +BenchmarkBatchInserter_Insert/50k-16 5 246132897 ns/op 82625276 B/op 702716 allocs/op +BenchmarkBatchInserter_Insert/50k-16 4 251034666 ns/op 82421572 B/op 702714 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-16 444 2639558 ns/op 299270 B/op 2950 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-16 456 2629506 ns/op 323670 B/op 2950 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-16 447 2672728 ns/op 345822 B/op 2950 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-16 441 2632272 ns/op 328849 B/op 2950 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-16 445 2657806 ns/op 299061 B/op 2950 allocs/op +BenchmarkBatchInserter_FlushCost/100rows-16 447 2637980 ns/op 317272 B/op 2950 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-16 294 4077205 ns/op 1307696 B/op 8940 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-16 291 4078473 ns/op 1269606 B/op 8940 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-16 291 4143221 ns/op 1313362 B/op 8940 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-16 297 4058848 ns/op 1332433 B/op 8940 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-16 288 4076364 ns/op 1333387 B/op 8940 allocs/op +BenchmarkBatchInserter_FlushCost/1000rows-16 292 4146429 ns/op 1385177 B/op 8940 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-16 88 13461628 ns/op 6179622 B/op 36995 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-16 90 13337239 ns/op 6141997 B/op 36994 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-16 75 13531487 ns/op 6188114 B/op 36995 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-16 80 13837232 ns/op 6453857 B/op 36995 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-16 96 13186553 ns/op 5950680 B/op 36993 allocs/op +BenchmarkBatchInserter_FlushCost/5000rows-16 90 13445214 ns/op 6517695 B/op 36996 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-16 283 4211566 ns/op 2018386 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-16 277 4263134 ns/op 2049813 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-16 286 4115549 ns/op 1787805 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-16 288 4172177 ns/op 1991540 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-16 284 4198795 ns/op 1971856 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/1k-16 282 4229584 ns/op 2020863 B/op 8941 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-16 85 13492452 ns/op 6089609 B/op 36994 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-16 88 13468476 ns/op 6468671 B/op 36995 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-16 78 13410614 ns/op 6394948 B/op 36995 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-16 84 13391031 ns/op 6611568 B/op 36996 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-16 81 13439682 ns/op 6534541 B/op 36995 allocs/op +BenchmarkTransactionCycle_CommitCost/5k-16 82 13313642 ns/op 6252759 B/op 36995 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-16 43 24528053 ns/op 11317000 B/op 72071 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-16 42 24653618 ns/op 11287198 B/op 72070 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-16 50 24348803 ns/op 10903786 B/op 72070 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-16 45 24532235 ns/op 11750765 B/op 72072 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-16 45 24444897 ns/op 11378128 B/op 72072 allocs/op +BenchmarkTransactionCycle_CommitCost/10k-16 43 24300230 ns/op 11121788 B/op 72071 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-16 272719 4291 ns/op 1053 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-16 284764 4340 ns/op 1054 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-16 269744 4304 ns/op 1050 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-16 276866 4350 ns/op 1053 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-16 273144 4406 ns/op 1053 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_simple-16 283840 4317 ns/op 1053 B/op 39 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-16 103712 11267 ns/op 2519 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-16 105096 11317 ns/op 2525 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-16 105523 11265 ns/op 2526 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-16 106472 11507 ns/op 2523 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-16 107866 11271 ns/op 2517 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Latin_complex-16 106512 11369 ns/op 2519 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-16 219175 5472 ns/op 1682 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-16 216780 5438 ns/op 1689 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-16 219266 5482 ns/op 1685 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-16 220676 5566 ns/op 1687 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-16 207327 5426 ns/op 1687 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Japanese-16 222039 5476 ns/op 1687 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-16 263676 4439 ns/op 1476 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-16 269048 4470 ns/op 1475 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-16 244083 4424 ns/op 1472 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-16 267204 4386 ns/op 1479 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-16 250742 4395 ns/op 1477 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/CJK_Chinese-16 268112 4498 ns/op 1472 B/op 30 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-16 108333 11216 ns/op 2422 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-16 105516 11192 ns/op 2422 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-16 105231 11104 ns/op 2429 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-16 103300 11405 ns/op 2429 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-16 105756 11113 ns/op 2419 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/With_secondary-16 109632 11070 ns/op 2423 B/op 80 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-16 60662 20175 ns/op 5111 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-16 58718 20283 ns/op 5125 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-16 60753 20061 ns/op 5107 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-16 59809 20068 ns/op 5110 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-16 60325 20044 ns/op 5108 B/op 104 allocs/op +BenchmarkGenerateSlugWithMetadata/Long_ROM-16 59679 20548 ns/op 5118 B/op 104 allocs/op +BenchmarkSlugSearchCacheSearch_50k-16 1556 779607 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-16 1544 774814 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-16 1538 775852 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-16 1556 780266 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-16 1528 781370 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_50k-16 1549 773405 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_250k-16 300 3986462 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-16 302 3981443 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-16 298 4063399 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-16 298 3975643 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-16 306 3916943 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_250k-16 307 3950418 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheBuild-16 48 23281451 ns/op 26966618 B/op 599358 allocs/op +BenchmarkSlugSearchCacheBuild-16 48 22982489 ns/op 26966652 B/op 599358 allocs/op +BenchmarkSlugSearchCacheBuild-16 44 23005388 ns/op 26966669 B/op 599358 allocs/op +BenchmarkSlugSearchCacheBuild-16 46 22913494 ns/op 26966630 B/op 599358 allocs/op +BenchmarkSlugSearchCacheBuild-16 48 22831976 ns/op 26966674 B/op 599358 allocs/op +BenchmarkSlugSearchCacheBuild-16 46 23032123 ns/op 26966757 B/op 599359 allocs/op +BenchmarkSlugSearchCacheMemory-16 1000000000 0.02485 ns/op 12.56 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory-16 1000000000 0.02183 ns/op 12.56 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory-16 1000000000 0.02500 ns/op 12.56 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory-16 1000000000 0.02415 ns/op 12.56 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory-16 1000000000 0.02525 ns/op 12.56 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory-16 1000000000 0.02184 ns/op 12.56 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheSearch_500k-16 146 8111483 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-16 147 8090463 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-16 147 8057169 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-16 146 8240347 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-16 146 8128137 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_500k-16 147 8094617 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheBuild_500k-16 27 43697938 ns/op 53880766 B/op 1199110 allocs/op +BenchmarkSlugSearchCacheBuild_500k-16 24 43809391 ns/op 53880700 B/op 1199110 allocs/op +BenchmarkSlugSearchCacheBuild_500k-16 25 43959425 ns/op 53880710 B/op 1199110 allocs/op +BenchmarkSlugSearchCacheBuild_500k-16 27 43648543 ns/op 53880700 B/op 1199110 allocs/op +BenchmarkSlugSearchCacheBuild_500k-16 25 44279638 ns/op 53880678 B/op 1199109 allocs/op +BenchmarkSlugSearchCacheBuild_500k-16 26 44246150 ns/op 53880721 B/op 1199110 allocs/op +BenchmarkSlugSearchCacheMemory_500k-16 1000000000 0.04758 ns/op 25.26 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory_500k-16 1000000000 0.04599 ns/op 25.26 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory_500k-16 1000000000 0.04488 ns/op 25.26 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory_500k-16 1000000000 0.04564 ns/op 25.26 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory_500k-16 1000000000 0.04191 ns/op 25.26 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheMemory_500k-16 1000000000 0.04718 ns/op 25.26 MB 0 B/op 0 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-16 158 7496184 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-16 157 7570040 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-16 159 7499437 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-16 160 7451545 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-16 159 7521846 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/SingleWord-16 158 7486846 ns/op 2160640 B/op 15 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-16 147 8111127 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-16 147 8101706 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-16 147 8185017 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-16 146 8165311 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-16 146 8138701 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/TwoWords-16 148 8039490 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-16 147 8089324 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-16 146 8128379 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-16 150 7995603 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-16 148 8026151 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-16 150 7965025 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/ThreeWords-16 147 8120003 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-16 169 7024250 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-16 169 7030819 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-16 171 6970014 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-16 171 6969954 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-16 171 6988300 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_QueryComplexity/CommonSubstring-16 170 7019046 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-16 146 8123839 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-16 147 8102208 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-16 146 8188056 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-16 145 8164024 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-16 148 8054318 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/NoFilter-16 147 8140592 ns/op 145408 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-16 328 3719948 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-16 327 3629767 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-16 328 3608377 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-16 333 3604646 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-16 328 3620380 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/SingleSystem-16 328 3630542 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-16 151 7898335 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-16 151 7890790 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-16 151 7946855 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-16 151 7877409 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-16 152 7877580 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_SystemFiltered_500k/ThreeSystems-16 152 7878780 ns/op 63488 B/op 4 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-16 141 8462744 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-16 141 8419248 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-16 141 8407661 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-16 144 8315876 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-16 142 8403034 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_NoMatch_500k-16 144 8341252 ns/op 8192 B/op 1 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-16 1262 919051 ns/op 145409 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-16 1258 964900 ns/op 145409 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-16 1114 962090 ns/op 145409 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-16 1203 914065 ns/op 145413 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-16 1255 917612 ns/op 145409 B/op 6 allocs/op +BenchmarkSlugSearchCacheSearch_Concurrent_500k-16 1224 917228 ns/op 145409 B/op 6 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-16 202 5878764 ns/op 3954330 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-16 196 6034463 ns/op 3954330 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-16 200 5937531 ns/op 3954330 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-16 201 5951884 ns/op 3954329 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-16 200 5963176 ns/op 3954335 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/10k-16 201 5899321 ns/op 3954335 B/op 63816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-16 43 27524523 ns/op 8320569 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-16 43 28280098 ns/op 8320572 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-16 44 27997254 ns/op 8320573 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-16 44 27772385 ns/op 8320561 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-16 44 27851005 ns/op 8320569 B/op 319816 allocs/op +BenchmarkSlugSearchCacheBuild_FromDB/50k-16 44 27932211 ns/op 8320562 B/op 319816 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/mediascanner +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkGetPathFragments/Simple-16 71114 16370 ns/op 6058 B/op 116 allocs/op +BenchmarkGetPathFragments/Simple-16 72738 16300 ns/op 6057 B/op 116 allocs/op +BenchmarkGetPathFragments/Simple-16 70455 16520 ns/op 6067 B/op 116 allocs/op +BenchmarkGetPathFragments/Simple-16 74145 16320 ns/op 6062 B/op 116 allocs/op +BenchmarkGetPathFragments/Simple-16 72921 16199 ns/op 6066 B/op 116 allocs/op +BenchmarkGetPathFragments/Simple-16 72968 16321 ns/op 6063 B/op 116 allocs/op +BenchmarkGetPathFragments/Complex-16 41281 28690 ns/op 8244 B/op 169 allocs/op +BenchmarkGetPathFragments/Complex-16 41751 29221 ns/op 8245 B/op 169 allocs/op +BenchmarkGetPathFragments/Complex-16 41196 29152 ns/op 8250 B/op 169 allocs/op +BenchmarkGetPathFragments/Complex-16 40807 30996 ns/op 8268 B/op 169 allocs/op +BenchmarkGetPathFragments/Complex-16 40390 29190 ns/op 8253 B/op 169 allocs/op +BenchmarkGetPathFragments/Complex-16 40789 29699 ns/op 8262 B/op 169 allocs/op +BenchmarkGetPathFragments/Long_path-16 27343 42849 ns/op 8785 B/op 176 allocs/op +BenchmarkGetPathFragments/Long_path-16 28638 41039 ns/op 8783 B/op 176 allocs/op +BenchmarkGetPathFragments/Long_path-16 29802 40894 ns/op 8785 B/op 176 allocs/op +BenchmarkGetPathFragments/Long_path-16 29654 40709 ns/op 8767 B/op 176 allocs/op +BenchmarkGetPathFragments/Long_path-16 29371 41162 ns/op 8790 B/op 176 allocs/op +BenchmarkGetPathFragments/Long_path-16 29652 41421 ns/op 8765 B/op 176 allocs/op +BenchmarkGetPathFragments/Scene_release-16 70154 17177 ns/op 6135 B/op 119 allocs/op +BenchmarkGetPathFragments/Scene_release-16 71988 16995 ns/op 6140 B/op 119 allocs/op +BenchmarkGetPathFragments/Scene_release-16 69634 17081 ns/op 6143 B/op 119 allocs/op +BenchmarkGetPathFragments/Scene_release-16 71221 17250 ns/op 6140 B/op 119 allocs/op +BenchmarkGetPathFragments/Scene_release-16 70186 17087 ns/op 6137 B/op 119 allocs/op +BenchmarkGetPathFragments/Scene_release-16 68956 17280 ns/op 6134 B/op 119 allocs/op +BenchmarkGetPathFragments/URI_scheme-16 81555 13850 ns/op 5939 B/op 115 allocs/op +BenchmarkGetPathFragments/URI_scheme-16 90573 13431 ns/op 5942 B/op 115 allocs/op +BenchmarkGetPathFragments/URI_scheme-16 88237 13506 ns/op 5939 B/op 115 allocs/op +BenchmarkGetPathFragments/URI_scheme-16 88690 13318 ns/op 5938 B/op 115 allocs/op +BenchmarkGetPathFragments/URI_scheme-16 86011 13936 ns/op 5928 B/op 115 allocs/op +BenchmarkGetPathFragments/URI_scheme-16 88178 13328 ns/op 5934 B/op 115 allocs/op +BenchmarkGetPathFragments/NoExt-16 74520 16392 ns/op 6064 B/op 116 allocs/op +BenchmarkGetPathFragments/NoExt-16 69782 17024 ns/op 6071 B/op 116 allocs/op +BenchmarkGetPathFragments/NoExt-16 71274 17225 ns/op 6075 B/op 116 allocs/op +BenchmarkGetPathFragments/NoExt-16 71022 16533 ns/op 6070 B/op 116 allocs/op +BenchmarkGetPathFragments/NoExt-16 71856 16801 ns/op 6064 B/op 116 allocs/op +BenchmarkGetPathFragments/NoExt-16 72812 17004 ns/op 6078 B/op 116 allocs/op +BenchmarkGetPathFragments/CJK-16 79195 14589 ns/op 6955 B/op 117 allocs/op +BenchmarkGetPathFragments/CJK-16 83684 14761 ns/op 6956 B/op 117 allocs/op +BenchmarkGetPathFragments/CJK-16 77812 14786 ns/op 6953 B/op 117 allocs/op +BenchmarkGetPathFragments/CJK-16 78336 14476 ns/op 6963 B/op 117 allocs/op +BenchmarkGetPathFragments/CJK-16 83208 14238 ns/op 6953 B/op 117 allocs/op +BenchmarkGetPathFragments/CJK-16 80750 14489 ns/op 6955 B/op 117 allocs/op +BenchmarkGetPathFragments_Batch/10k-16 4 265821097 ns/op 71222872 B/op 1365375 allocs/op +BenchmarkGetPathFragments_Batch/10k-16 4 267052618 ns/op 71439756 B/op 1365421 allocs/op +BenchmarkGetPathFragments_Batch/10k-16 4 266092980 ns/op 71347728 B/op 1365418 allocs/op +BenchmarkGetPathFragments_Batch/10k-16 4 268631296 ns/op 71301072 B/op 1365402 allocs/op +BenchmarkGetPathFragments_Batch/10k-16 4 263892646 ns/op 71348642 B/op 1365415 allocs/op +BenchmarkGetPathFragments_Batch/10k-16 4 263200428 ns/op 71233066 B/op 1365371 allocs/op +BenchmarkGetPathFragments_Batch/50k-16 1 1246880520 ns/op 353095640 B/op 6829556 allocs/op +BenchmarkGetPathFragments_Batch/50k-16 1 1260731290 ns/op 353323384 B/op 6829670 allocs/op +BenchmarkGetPathFragments_Batch/50k-16 1 1276988156 ns/op 353246440 B/op 6829625 allocs/op +BenchmarkGetPathFragments_Batch/50k-16 1 1298459374 ns/op 353209896 B/op 6829617 allocs/op +BenchmarkGetPathFragments_Batch/50k-16 1 1278926795 ns/op 352719216 B/op 6829501 allocs/op +BenchmarkGetPathFragments_Batch/50k-16 1 1255160672 ns/op 352755152 B/op 6829484 allocs/op +BenchmarkFlushScanStateMaps/10k-16 20710 57565 ns/op 1 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-16 20844 57843 ns/op 1 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-16 20902 57623 ns/op 1 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-16 20748 58132 ns/op 1 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-16 20289 58879 ns/op 1 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/10k-16 20720 57937 ns/op 1 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-16 8122 206345 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-16 7814 190016 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-16 7951 160186 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-16 7564 157960 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-16 8286 193490 ns/op 0 B/op 0 allocs/op +BenchmarkFlushScanStateMaps/50k-16 7990 181728 ns/op 0 B/op 0 allocs/op +BenchmarkAddMediaPath_MockDB/1k-16 19 55360847 ns/op 21862190 B/op 314378 allocs/op +BenchmarkAddMediaPath_MockDB/1k-16 20 54316107 ns/op 21717204 B/op 314377 allocs/op +BenchmarkAddMediaPath_MockDB/1k-16 19 54842181 ns/op 21833936 B/op 314367 allocs/op +BenchmarkAddMediaPath_MockDB/1k-16 20 54843975 ns/op 21718889 B/op 314376 allocs/op +BenchmarkAddMediaPath_MockDB/1k-16 19 55242369 ns/op 21865909 B/op 314380 allocs/op +BenchmarkAddMediaPath_MockDB/1k-16 20 54495844 ns/op 21710912 B/op 314372 allocs/op +BenchmarkAddMediaPath_MockDB/10k-16 2 539341074 ns/op 215790688 B/op 3128584 allocs/op +BenchmarkAddMediaPath_MockDB/10k-16 2 541286642 ns/op 215766120 B/op 3128557 allocs/op +BenchmarkAddMediaPath_MockDB/10k-16 2 570343781 ns/op 215639512 B/op 3128530 allocs/op +BenchmarkAddMediaPath_MockDB/10k-16 2 568658623 ns/op 215806148 B/op 3128599 allocs/op +BenchmarkAddMediaPath_MockDB/10k-16 2 583027929 ns/op 215696016 B/op 3128547 allocs/op +BenchmarkAddMediaPath_MockDB/10k-16 2 566603998 ns/op 215865332 B/op 3128606 allocs/op +BenchmarkAddMediaPath_RealDB/1k-16 22 47858326 ns/op 21073261 B/op 213592 allocs/op +BenchmarkAddMediaPath_RealDB/1k-16 25 47058654 ns/op 20750879 B/op 213799 allocs/op +BenchmarkAddMediaPath_RealDB/1k-16 25 46785344 ns/op 21254314 B/op 213671 allocs/op +BenchmarkAddMediaPath_RealDB/1k-16 24 46377732 ns/op 21603820 B/op 213781 allocs/op +BenchmarkAddMediaPath_RealDB/1k-16 26 44046932 ns/op 18320297 B/op 213778 allocs/op +BenchmarkAddMediaPath_RealDB/1k-16 25 45143649 ns/op 20248662 B/op 213983 allocs/op +BenchmarkAddMediaPath_RealDB/10k-16 3 407619927 ns/op 116681752 B/op 2028747 allocs/op +BenchmarkAddMediaPath_RealDB/10k-16 3 424070762 ns/op 115274704 B/op 2026741 allocs/op +BenchmarkAddMediaPath_RealDB/10k-16 3 424098470 ns/op 115242424 B/op 2024763 allocs/op +BenchmarkAddMediaPath_RealDB/10k-16 3 443685014 ns/op 115378392 B/op 2024801 allocs/op +BenchmarkAddMediaPath_RealDB/10k-16 3 425490344 ns/op 115324613 B/op 2022784 allocs/op +BenchmarkAddMediaPath_RealDB/10k-16 3 432541472 ns/op 116664344 B/op 2024779 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-16 3 430214186 ns/op 118046674 B/op 2020824 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-16 3 423121004 ns/op 116561866 B/op 2028757 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-16 3 429348532 ns/op 116586253 B/op 2024776 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-16 3 423408839 ns/op 116479178 B/op 2022756 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-16 3 425794909 ns/op 115279466 B/op 2026772 allocs/op +BenchmarkIndexingPipeline_EndToEnd/10k_1sys-16 3 430034393 ns/op 116475658 B/op 2022732 allocs/op +BenchmarkGetPathFragments_PeakMemory-16 1 1238585219 ns/op 340.2 total-MB 364265680 B/op 7128986 allocs/op +BenchmarkGetPathFragments_PeakMemory-16 1 1233952088 ns/op 340.4 total-MB 364415440 B/op 7129025 allocs/op +BenchmarkGetPathFragments_PeakMemory-16 1 1218514926 ns/op 340.1 total-MB 364190320 B/op 7128950 allocs/op +BenchmarkGetPathFragments_PeakMemory-16 1 1234001358 ns/op 340.1 total-MB 364116352 B/op 7128950 allocs/op +BenchmarkGetPathFragments_PeakMemory-16 1 1254625113 ns/op 340.1 total-MB 364154640 B/op 7128975 allocs/op +BenchmarkGetPathFragments_PeakMemory-16 1 1220482945 ns/op 340.2 total-MB 364266208 B/op 7128999 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/slugs +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-16 169689 6863 ns/op 1506 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-16 179914 6866 ns/op 1504 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-16 176973 6883 ns/op 1508 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-16 170901 6804 ns/op 1507 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-16 177496 6779 ns/op 1505 B/op 43 allocs/op +BenchmarkSlugifyBasic/The_Legend_of_Zelda:_Ocarina_of_Time_(USA)_[!]-16 167762 6867 ns/op 1501 B/op 43 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-16 138786 8702 ns/op 11123 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-16 143832 8758 ns/op 11124 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-16 140700 8769 ns/op 11129 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-16 131661 9070 ns/op 11125 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-16 143408 8976 ns/op 11129 B/op 47 allocs/op +BenchmarkSlugifyBasic/Pokémon_Stadium_2-16 131208 8723 ns/op 11128 B/op 47 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-16 287622 4042 ns/op 1002 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-16 272420 4033 ns/op 1001 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-16 304593 4073 ns/op 1004 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-16 290689 4141 ns/op 1002 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-16 308628 4095 ns/op 1001 B/op 37 allocs/op +BenchmarkSlugifyBasic/Final_Fantasy_VII-16 301753 3963 ns/op 1000 B/op 37 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-16 280755 4155 ns/op 1033 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-16 293853 4158 ns/op 1034 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-16 292281 4151 ns/op 1032 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-16 282253 4194 ns/op 1035 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-16 285789 4162 ns/op 1036 B/op 38 allocs/op +BenchmarkSlugifyBasic/Super_Mario_Bros-16 275672 4232 ns/op 1035 B/op 38 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-16 294202 4031 ns/op 968 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-16 279094 4039 ns/op 967 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-16 290902 4117 ns/op 971 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-16 296371 4116 ns/op 971 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-16 298014 4232 ns/op 969 B/op 36 allocs/op +BenchmarkSlugifyBasic/Sonic_&_Knuckles-16 279471 4213 ns/op 971 B/op 36 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-16 82292 14852 ns/op 3797 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-16 79340 14956 ns/op 3804 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-16 75550 14960 ns/op 3805 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-16 80706 15110 ns/op 3806 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-16 83986 14877 ns/op 3801 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/ROM_with_tags-16 80534 14824 ns/op 3799 B/op 61 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-16 101922 11789 ns/op 2901 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-16 107881 11352 ns/op 2895 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-16 105474 11396 ns/op 2900 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-16 106833 11703 ns/op 2900 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-16 104042 11540 ns/op 2895 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Japanese_RPG-16 90033 11862 ns/op 2897 B/op 55 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-16 55762 21772 ns/op 5364 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-16 57868 21459 ns/op 5343 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-16 56238 20979 ns/op 5351 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-16 57784 21055 ns/op 5343 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-16 57680 21344 ns/op 5351 B/op 70 allocs/op +BenchmarkSlugify_LongTitle/Scene_release-16 57414 20969 ns/op 5351 B/op 70 allocs/op +BenchmarkSlugify_CJK/Japanese-16 207517 5582 ns/op 1686 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-16 230176 5251 ns/op 1682 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-16 236540 5270 ns/op 1687 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-16 227132 5389 ns/op 1687 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-16 228060 5560 ns/op 1687 B/op 30 allocs/op +BenchmarkSlugify_CJK/Japanese-16 226796 5528 ns/op 1691 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-16 270247 4451 ns/op 1474 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-16 269430 4452 ns/op 1471 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-16 276811 4429 ns/op 1476 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-16 280778 4512 ns/op 1473 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-16 282014 4442 ns/op 1474 B/op 30 allocs/op +BenchmarkSlugify_CJK/Chinese-16 273366 4296 ns/op 1473 B/op 30 allocs/op +BenchmarkSlugify_CJK/Korean-16 133551 9185 ns/op 1902 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-16 131218 9118 ns/op 1901 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-16 134241 9087 ns/op 1904 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-16 130831 9422 ns/op 1901 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-16 126606 9428 ns/op 1909 B/op 35 allocs/op +BenchmarkSlugify_CJK/Korean-16 127231 9489 ns/op 1904 B/op 35 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-16 115018 10353 ns/op 1920 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-16 111649 10271 ns/op 1915 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-16 119197 10368 ns/op 1918 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-16 116808 10166 ns/op 1913 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-16 124014 10124 ns/op 1919 B/op 37 allocs/op +BenchmarkSlugify_CJK/Mixed_CJK_Latin-16 117622 10225 ns/op 1908 B/op 37 allocs/op +BenchmarkSlugify_Batch_500k-16 1 2515662320 ns/op 586251064 B/op 19317666 allocs/op +BenchmarkSlugify_Batch_500k-16 1 2494811323 ns/op 586197264 B/op 19317640 allocs/op +BenchmarkSlugify_Batch_500k-16 1 2564338014 ns/op 586048800 B/op 19317618 allocs/op +BenchmarkSlugify_Batch_500k-16 1 2600011742 ns/op 586160240 B/op 19317637 allocs/op +BenchmarkSlugify_Batch_500k-16 1 2479328710 ns/op 586271600 B/op 19317652 allocs/op +BenchmarkSlugify_Batch_500k-16 1 2450548226 ns/op 586160016 B/op 19317631 allocs/op +BenchmarkNormalizeToWords/Simple-16 364692 3216 ns/op 842 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-16 383425 3189 ns/op 842 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-16 371844 3195 ns/op 842 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-16 371614 3202 ns/op 842 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-16 390304 3221 ns/op 842 B/op 28 allocs/op +BenchmarkNormalizeToWords/Simple-16 381891 3209 ns/op 844 B/op 28 allocs/op +BenchmarkNormalizeToWords/With_metadata-16 230194 5403 ns/op 1299 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-16 226490 5420 ns/op 1303 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-16 212906 5544 ns/op 1301 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-16 211702 5511 ns/op 1297 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-16 233472 5543 ns/op 1300 B/op 33 allocs/op +BenchmarkNormalizeToWords/With_metadata-16 223287 5510 ns/op 1298 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-16 267938 4519 ns/op 1152 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-16 268030 4548 ns/op 1152 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-16 267421 4546 ns/op 1154 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-16 258562 4619 ns/op 1157 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-16 267189 4557 ns/op 1154 B/op 33 allocs/op +BenchmarkNormalizeToWords/Roman_numerals-16 274437 4555 ns/op 1151 B/op 33 allocs/op +BenchmarkNormalizeToWords/Ampersand-16 371883 3189 ns/op 860 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-16 388911 3315 ns/op 859 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-16 358118 3283 ns/op 860 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-16 388762 3299 ns/op 860 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-16 364858 3339 ns/op 860 B/op 28 allocs/op +BenchmarkNormalizeToWords/Ampersand-16 355849 3307 ns/op 858 B/op 28 allocs/op +BenchmarkNormalizeToWords/Long_title-16 133311 9041 ns/op 2479 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-16 138274 8891 ns/op 2476 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-16 131178 9079 ns/op 2477 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-16 131904 8876 ns/op 2477 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-16 124782 8998 ns/op 2480 B/op 43 allocs/op +BenchmarkNormalizeToWords/Long_title-16 138519 9044 ns/op 2480 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Simple-16 249988 4378 ns/op 1039 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-16 276308 4348 ns/op 1039 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-16 271279 4323 ns/op 1036 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-16 262936 4310 ns/op 1038 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-16 277640 4361 ns/op 1038 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Simple-16 290056 4337 ns/op 1038 B/op 38 allocs/op +BenchmarkSlugifyWithTokens/Complex-16 171063 6945 ns/op 1509 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-16 164527 7113 ns/op 1509 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-16 168714 7141 ns/op 1511 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-16 167740 7202 ns/op 1506 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-16 165955 7182 ns/op 1509 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/Complex-16 169821 7217 ns/op 1512 B/op 43 allocs/op +BenchmarkSlugifyWithTokens/CJK-16 223392 5468 ns/op 1690 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-16 231399 5429 ns/op 1688 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-16 233990 5465 ns/op 1686 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-16 222405 5498 ns/op 1692 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-16 219867 5545 ns/op 1688 B/op 30 allocs/op +BenchmarkSlugifyWithTokens/CJK-16 225477 5514 ns/op 1692 B/op 30 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/systemdefs +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkLookupSystemExact-16 16115260 74.51 ns/op 120 B/op 2 allocs/op +BenchmarkLookupSystemExact-16 17010116 74.44 ns/op 120 B/op 2 allocs/op +BenchmarkLookupSystemExact-16 16100751 73.63 ns/op 120 B/op 2 allocs/op +BenchmarkLookupSystemExact-16 16492275 74.32 ns/op 120 B/op 2 allocs/op +BenchmarkLookupSystemExact-16 15651214 73.24 ns/op 120 B/op 2 allocs/op +BenchmarkLookupSystemExact-16 14706019 73.14 ns/op 120 B/op 2 allocs/op +BenchmarkLookupSystemSlug-16 370530 3203 ns/op 947 B/op 37 allocs/op +BenchmarkLookupSystemSlug-16 344252 3267 ns/op 946 B/op 37 allocs/op +BenchmarkLookupSystemSlug-16 337375 3165 ns/op 945 B/op 37 allocs/op +BenchmarkLookupSystemSlug-16 393228 3161 ns/op 946 B/op 37 allocs/op +BenchmarkLookupSystemSlug-16 362600 3238 ns/op 947 B/op 37 allocs/op +BenchmarkLookupSystemSlug-16 369837 3117 ns/op 947 B/op 37 allocs/op +BenchmarkLookupSystemAlias-16 13921628 82.42 ns/op 128 B/op 2 allocs/op +BenchmarkLookupSystemAlias-16 14382282 83.83 ns/op 128 B/op 2 allocs/op +BenchmarkLookupSystemAlias-16 15190450 81.74 ns/op 128 B/op 2 allocs/op +BenchmarkLookupSystemAlias-16 14900523 81.55 ns/op 128 B/op 2 allocs/op +BenchmarkLookupSystemAlias-16 14792221 80.05 ns/op 128 B/op 2 allocs/op +BenchmarkLookupSystemAlias-16 14993968 81.96 ns/op 128 B/op 2 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/database/tags +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkParseFilenameToCanonicalTags/Simple-16 319167 3723 ns/op 640 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-16 318490 3800 ns/op 639 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-16 313530 3773 ns/op 640 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-16 328666 3689 ns/op 639 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-16 316478 3745 ns/op 641 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Simple-16 314308 3675 ns/op 640 B/op 13 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-16 89606 13711 ns/op 3262 B/op 93 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-16 84764 13855 ns/op 3259 B/op 93 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-16 91347 13572 ns/op 3257 B/op 93 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-16 93410 13159 ns/op 3255 B/op 93 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-16 92354 13362 ns/op 3262 B/op 93 allocs/op +BenchmarkParseFilenameToCanonicalTags/Complex-16 91492 13610 ns/op 3256 B/op 93 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-16 205272 5898 ns/op 192 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-16 204640 5837 ns/op 193 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-16 207896 5781 ns/op 192 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-16 211542 5836 ns/op 193 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-16 206772 5868 ns/op 193 B/op 2 allocs/op +BenchmarkParseFilenameToCanonicalTags/Scene_release-16 202653 5773 ns/op 192 B/op 2 allocs/op +BenchmarkDetectNumberingPattern/100-16 5869 215788 ns/op 43746 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-16 4681 215506 ns/op 43681 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-16 5196 224713 ns/op 43738 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-16 5421 220685 ns/op 43670 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-16 5961 206544 ns/op 43619 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/100-16 6025 213258 ns/op 43702 B/op 900 allocs/op +BenchmarkDetectNumberingPattern/1000-16 501 2323744 ns/op 436357 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/1000-16 538 2182897 ns/op 435938 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/1000-16 546 2215257 ns/op 436981 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/1000-16 541 2214555 ns/op 436948 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/1000-16 559 2257323 ns/op 436407 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/1000-16 546 2238956 ns/op 436024 B/op 8994 allocs/op +BenchmarkDetectNumberingPattern/10000-16 46 25628698 ns/op 2178262 B/op 26998 allocs/op +BenchmarkDetectNumberingPattern/10000-16 48 25544881 ns/op 2170537 B/op 26996 allocs/op +BenchmarkDetectNumberingPattern/10000-16 44 25152284 ns/op 2175497 B/op 26997 allocs/op +BenchmarkDetectNumberingPattern/10000-16 49 25206196 ns/op 2169606 B/op 26996 allocs/op +BenchmarkDetectNumberingPattern/10000-16 46 24841933 ns/op 2174963 B/op 26997 allocs/op +BenchmarkDetectNumberingPattern/10000-16 46 25359782 ns/op 2178170 B/op 26998 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-16 229125 5491 ns/op 1386 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-16 223165 5334 ns/op 1383 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-16 227818 5308 ns/op 1381 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-16 232320 5322 ns/op 1383 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-16 221665 5305 ns/op 1383 B/op 19 allocs/op +BenchmarkFilenameParser_ExtractSpecialPatterns-16 235531 5292 ns/op 1383 B/op 19 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/helpers +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkRegexCacheVsStandard/Standard-16 780414 1599 ns/op 2373 B/op 22 allocs/op +BenchmarkRegexCacheVsStandard/Standard-16 682551 1570 ns/op 2373 B/op 22 allocs/op +BenchmarkRegexCacheVsStandard/Standard-16 737780 1578 ns/op 2371 B/op 22 allocs/op +BenchmarkRegexCacheVsStandard/Standard-16 802898 1618 ns/op 2368 B/op 22 allocs/op +BenchmarkRegexCacheVsStandard/Standard-16 760195 1622 ns/op 2369 B/op 22 allocs/op +BenchmarkRegexCacheVsStandard/Standard-16 745045 1608 ns/op 2367 B/op 22 allocs/op +BenchmarkRegexCacheVsStandard/Cached-16 12686800 93.60 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-16 12400378 93.70 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-16 12845404 100.0 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-16 12842341 93.22 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-16 12671840 94.07 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/Cached-16 12658456 93.81 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-16 12286305 128.2 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-16 12210376 96.87 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-16 8897814 123.0 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-16 12285897 129.7 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-16 12383254 125.2 ns/op 0 B/op 0 allocs/op +BenchmarkRegexCacheVsStandard/GlobalCached-16 9454172 122.1 ns/op 0 B/op 0 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/platforms/shared/kodi +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkIDExtraction/ManualParsing-16 37541112 31.08 ns/op 32 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-16 38541189 31.22 ns/op 32 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-16 38628298 30.86 ns/op 32 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-16 38113039 31.01 ns/op 32 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-16 40913943 30.87 ns/op 32 B/op 1 allocs/op +BenchmarkIDExtraction/ManualParsing-16 38152203 31.02 ns/op 32 B/op 1 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-16 5073752 241.3 ns/op 80 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-16 4838527 238.8 ns/op 80 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-16 4904144 245.7 ns/op 80 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-16 4895127 245.3 ns/op 80 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-16 4986106 246.1 ns/op 80 B/op 2 allocs/op +BenchmarkIDExtraction/ExtractSchemeID-16 4822728 251.4 ns/op 80 B/op 2 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/readers/shared/ndef +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkParseToText_TextRecord-16 40431891 27.81 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-16 38535483 28.38 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-16 41265374 28.47 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-16 37403652 28.17 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-16 44157043 27.47 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_TextRecord-16 42296852 27.38 ns/op 24 B/op 1 allocs/op +BenchmarkParseToText_URIRecord-16 20432566 58.70 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-16 20793781 58.81 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-16 20703864 58.39 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-16 20847220 60.01 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-16 20749591 60.45 ns/op 96 B/op 2 allocs/op +BenchmarkParseToText_URIRecord-16 19471881 60.29 ns/op 96 B/op 2 allocs/op +BenchmarkValidateNDEFMessage-16 935104284 1.301 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-16 920792634 1.291 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-16 910196300 1.292 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-16 923392480 1.256 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-16 955971855 1.256 ns/op 0 B/op 0 allocs/op +BenchmarkValidateNDEFMessage-16 899992117 1.264 ns/op 0 B/op 0 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/service +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkMatchMapping_Exact_50rules-16 118854 9701 ns/op 3632 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-16 123813 9780 ns/op 3632 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-16 124221 9768 ns/op 3632 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-16 123331 9829 ns/op 3632 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-16 121194 9736 ns/op 3632 B/op 218 allocs/op +BenchmarkMatchMapping_Exact_50rules-16 117860 9856 ns/op 3632 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-16 51444 23373 ns/op 4259 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-16 51151 23418 ns/op 4247 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-16 49666 23848 ns/op 4247 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-16 49602 24441 ns/op 4249 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-16 48558 24591 ns/op 4260 B/op 218 allocs/op +BenchmarkMatchMapping_Regex_50rules-16 49128 24580 ns/op 4256 B/op 218 allocs/op +BenchmarkMatchMapping_Mixed_100rules-16 57831 20643 ns/op 7503 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-16 57007 20988 ns/op 7500 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-16 58044 21023 ns/op 7511 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-16 59283 20893 ns/op 7524 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-16 59012 20035 ns/op 7504 B/op 412 allocs/op +BenchmarkMatchMapping_Mixed_100rules-16 59241 20090 ns/op 7503 B/op 412 allocs/op +BenchmarkScanToLaunch_ExactMatch-16 7609 144201 ns/op 45035 B/op 436 allocs/op +BenchmarkScanToLaunch_ExactMatch-16 8547 145070 ns/op 45515 B/op 435 allocs/op +BenchmarkScanToLaunch_ExactMatch-16 7376 146304 ns/op 45100 B/op 435 allocs/op +BenchmarkScanToLaunch_ExactMatch-16 7081 149698 ns/op 45299 B/op 435 allocs/op +BenchmarkScanToLaunch_ExactMatch-16 8092 149116 ns/op 45871 B/op 435 allocs/op +BenchmarkScanToLaunch_ExactMatch-16 8001 149382 ns/op 45881 B/op 435 allocs/op +BenchmarkScanToLaunch_DirectPath-16 19690 60536 ns/op 43174 B/op 389 allocs/op +BenchmarkScanToLaunch_DirectPath-16 19378 60034 ns/op 43233 B/op 389 allocs/op +BenchmarkScanToLaunch_DirectPath-16 20289 60000 ns/op 44116 B/op 389 allocs/op +BenchmarkScanToLaunch_DirectPath-16 21008 56651 ns/op 43946 B/op 389 allocs/op +BenchmarkScanToLaunch_DirectPath-16 21090 56456 ns/op 43919 B/op 389 allocs/op +BenchmarkScanToLaunch_DirectPath-16 21108 56483 ns/op 43888 B/op 389 allocs/op +BenchmarkScanToLaunch_WithMapping-16 8311 135638 ns/op 41757 B/op 409 allocs/op +BenchmarkScanToLaunch_WithMapping-16 7675 135394 ns/op 41827 B/op 409 allocs/op +BenchmarkScanToLaunch_WithMapping-16 8293 135926 ns/op 41738 B/op 409 allocs/op +BenchmarkScanToLaunch_WithMapping-16 8208 136516 ns/op 41799 B/op 409 allocs/op +BenchmarkScanToLaunch_WithMapping-16 8792 136968 ns/op 42229 B/op 409 allocs/op +BenchmarkScanToLaunch_WithMapping-16 8462 139733 ns/op 41677 B/op 409 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/zapscript/advargs +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkParse_GlobalArgs-16 971505 1169 ns/op 488 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-16 1000000 1203 ns/op 488 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-16 887494 1199 ns/op 488 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-16 1000000 1202 ns/op 488 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-16 937125 1182 ns/op 488 B/op 14 allocs/op +BenchmarkParse_GlobalArgs-16 981489 1192 ns/op 488 B/op 14 allocs/op +BenchmarkParse_LaunchArgs-16 222930 5362 ns/op 2850 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-16 232874 5296 ns/op 2854 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-16 232779 5310 ns/op 2859 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-16 225796 5364 ns/op 2855 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-16 225028 5381 ns/op 2855 B/op 79 allocs/op +BenchmarkParse_LaunchArgs-16 228094 5290 ns/op 2857 B/op 79 allocs/op +BenchmarkParse_LaunchSearchArgs-16 223276 5476 ns/op 2750 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-16 218863 5513 ns/op 2751 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-16 208042 5536 ns/op 2752 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-16 224590 5541 ns/op 2747 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-16 223688 5489 ns/op 2747 B/op 80 allocs/op +BenchmarkParse_LaunchSearchArgs-16 212740 5410 ns/op 2744 B/op 80 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/ZaparooProject/zaparoo-core/v2/pkg/zapscript/titles +cpu: AMD Ryzen 7 8700G w/ Radeon 780M Graphics +BenchmarkResolveTitle_CacheHit/10k-16 19180 62470 ns/op 4620 B/op 142 allocs/op +BenchmarkResolveTitle_CacheHit/10k-16 19380 62067 ns/op 4615 B/op 142 allocs/op +BenchmarkResolveTitle_CacheHit/10k-16 19641 61541 ns/op 4605 B/op 142 allocs/op +BenchmarkResolveTitle_CacheHit/10k-16 19476 62037 ns/op 4617 B/op 142 allocs/op +BenchmarkResolveTitle_CacheHit/10k-16 18968 63360 ns/op 4631 B/op 142 allocs/op +BenchmarkResolveTitle_CacheHit/10k-16 18824 63257 ns/op 4611 B/op 142 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-16 3622 306489 ns/op 15489 B/op 262 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-16 3765 313923 ns/op 15458 B/op 262 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-16 3910 311486 ns/op 15489 B/op 262 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-16 3752 309134 ns/op 15481 B/op 262 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-16 3703 312777 ns/op 15476 B/op 262 allocs/op +BenchmarkResolveTitle_ExactMatch/10k-16 3884 305048 ns/op 15435 B/op 262 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-16 37 30718125 ns/op 8959623 B/op 209061 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-16 38 30720853 ns/op 8973685 B/op 209064 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-16 37 30559423 ns/op 8974529 B/op 209064 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-16 38 30712325 ns/op 8963878 B/op 209062 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-16 38 31221164 ns/op 8970734 B/op 209063 allocs/op +BenchmarkResolveTitle_FuzzyFallback/10k-16 37 30749301 ns/op 8972729 B/op 209064 allocs/op