172 lines
6.2 KiB
Rust
172 lines
6.2 KiB
Rust
//! Cold-start bug audit for the artist-page pipeline. Companion to
|
|
//! `artist_page_bench` (latency) — this one checks INVARIANTS across a spread
|
|
//! of artist shapes and prints a statistics row per artist:
|
|
//!
|
|
//! - overview / page / identity wall-clock, run the way the frontend runs
|
|
//! them (all three overlapped, one fresh client per artist, no disk cache)
|
|
//! - album and popular-row counts at each stage
|
|
//! - listeners coverage (the Last.fm anchor working end-to-end)
|
|
//! - release-date coverage (the MusicBrainz dates pass reaching the page)
|
|
//! - BUG lines for anything that violates an invariant
|
|
//!
|
|
//! Artists are audited SEQUENTIALLY — each fresh client has its own
|
|
//! MusicBrainz pacer, and overlapping artists would multiply request rate
|
|
//! against the 1 req/s policy (and contaminate the numbers, see the
|
|
//! paced-probe rule).
|
|
//!
|
|
//! Usage:
|
|
//! cargo run -p metadata --release --example cold_audit -- ["Artist" ...]
|
|
//! (no args = the built-in spread)
|
|
|
|
use metadata::MetadataClient;
|
|
use std::sync::Arc;
|
|
use std::time::Instant;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let mut artists: Vec<String> = std::env::args().skip(1).collect();
|
|
if artists.is_empty() {
|
|
artists = [
|
|
"Alvvays", // small, clean MB identity
|
|
"Weezer", // medium, deep catalog
|
|
"Queen", // huge, 1119 MB pressings
|
|
"Death Grips", // mixtape/street edge
|
|
"King Gizzard & The Lizard Wizard", // Last.fm mbid-index miss + '&'
|
|
"Boards of Canada", // electronic, sparse Deezer
|
|
"Duster", // namesake-collision shape
|
|
]
|
|
.into_iter()
|
|
.map(String::from)
|
|
.collect();
|
|
}
|
|
|
|
let mut bugs: Vec<String> = Vec::new();
|
|
println!(
|
|
"{:<36} {:>8} {:>8} {:>8} {:>6} {:>6} {:>7} {:>9} {:>8}",
|
|
"artist", "overview", "page", "identity", "ov-alb", "pg-alb", "popular", "listeners", "dates"
|
|
);
|
|
|
|
for artist in &artists {
|
|
let client = Arc::new(MetadataClient::new());
|
|
let search = client.catalog_search(artist).await;
|
|
let Some(found) = search
|
|
.artists
|
|
.iter()
|
|
.find(|a| normalize(&a.name) == normalize(artist))
|
|
else {
|
|
bugs.push(format!("{artist}: no exact-name artist in catalog search"));
|
|
continue;
|
|
};
|
|
let id = found.id;
|
|
|
|
// The frontend shape: overview, page and the identity warm-up all in
|
|
// flight together on one client.
|
|
let (overview, page, identity) = tokio::join!(
|
|
async {
|
|
let t = Instant::now();
|
|
let overview = client.catalog_artist_overview(id).await;
|
|
(t.elapsed().as_millis(), overview)
|
|
},
|
|
async {
|
|
let t = Instant::now();
|
|
let page = client.catalog_artist(id).await;
|
|
(t.elapsed().as_millis(), page)
|
|
},
|
|
async {
|
|
let t = Instant::now();
|
|
let identity = client.catalog_artist_identity(id, artist).await;
|
|
(t.elapsed().as_millis(), identity)
|
|
},
|
|
);
|
|
|
|
let (ov_ms, overview) = overview;
|
|
let (pg_ms, page) = page;
|
|
let (id_ms, identity) = identity;
|
|
|
|
let mut flag = |what: &str| bugs.push(format!("{artist}: {what}"));
|
|
|
|
let (ov_albums, _ov_popular) = match &overview {
|
|
Some(ov) => {
|
|
if ov.artist.id != id {
|
|
flag("overview echoed a different artist id");
|
|
}
|
|
if ov.artist.name.is_empty() {
|
|
flag("overview artist name empty");
|
|
}
|
|
if ov.albums.is_empty() {
|
|
flag("overview albums empty");
|
|
}
|
|
if ov.popular_tracks.is_empty() {
|
|
flag("overview popular empty");
|
|
}
|
|
(ov.albums.len(), ov.popular_tracks.len())
|
|
}
|
|
None => {
|
|
flag("overview returned None");
|
|
(0, 0)
|
|
}
|
|
};
|
|
|
|
let (pg_albums, pg_popular, with_listeners, with_dates) = match &page {
|
|
Some(pg) => {
|
|
if pg.artist.id != id {
|
|
flag("page echoed a different artist id");
|
|
}
|
|
if pg.albums.is_empty() {
|
|
flag("page albums empty");
|
|
}
|
|
if pg.popular_tracks.is_empty() {
|
|
flag("page popular empty");
|
|
}
|
|
let listeners = pg
|
|
.popular_tracks
|
|
.iter()
|
|
.filter(|t| t.listeners.is_some())
|
|
.count();
|
|
let dates = pg
|
|
.albums
|
|
.iter()
|
|
.filter(|a| a.release_date.is_some())
|
|
.count();
|
|
// The page waits for real supplements; zero listener counts on
|
|
// a popular row set means the Last.fm anchor died end-to-end.
|
|
if !pg.popular_tracks.is_empty() && listeners == 0 {
|
|
flag("page popular has zero Last.fm listener counts");
|
|
}
|
|
(pg.albums.len(), pg.popular_tracks.len(), listeners, dates)
|
|
}
|
|
None => {
|
|
flag("page returned None");
|
|
(0, 0, 0, 0)
|
|
}
|
|
};
|
|
|
|
if identity.artist_id != id {
|
|
flag("identity echoed a different artist id");
|
|
}
|
|
|
|
println!(
|
|
"{:<36} {:>6}ms {:>6}ms {:>6}ms {:>6} {:>6} {:>7} {:>6}/{:<2} {:>4}/{:<3}",
|
|
artist, ov_ms, pg_ms, id_ms, ov_albums, pg_albums, pg_popular,
|
|
with_listeners, pg_popular, with_dates, pg_albums
|
|
);
|
|
}
|
|
|
|
println!();
|
|
if bugs.is_empty() {
|
|
println!("NO INVARIANT FAILURES");
|
|
} else {
|
|
println!("FAILURES ({}):", bugs.len());
|
|
for bug in &bugs {
|
|
println!(" BUG {bug}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn normalize(s: &str) -> String {
|
|
s.to_lowercase()
|
|
.chars()
|
|
.filter(|c| c.is_alphanumeric())
|
|
.collect()
|
|
}
|