forked from Keelrleek/SoulStream
Baseline: full tree snapshot after git repository loss
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.
This commit is contained in:
commit
a667b4b28e
161 files changed
+53170
No files matched your search
@@ -0,0 +1,119 @@
|
||||
//! 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()
|
||||
}
|
||||
Reference in new issue
Block a user