forked from Keelrleek/SoulStream
The previous .git directory was found gutted (only info/ remained), so history is unrecoverable locally. This commit restores version control with the tree exactly as it stands: the working app, the preview-tracks launcher on the artist hero, the scrollable-track-previews design bundle (docs + approved mockups), and the Phase 0 validation artifacts.
120 lines
4.6 KiB
Rust
120 lines
4.6 KiB
Rust
//! Latency harness for the whole discography-load pipeline — the three
|
|
//! backend stages the artist page runs, in the order the frontend runs them:
|
|
//!
|
|
//! 1. `catalog_artist_overview` — the first-paint content stage
|
|
//! 2. `catalog_artist` — the exhaustive classification page
|
|
//! 3. `catalog_artist_identity` — the MusicBrainz supplement/type-hint pass
|
|
//!
|
|
//! Companion to `album_info_bench` (descriptions). Run with
|
|
//! `SOULSTREAM_STAGE_TRACE=1` to attribute time inside each stage.
|
|
//!
|
|
//! Usage:
|
|
//! cargo run -p metadata --example artist_page_bench -- "Artist" ["Artist" ...]
|
|
|
|
use metadata::MetadataClient;
|
|
use std::sync::Arc;
|
|
use std::time::Instant;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let artists: Vec<String> = std::env::args().skip(1).collect();
|
|
if artists.is_empty() {
|
|
eprintln!("usage: artist_page_bench -- \"Artist\" [\"Artist\" ...]");
|
|
return;
|
|
}
|
|
|
|
for artist in &artists {
|
|
let client = Arc::new(MetadataClient::new());
|
|
let search = client.catalog_search(artist).await;
|
|
let Some(id) = search
|
|
.artists
|
|
.iter()
|
|
.find(|a| normalize(&a.name) == normalize(artist))
|
|
.map(|a| a.id)
|
|
else {
|
|
println!("== {artist}: no exact-name artist found, skipping ==\n");
|
|
continue;
|
|
};
|
|
println!("== {artist} (deezer {id}) ==");
|
|
|
|
// The frontend overlaps overview + page, then runs the identity pass
|
|
// after the page lands. Reproduce exactly that shape.
|
|
let cold_started = Instant::now();
|
|
let (overview, page) = 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)
|
|
},
|
|
);
|
|
let overlap_ms = cold_started.elapsed().as_millis();
|
|
println!(
|
|
" cold overview: {} ms ({} albums) cold page: {} ms ({} albums) overlapped: {overlap_ms} ms",
|
|
overview.0,
|
|
overview.1.as_ref().map(|o| o.albums.len()).unwrap_or(0),
|
|
page.0,
|
|
page.1.as_ref().map(|p| p.albums.len()).unwrap_or(0),
|
|
);
|
|
|
|
let t = Instant::now();
|
|
let identity = client.catalog_artist_identity(id, artist).await;
|
|
let identity_ms = t.elapsed().as_millis();
|
|
println!(
|
|
" cold identity pass: {identity_ms} ms (supplement {} albums, {} type hints)",
|
|
identity.albums.len(),
|
|
identity.type_hints.len(),
|
|
);
|
|
println!(
|
|
" COLD TOTAL to fully sorted (page ∥ overview, then identity): {} ms",
|
|
overlap_ms + identity_ms
|
|
);
|
|
|
|
let t = Instant::now();
|
|
let _ = client.catalog_artist(id).await;
|
|
println!(" warm page: {} ms", t.elapsed().as_millis());
|
|
let t = Instant::now();
|
|
let _ = client.catalog_artist_identity(id, artist).await;
|
|
println!(" warm identity: {} ms", t.elapsed().as_millis());
|
|
|
|
// The new frontend shape: the identity pass is kicked off BESIDE the
|
|
// page load instead of after it, so cold time-to-sorted is max(), not
|
|
// sum(). A fresh client so nothing above warms it.
|
|
let fresh = Arc::new(MetadataClient::new());
|
|
let t = Instant::now();
|
|
let (_page, _identity) = tokio::join!(
|
|
fresh.catalog_artist(id),
|
|
fresh.catalog_artist_identity(id, artist),
|
|
);
|
|
println!(
|
|
" cold page ∥ identity (new frontend overlap): {} ms",
|
|
t.elapsed().as_millis()
|
|
);
|
|
|
|
// Restart warmth: two separate clients sharing one disk cache dir —
|
|
// the second models an app relaunch.
|
|
let dir = std::env::temp_dir().join("soulstream-artist-page-bench-cache");
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
let writer = Arc::new(MetadataClient::new().with_disk_cache(dir.clone()));
|
|
let _ = writer.catalog_artist_identity(id, artist).await;
|
|
let restarted = Arc::new(MetadataClient::new().with_disk_cache(dir.clone()));
|
|
let t = Instant::now();
|
|
let _ = restarted.catalog_artist_identity(id, artist).await;
|
|
println!(
|
|
" identity after app restart (disk cache): {} ms\n",
|
|
t.elapsed().as_millis()
|
|
);
|
|
}
|
|
}
|
|
|
|
fn normalize(s: &str) -> String {
|
|
s.to_lowercase()
|
|
.chars()
|
|
.filter(|c| c.is_alphanumeric())
|
|
.collect()
|
|
}
|