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,123 @@
|
||||
//! Latency harness for the album-description path (`album_info_for_artist_id`).
|
||||
//!
|
||||
//! This is the description counterpart to `discography_audit`: it measures the
|
||||
//! call the artist page's expander and background hint harvest both make, so
|
||||
//! optimization work has a before/after number instead of an impression.
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run -p metadata --example album_info_bench -- "Artist" "Title" "Title" ...
|
||||
//!
|
||||
//! Reports, in order:
|
||||
//! 1. COLD SEQUENTIAL — one fresh client, titles one at a time. This is what
|
||||
//! the frontend harvest does today (`for (const title of titles) await`),
|
||||
//! so its total is the wall-clock the user waits for the tabs to settle.
|
||||
//! 2. WARM SEQUENTIAL — same client again, exercising the per-source caches.
|
||||
//! 3. COLD CONCURRENT — a second fresh client, all titles at once. The gap
|
||||
//! against (1) is the ceiling available from parallelizing the harvest.
|
||||
|
||||
use metadata::MetadataClient;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let Some(artist) = args.next() else {
|
||||
eprintln!("usage: album_info_bench -- \"Artist\" \"Title\" [\"Title\" ...]");
|
||||
return;
|
||||
};
|
||||
let titles: Vec<String> = args.collect();
|
||||
if titles.is_empty() {
|
||||
eprintln!("give at least one album title");
|
||||
return;
|
||||
}
|
||||
|
||||
let client = Arc::new(MetadataClient::new());
|
||||
let search = client.catalog_search(&artist).await;
|
||||
let artist_id = search
|
||||
.artists
|
||||
.iter()
|
||||
.find(|a| normalize(&a.name) == normalize(&artist))
|
||||
.map(|a| a.id);
|
||||
println!(
|
||||
"artist {artist:?} -> id {artist_id:?}, {} titles\n",
|
||||
titles.len()
|
||||
);
|
||||
|
||||
let cold = run_sequential(&client, artist_id, &artist, &titles, "COLD SEQUENTIAL").await;
|
||||
let warm = run_sequential(&client, artist_id, &artist, &titles, "WARM SEQUENTIAL").await;
|
||||
|
||||
// A second client so the concurrent run is genuinely cold; sharing the
|
||||
// first one would measure the cache, not the network path.
|
||||
let fresh = Arc::new(MetadataClient::new());
|
||||
let started = Instant::now();
|
||||
let mut tasks = Vec::new();
|
||||
for title in &titles {
|
||||
let fresh = Arc::clone(&fresh);
|
||||
let artist = artist.clone();
|
||||
let title = title.clone();
|
||||
tasks.push(tokio::spawn(async move {
|
||||
let at = Instant::now();
|
||||
let info = fresh
|
||||
.album_info_for_artist_id(artist_id, &artist, &title)
|
||||
.await;
|
||||
(title, at.elapsed().as_millis(), info.type_hint)
|
||||
}));
|
||||
}
|
||||
let mut rows = Vec::new();
|
||||
for task in tasks {
|
||||
if let Ok(row) = task.await {
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
let concurrent = started.elapsed().as_millis();
|
||||
println!("== COLD CONCURRENT ==");
|
||||
for (title, ms, hint) in &rows {
|
||||
println!(" {ms:>6} ms {title} hint={hint:?}");
|
||||
}
|
||||
println!(" total {concurrent} ms\n");
|
||||
|
||||
println!("== SUMMARY ==");
|
||||
println!(" cold sequential : {cold:>6} ms <- today's harvest wall-clock");
|
||||
println!(" warm sequential : {warm:>6} ms <- per-source cache hit");
|
||||
println!(" cold concurrent : {concurrent:>6} ms <- ceiling from fan-out");
|
||||
if concurrent > 0 {
|
||||
println!(
|
||||
" speedup : {:.1}x",
|
||||
cold as f64 / concurrent as f64
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_sequential(
|
||||
client: &Arc<MetadataClient>,
|
||||
artist_id: Option<u64>,
|
||||
artist: &str,
|
||||
titles: &[String],
|
||||
label: &str,
|
||||
) -> u128 {
|
||||
println!("== {label} ==");
|
||||
let started = Instant::now();
|
||||
for title in titles {
|
||||
let at = Instant::now();
|
||||
let info = client
|
||||
.album_info_for_artist_id(artist_id, artist, title)
|
||||
.await;
|
||||
println!(
|
||||
" {:>6} ms {title} hint={:?} source={:?}",
|
||||
at.elapsed().as_millis(),
|
||||
info.type_hint,
|
||||
info.source,
|
||||
);
|
||||
}
|
||||
let total = started.elapsed().as_millis();
|
||||
println!(" total {total} ms\n");
|
||||
total
|
||||
}
|
||||
|
||||
fn normalize(s: &str) -> String {
|
||||
s.to_lowercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_alphanumeric())
|
||||
.collect()
|
||||
}
|
||||
Reference in new issue
Block a user