sidebar and artist pipeline: one list in the rail, recents anchored to ids, and popular takes its own lane
This commit is contained in:
1 parent
606250c361
commit
eba29824c9
12 files changed
+1595
-281
No files matched your search
@@ -0,0 +1,171 @@
|
||||
//! 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()
|
||||
}
|
||||
Reference in new issue
Block a user