Help us shape the future of Go! Take the 2026 Go Developer Survey

scorecard

command
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: GPL-3.0 Imports: 34 Imported by: 0

README

HTTP latency measurement (experimental)

This opt-in tool measures full response time for configured GET requests. It is only one component of the scorecard; it does not establish fixture parity, profile compatibility, streaming startup, seek, features, or device experience. It makes samples total requests to each server at each specified concurrency, with a 30-second timeout. Non-2xx responses and transport errors are counted; any errors make the comparison inconclusive. p50 and p95 use nearest rank among successful responses. Reference is measured before Ether, so repeat and reverse order externally when assessing cache or time effects. A mixed result means one server won p50 and the other p95. No statistical significance is implied.

Set SCORECARD_CONFIG to a private JSON file outside the repository, or pass -config. Supply endpoint URLs and authentication headers separately for each server. Output contains names and aggregate measurements, never URLs or headers. Use only read-only endpoints; a GET endpoint can still have side effects. Verify that the servers, viewers, media and operations are equivalent before comparison.

{
  "samples": 100,
  "concurrency": [1, 10, 50],
  "scenarios": [{
    "name": "home",
    "reference": {"url": "http://reference/Users/example/Views", "headers": {}},
    "ether": {"url": "http://ether/Users/example/Views", "headers": {}}
  }]
}

Run go run ./cmd/scorecard > report.json. Keep the configuration private; it may contain credentials. This tool does not authenticate or discover IDs.

Experimental local-file fixture

The scorecard build tag adds a disposable local-file resolver and engine and scorecard serve. It must not be used in a production build. Build with CGO_ENABLED=0 go build -tags scorecard ./cmd/scorecard. Provide a fresh data directory containing only scorecard-files.json, and a JSON reference Items listing with ProviderIds, Path, media metadata and UserData. The source maps movie TMDB IDs and show TMDB/season/episode to files; distinct hashes yield distinct release IDs. The loader checks every hash before accepting requests. The listing, manifest and directory are disposable; never point it at a real Ether data directory. Set SCORECARD_USER and SCORECARD_PASSWORD for a test viewer. serve -data DIR -items ITEMS.json listens on loopback ports supplied with -jellyfin and -emby; bind both to loopback in a lab. It does not start the normal background worker lifecycle, and does not publish a Plex identity.

prepare-manifest.py accepts a reference listing, a host media directory, ffprobe and the reference container name. It compares each host hash with the hash calculated inside the reference container before writing the manifest. Mount the same media at /media:ro in the scorecard container. Verify every reference separately and retain the resulting manifest as evidence.

This remains an experimental harness: state copied from the listing is limited to played/progress, reference versions can have independent state even though Ether has one title, selected-source verification must be performed per session, and the stubbed TMDB responses do not reproduce the complete offline parity fixture. Do not treat its results as fairness-certified without checking catalogue, viewer state, selected file, media bytes and client requests on both sides.

Setup hashes and probes the files once, before the handlers listen, and caches probed MediaInfo in the private manifest. Variant lookup reads this prepared metadata; it does not probe during a measured request. On the loopback listener, GET /__scorecard/sessions lists each active playback's session ID, resolved release ID, source ID and expected SHA-256. GET /__scorecard/verify?id=SESSION reads the actual selected MediaSource and returns its SHA-256 and match; run this outside the timed interval. For opaque versions, obtain a handle from the authenticated item's MediaSources response, then query the fixture-only loopback endpoint GET /__scorecard/offer?handle=HANDLE to correlate it with the seeded item, release and expected SHA-256. An offer mapping by itself is not authorization or proof that a handle was offered to the current viewer; only use handles from the authenticated response. The broker session must bind that handle to the same viewer, device, protocol and item. A comparison is valid only if that selected release and source agree and the actual verified hash matches the reference file. Diagnostics contain no credentials or file paths. GET /__scorecard/broker links the protocol PlaySessionId to playback_id and reports the broker plan decision and stream format. When a transcoder is active, it also reports the actual transcoder decision and copy flags. Treat an absent active transcoder decision as unknown for an encoding claim; the plan alone does not prove what ran. client_id and item_id in the session listing allow independent correlation. For a Jellyfin DeviceId X, the broker client ID is 8:jellyfinX. GET /__scorecard/events returns only allowlisted playback/transcoder failure messages and fixed error categories from the in-memory log ring. It omits raw attributes, credentials, media paths and arbitrary error text. Events are process-wide; isolate a request in time before using them as evidence.

For a route-level feature inventory, run scorecard inventory -protocol jellyfin -spec PATH (or emby). The command verifies the input against the repository's pinned OpenAPI SHA-256, mounts the protocol router with a fake identity, and reports every spec operation with exact method/template matching. A missing registration is a review candidate: alternate routes, wildcard handlers, licensing and feature applicability can change the conclusion. A registered route does not establish complete behavior.

Recorded MediaBrowser text GETs can also drive a scenario: set profile_file to a JSON profile under internal/conform/profiles, and set flow and probe to the recorded names. In this mode each target url is a base URL and each variables object supplies its own ID and device substitutions; headers can supply the authenticated token. Missing variables, binary, fixture-only and non-GET probes are rejected. Response content and state still need separate alignment checks before timings can be interpreted as fair comparisons.

Each measurement also reports first-response-byte p50/p95 (from Go's HTTP trace) and a winner for each percentile. Set the same positive read_limit on both targets to stop after at most that many body bytes; completion_scope records this explicitly. Use an explicit Range header when testing a bounded stream. First byte is an HTTP response measurement, not decoded first frame; a first HLS segment requires a separately configured segment request after negotiation.

For full JSON response probes, set spec_file to the pinned OpenAPI JSON, protocol to jellyfin or emby, and each target's spec_path to the corresponding path in that spec (profile-based requests fill this automatically). Every successful response is checked against the documented status, content type and schema, including fields marked required; schema_checked and schema_errors distinguish checks from untested responses. Results with schema errors have an inconclusive latency winner. Validation occurs after the timed read and may affect throughput. It is a spec check, not evidence that every client's strict decoder has run; schemas may leave client-required fields optional. Bounded streaming responses cannot use this mode, and JSON responses larger than 16 MiB count as schema errors. Undocumented method/path combinations are counted separately as schema_uncovered; they are not reported as required-field failures. Either schema failures or uncovered operations make the latency winner inconclusive. lab-profile-browse.py is an opt-in lab driver that reads credentials only from stdin and refuses to time resume/next-up responses whose visible item projections differ. It can compare views by collection type despite different labels; those timings still require the stated workload caveats. For fixture-only session failures, /__scorecard/events may also report a scorecard session stage failure with stage plan, open, or start_stream and a fixed error category. An absent event does not identify the failure; only sentinel errors are classified, and arbitrary error text is not exposed.

For bounded open-loop tests set rate_per_second to the offered arrival rate, repetitions to at least two, and timeout_ms explicitly. Requests retain their intended schedule even when all workers are busy; queue p50/p95/p99 and all_scheduled_* include scheduler delay and failed/timed-out work. The successful-response p50/p95/p99 and first-byte percentiles exclude failed HTTP requests; error and timeout counts and valid-success throughput are reported alongside them, and any error makes the winner inconclusive. Repetitions alternate reference-first and Ether-first. cache_state is an explicit label, not an automatic cache flush. With no rate, the tool uses closed-loop load and cannot measure omitted arrivals; queue values are not meaningful. This is a bounded request generator, not yet a mixed-playback or resource-efficiency soak harness, and it does not establish statistical significance by itself.

For preliminary cgroup-v2 resource evidence, set an absolute cgroup path on both targets. The run rejects different or unlimited CPU/memory limits and different effective CPU sets. Before and after each target run it samples hierarchical CPU and block I/O counters, cgroup memory, and best-effort process RSS/FD/thread/child counts. It reports CPU-seconds per valid successful HTTP operation when the counters are available. Run the generator outside both server cgroups and use fresh, isolated cgroups; otherwise its own work or other jobs contaminate the measurement. An HTTP success is not proof of correct media quality or an SDK-valid response. Counter regressions and incomplete process snapshots make the latency winner inconclusive.

For growth and peak observation use scorecard resources -cgroup /absolute/path -interval 1s -samples 7201 in a separately coordinated run. This emits a machine-readable cgroup-v2 time series; interrupted runs have complete=false. Memory.current includes cache, RSS sums can double-count shared pages, and memory.peak may predate the run unless the cgroup is fresh. The resources subcommand does not collect per-container network I/O, GPU utilization or Go goroutine counts. FFmpeg children are included only if they remain in the measured cgroup. No extended comparative soak should run without aligned fixtures, resource isolation and the coordinator's reservation.

Soak, fault and leak gates

scorecard soak (scorecard build tag) starts a disposable Ether with Plex, Jellyfin and Emby on loopback, drives mixed browse and direct, remux and transcode playback with session churn, and injects a source outage, a slow read, an encoder crash, an encoder-guardian kill and a SQLite writer stall. scorecard soak-eval -input manifest.json applies the same gates to a saved manifest. Pass requires the injected faults, a Plex-majority mix, zero unexpected errors outside fault windows, three verified playback starts within 30 seconds of fault removal followed by a clean five minutes, cleanup within five minutes (sessions and encoder processes back to baseline, FD delta at most 5, retry queue not growing), goroutines within 50 of baseline, and monotonic network counters. GPU is recorded as no_device, dri_present or nvidia_present; a missing scope fails. The two-hour RSS gate is not_run on the shakedown profile and is required on sustained. A shakedown pass does not claim the two-hour run. Profiles: -profile shakedown (about 22 minutes plus cleanup) and -profile sustained (two hours of steady load before the same fault and recovery sequence). The manifest and report contain no credentials.

When each quantile has enough tail observations (n·p·(1−p) >= 10), the collector emits approximate 95% binomial order-statistic intervals for successful-response and first-byte percentiles. These intervals assume independent samples and can understate uncertainty for correlated requests; point-estimate winners are not significance tests. Missing intervals mean the sample count is insufficient, not zero uncertainty. The lab drivers attempt logout for every successfully authenticated session; a logout failure is recorded without credentials and exits nonzero. Earlier reports created before this check do not prove their reference grants were revoked. The offline lifecycle test is python3 -B cmd/scorecard/lab_cleanup_test.py.

scorecard acceptance -input FILE evaluates predeclared recovery, growth and cleanup evidence. Recovery requires three consecutive verified successes from distinct clients within 30 seconds, a continuously observed five-minute follow-up, and at least 211 distinct matched trials before comparing p95 with approximate order-statistic intervals. Overlap is inconclusive unless the full interval of differences falls within a predeclared positive tolerance. RSS checks require a complete two-hour series with at most 30-second gaps and apply the predeclared final-window drift/slope bounds. Cleanup requires continuous five-minute observation and exposes failure or incomplete coverage. Metadata must match for host, fixed limits, media, profile, trace and quality; errors, work accounting, cleanup and timing gaps disqualify a recovery winner. This is an assertion evaluator, not a workload driver; inputs and manifest hashes must be independently collected and verified. It cannot authenticate their provenance or establish overall efficiency/robustness acceptance by itself.

Isolated mixed workload (experimental)

scorecard workload -config /private/path/workload.json reads an operator-prepared JSON configuration and emits redacted JSON evidence. Only use it after the coordinator reserves isolated staging resources; a reservation_id is a label, not proof of a reservation. The configuration supplies loopback reference and ether targets with distinct matched cgroup limits, a pinned ffmpeg path and ffmpeg_sha256, manifest_sha256, cache_state, concurrency, repetitions, timeout_ms, named flows, and a scheduled trace of client/flow/at_ms. Target variables and captured JSON values may contain credentials: protect the configuration and never put credentials in client or flow names. Steps declare kind, HTTP method, path or captured URL variable, expected status and response byte limit; cleanup steps are attempted after errors and reported on failure. Media steps require the same declared delivered-byte SHA-256 on both targets and expected decoded duration. FFmpeg decodes the downloaded bytes before a stream minute can be counted. This currently supports identical bounded media responses, not distinct HLS encodes, independently verified source selection, injected provider faults, or full video-quality assessment. The manifest hash and cache state are declarations and require independent verification. A passing workload alone does not establish comparative superiority.

The acceptance input also accepts ttff_observations and performance_pairs. A verified, completed unresolved first-frame observation must be at most 5,000 ms (4,000 ms is the stretch target); a cached one must be at most 1,000 ms. These are per-observation hard thresholds, not statistical certification. Record resolve, probe, decision, first-byte and, for HLS, first-segment durations when measured; absent phases remain incomplete and must not be summed as if independent. Provide an independently established cache state and source match. Matched, successful, quality-verified pairs for transcode start, seek to picture, resume, subtitle/audio switch, browse latency and CPU per stream-minute fail when Ether exceeds the reference in that observation. A single passing pair is not proof of overall parity: collect repetitions and assess uncertainty.

For disposable TV fixtures, optional -silo 127.0.0.1:18098, -webdav 127.0.0.1:18099, and -stremio 127.0.0.1:18100 mount the real registered handlers, backed by the same fixture catalogue and broker. These extra listeners accept loopback IPs only; a separately managed recording proxy can provide controlled LAN access. The private fixture data directory contains scorecard-app-credentials.json (mode 0600), with distinct WebDAV, Stremio and M3U viewer grants. It is a secret: do not commit, log, or include it in captures. Silo uses the seeded local viewer login. Stremio clients may require HTTPS and app-origin configuration not provided by this fixture. A mounted handler does not establish that end-to-end playback is certified. Plex is deliberately not mounted.

python3 -B cmd/scorecard/sync-state.py accepts private JSON on stdin with reference_url, target_url, reference_user, reference_password, target_user, target_password, and optional apply: true. The target must be a disposable loopback fixture. It reads the reference viewer's movie and episode UserData, matches TMDB movie IDs and show/season/episode IDs, and rejects missing or conflicting duplicate-version state before changing anything. A dry run is the default. Apply uses the target's authenticated played, favorite, and playback-stop APIs and verifies the resulting state, surfacing errors and logout failures. Resume seeding can open real fixture playback and must occur before timed runs. An unsupported state (for example a reference resume position the target refuses to retain), incompatible viewers, or ambiguous matching is a failure, not evidence of alignment; a failed apply may have made partial changes, so recreate or inspect the disposable fixture before rerunning. Supply credentials only over private stdin; never copy or print the reference lab credential file. Each invocation aligns one reference viewer to one target viewer; multiple viewers need separately provisioned fixture users.

-dlna 127.0.0.1:18101 additionally mounts the real DLNA handler with the fixture's disposable viewer selected and SSDP discovery disabled. A recording proxy must enforce LAN-only access and block /__scorecard/ diagnostics; the upstream sees the proxy as loopback. HTTP proxying alone does not provide SSDP multicast discovery, so discovery and end-to-end DLNA playback require a separate isolated LAN test.

-m3u 127.0.0.1:18102 mounts the registered M3U/XMLTV surface using its separate viewer grant. Channel and guide coverage require explicitly seeded Live TV sources; an empty playlist only proves the handler and grant work.

When duplicate reference versions have different state, an explicit selected_reference_variants map from movie TMDB ID to lowercase SHA-256 and verified_manifest path selects one reference item whose source path matches the independently verified manifest. The chosen hashes are included in the result. This is a per-version comparison only; it does not align the other reference versions to Ether's title-wide state.

The loopback-only fixture diagnostic /__scorecard/traces reports resolver candidate rejection categories and, for scorecard sources, fixture SHA-256. It omits release names, paths, arbitrary errors and client credentials. The trace ring is process-wide; isolate requests before associating a trace with a specific test. Recording proxies must deny every /__scorecard/* route.

Head-to-head lab run

h2h.py is the opt-in Jellyfin/Emby comparison against a disposable scorecard fixture. It reads credentials from stdin and writes aggregate JSON to stdout. The report is provisional unless the caller sets provisional to false after an attested same-catalogue Ether target is available. Provisional losses are not treated as product defects. Passwords, tokens and stream URLs are not included. Browse timings use the same nearest-rank percentile as scorecard ((n*p+99)/100 - 1). A latency winner is recorded only when both sides return the same TotalRecordCount, every sample succeeds, and the response is fully read. Playback timings are one wall-clock span from PlaybackInfo through the first media byte (direct static read, or the first HLS media segment). Seek is a byte-range read of the same verified file. Resume is a fresh PlaybackInfo at a 30-second StartTimeTicks after progress was reported. Reference and Ether must open the same SHA-256; otherwise the playback winner is inconclusive. The tool stops each session and logs out. It does not restart or reseed the reference servers.

Set SCORECARD_FFMPEG and SCORECARD_FFPROBE when the fixture should use the lab binaries. SCORECARD_EXTRA_USERS is a JSON array of {username,password} objects for additional disposable viewers (the primary SCORECARD_USER remains the measured account). Keep that JSON out of the repository.

The loopback-only /__scorecard/detail-offer?handle=HANDLE joins an unexpired, surface-specific detail handle to its fixture item, release, source revision and expected SHA-256. The caller must obtain the handle from an authenticated detail response and independently verify the actual opened source; this diagnostic is not an authorization check and does not prove the requesting viewer or device owns the handle. Recording proxies must block it.

Documentation

Overview

Scorecard measures explicitly configured, read-only HTTP requests against two servers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL