From 0df580e0d734e3086ef7d57003ca7f8f7915ac56 Mon Sep 17 00:00:00 2001 From: John Law <8260377+Wal8800@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:14:46 +1300 Subject: [PATCH 1/3] fix: writing multiple profile zip --- docs/profiling.md | 4 +-- src/service/mod.rs | 82 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/docs/profiling.md b/docs/profiling.md index 5aba6d6..45601c6 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -48,12 +48,12 @@ Stall Barrier inst 0.75 After profiling, a zip file is saved to your current directory: ``` -profile_20260113_031052_run0.zip +profile_20260113_031052_result0_profile0.zip ``` This contains a `.ncu-rep` file (the full Nsight Compute report): ``` -$ unzip -l profile_20260113_031052_run0.zip +$ unzip -l profile_20260113_031052_result0_profile0.zip Length Date Time Name --------- ---------- ----- ---- 2178383 01-13-2026 03:10 profile.ncu-rep diff --git a/src/service/mod.rs b/src/service/mod.rs index 1aee5f6..932b448 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use base64::Engine; -use chrono::Utc; +use chrono::{DateTime, Utc}; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::multipart::{Form, Part}; use reqwest::Client; @@ -650,7 +650,7 @@ pub async fn submit_solution>( { for (key, run_data) in runs.iter() { if key.starts_with("profile") { - handle_profile_result(cb, run_data, i); + handle_profile_result(cb, run_data, i, key); } } } @@ -754,7 +754,12 @@ pub async fn submit_solution>( /// Handle profile mode results by decoding and displaying profile data, /// and saving trace files to the current directory. -fn handle_profile_result(cb: &(dyn Fn(String) + Send + Sync), run_data: &Value, run_idx: usize) { +fn handle_profile_result( + cb: &(dyn Fn(String) + Send + Sync), + run_data: &Value, + result_idx: usize, + run_key: &str, +) { // 1. Get profiler type and display it if let Some(profile) = run_data.get("profile") { let profiler = profile @@ -814,11 +819,9 @@ fn handle_profile_result(cb: &(dyn Fn(String) + Send + Sync), run_data: &Value, if !trace_b64.is_empty() { match base64::engine::general_purpose::STANDARD.decode(trace_b64) { Ok(trace_data) => { - // Generate unique filename with timestamp and run index - let timestamp = Utc::now().format("%Y%m%d_%H%M%S"); - let filename = format!("profile_{}_run{}.zip", timestamp, run_idx); - match std::fs::write(&filename, &trace_data) { - Ok(_) => cb(format!("\nSaved profile trace to: {}", filename)), + match write_profile_trace_file(&trace_data, Utc::now(), result_idx, run_key) + { + Ok(filename) => cb(format!("\nSaved profile trace to: {}", filename)), Err(e) => cb(format!("Failed to save trace file: {}", e)), } } @@ -836,9 +839,54 @@ fn handle_profile_result(cb: &(dyn Fn(String) + Send + Sync), run_data: &Value, } } +fn sanitize_profile_run_key(run_key: &str) -> String { + let sanitized: String = run_key + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' { + ch + } else { + '_' + } + }) + .collect(); + + if sanitized.is_empty() { + "profile".to_string() + } else { + sanitized + } +} + +fn build_profile_trace_filename( + timestamp: DateTime, + result_idx: usize, + run_key: &str, +) -> String { + let run_key = sanitize_profile_run_key(run_key); + format!( + "profile_{}_result{}_{}.zip", + timestamp.format("%Y%m%d_%H%M%S"), + result_idx, + run_key + ) +} + +fn write_profile_trace_file( + trace_data: &[u8], + timestamp: DateTime, + result_idx: usize, + run_key: &str, +) -> std::io::Result { + let filename = build_profile_trace_filename(timestamp, result_idx, run_key); + std::fs::write(&filename, trace_data)?; + Ok(filename) +} + #[cfg(test)] mod tests { use super::*; + use chrono::TimeZone; #[test] fn test_create_client_without_cli_id() { @@ -932,4 +980,22 @@ mod tests { std::env::set_var("POPCORN_API_URL", val); } } + + #[test] + fn test_build_profile_trace_filename_uses_result_index_and_run_key() { + let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + + let filename = build_profile_trace_filename(timestamp, 0, "profile3"); + + assert_eq!(filename, "profile_20260327_093846_result0_profile3.zip"); + } + + #[test] + fn test_build_profile_trace_filename_sanitizes_run_key() { + let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + + let filename = build_profile_trace_filename(timestamp, 1, "profile:1/a b"); + + assert_eq!(filename, "profile_20260327_093846_result1_profile_1_a_b.zip"); + } } From 90db98186d9fce6c0e69eab02dd2d42509a4548d Mon Sep 17 00:00:00 2001 From: Mark Saroufim Date: Fri, 3 Apr 2026 14:00:57 -0700 Subject: [PATCH 2/3] test: cover profile trace helpers --- src/service/mod.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/service/mod.rs b/src/service/mod.rs index 932b448..b29f8d8 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -887,6 +887,7 @@ fn write_profile_trace_file( mod tests { use super::*; use chrono::TimeZone; + use tempfile::tempdir; #[test] fn test_create_client_without_cli_id() { @@ -998,4 +999,31 @@ mod tests { assert_eq!(filename, "profile_20260327_093846_result1_profile_1_a_b.zip"); } + + #[test] + fn test_build_profile_trace_filename_uses_default_run_key_when_empty() { + let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + + let filename = build_profile_trace_filename(timestamp, 2, ""); + + assert_eq!(filename, "profile_20260327_093846_result2_profile.zip"); + } + + #[test] + fn test_write_profile_trace_file_writes_expected_contents() { + let temp_dir = tempdir().unwrap(); + let original_dir = std::env::current_dir().unwrap(); + let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + let trace_data = b"trace-bytes"; + + std::env::set_current_dir(temp_dir.path()).unwrap(); + + let filename = write_profile_trace_file(trace_data, timestamp, 3, "profile/3").unwrap(); + let written_path = temp_dir.path().join(&filename); + + assert_eq!(filename, "profile_20260327_093846_result3_profile_3.zip"); + assert_eq!(std::fs::read(&written_path).unwrap(), trace_data); + + std::env::set_current_dir(original_dir).unwrap(); + } } From 7895394527099873788307b1b3a207a44996d25a Mon Sep 17 00:00:00 2001 From: Mark Saroufim Date: Fri, 3 Apr 2026 14:01:58 -0700 Subject: [PATCH 3/3] ci: fix PR validation workflows --- .github/workflows/build.yml | 3 --- src/service/mod.rs | 25 ++++++++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1640c6a..6a28a05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,9 +6,6 @@ on: - main tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - - # Keep pull request builds for testing - pull_request: workflow_dispatch: permissions: diff --git a/src/service/mod.rs b/src/service/mod.rs index b29f8d8..bcdfa80 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -984,7 +984,10 @@ mod tests { #[test] fn test_build_profile_trace_filename_uses_result_index_and_run_key() { - let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + let timestamp = Utc + .with_ymd_and_hms(2026, 3, 27, 9, 38, 46) + .single() + .unwrap(); let filename = build_profile_trace_filename(timestamp, 0, "profile3"); @@ -993,16 +996,25 @@ mod tests { #[test] fn test_build_profile_trace_filename_sanitizes_run_key() { - let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + let timestamp = Utc + .with_ymd_and_hms(2026, 3, 27, 9, 38, 46) + .single() + .unwrap(); let filename = build_profile_trace_filename(timestamp, 1, "profile:1/a b"); - assert_eq!(filename, "profile_20260327_093846_result1_profile_1_a_b.zip"); + assert_eq!( + filename, + "profile_20260327_093846_result1_profile_1_a_b.zip" + ); } #[test] fn test_build_profile_trace_filename_uses_default_run_key_when_empty() { - let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + let timestamp = Utc + .with_ymd_and_hms(2026, 3, 27, 9, 38, 46) + .single() + .unwrap(); let filename = build_profile_trace_filename(timestamp, 2, ""); @@ -1013,7 +1025,10 @@ mod tests { fn test_write_profile_trace_file_writes_expected_contents() { let temp_dir = tempdir().unwrap(); let original_dir = std::env::current_dir().unwrap(); - let timestamp = Utc.with_ymd_and_hms(2026, 3, 27, 9, 38, 46).single().unwrap(); + let timestamp = Utc + .with_ymd_and_hms(2026, 3, 27, 9, 38, 46) + .single() + .unwrap(); let trace_data = b"trace-bytes"; std::env::set_current_dir(temp_dir.path()).unwrap();