Skip to content

Commit 45e4ae5

Browse files
Wal8800msaroufim
andauthored
fix: writing multiple profile zip (#53)
* fix: writing multiple profile zip * test: cover profile trace helpers * ci: fix PR validation workflows --------- Co-authored-by: Mark Saroufim <marksaroufim@gmail.com>
1 parent 4ee4737 commit 45e4ae5

File tree

3 files changed

+119
-13
lines changed

3 files changed

+119
-13
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ on:
66
- main
77
tags:
88
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
9-
10-
# Keep pull request builds for testing
11-
pull_request:
129
workflow_dispatch:
1310

1411
permissions:

docs/profiling.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ Stall Barrier inst 0.75
4848

4949
After profiling, a zip file is saved to your current directory:
5050
```
51-
profile_20260113_031052_run0.zip
51+
profile_20260113_031052_result0_profile0.zip
5252
```
5353

5454
This contains a `.ncu-rep` file (the full Nsight Compute report):
5555
```
56-
$ unzip -l profile_20260113_031052_run0.zip
56+
$ unzip -l profile_20260113_031052_result0_profile0.zip
5757
Length Date Time Name
5858
--------- ---------- ----- ----
5959
2178383 01-13-2026 03:10 profile.ncu-rep

src/service/mod.rs

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::{anyhow, Result};
22
use base64::Engine;
3-
use chrono::Utc;
3+
use chrono::{DateTime, Utc};
44
use reqwest::header::{HeaderMap, HeaderValue};
55
use reqwest::multipart::{Form, Part};
66
use reqwest::Client;
@@ -650,7 +650,7 @@ pub async fn submit_solution<P: AsRef<Path>>(
650650
{
651651
for (key, run_data) in runs.iter() {
652652
if key.starts_with("profile") {
653-
handle_profile_result(cb, run_data, i);
653+
handle_profile_result(cb, run_data, i, key);
654654
}
655655
}
656656
}
@@ -754,7 +754,12 @@ pub async fn submit_solution<P: AsRef<Path>>(
754754

755755
/// Handle profile mode results by decoding and displaying profile data,
756756
/// and saving trace files to the current directory.
757-
fn handle_profile_result(cb: &(dyn Fn(String) + Send + Sync), run_data: &Value, run_idx: usize) {
757+
fn handle_profile_result(
758+
cb: &(dyn Fn(String) + Send + Sync),
759+
run_data: &Value,
760+
result_idx: usize,
761+
run_key: &str,
762+
) {
758763
// 1. Get profiler type and display it
759764
if let Some(profile) = run_data.get("profile") {
760765
let profiler = profile
@@ -814,11 +819,9 @@ fn handle_profile_result(cb: &(dyn Fn(String) + Send + Sync), run_data: &Value,
814819
if !trace_b64.is_empty() {
815820
match base64::engine::general_purpose::STANDARD.decode(trace_b64) {
816821
Ok(trace_data) => {
817-
// Generate unique filename with timestamp and run index
818-
let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
819-
let filename = format!("profile_{}_run{}.zip", timestamp, run_idx);
820-
match std::fs::write(&filename, &trace_data) {
821-
Ok(_) => cb(format!("\nSaved profile trace to: {}", filename)),
822+
match write_profile_trace_file(&trace_data, Utc::now(), result_idx, run_key)
823+
{
824+
Ok(filename) => cb(format!("\nSaved profile trace to: {}", filename)),
822825
Err(e) => cb(format!("Failed to save trace file: {}", e)),
823826
}
824827
}
@@ -836,9 +839,55 @@ fn handle_profile_result(cb: &(dyn Fn(String) + Send + Sync), run_data: &Value,
836839
}
837840
}
838841

842+
fn sanitize_profile_run_key(run_key: &str) -> String {
843+
let sanitized: String = run_key
844+
.chars()
845+
.map(|ch| {
846+
if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
847+
ch
848+
} else {
849+
'_'
850+
}
851+
})
852+
.collect();
853+
854+
if sanitized.is_empty() {
855+
"profile".to_string()
856+
} else {
857+
sanitized
858+
}
859+
}
860+
861+
fn build_profile_trace_filename(
862+
timestamp: DateTime<Utc>,
863+
result_idx: usize,
864+
run_key: &str,
865+
) -> String {
866+
let run_key = sanitize_profile_run_key(run_key);
867+
format!(
868+
"profile_{}_result{}_{}.zip",
869+
timestamp.format("%Y%m%d_%H%M%S"),
870+
result_idx,
871+
run_key
872+
)
873+
}
874+
875+
fn write_profile_trace_file(
876+
trace_data: &[u8],
877+
timestamp: DateTime<Utc>,
878+
result_idx: usize,
879+
run_key: &str,
880+
) -> std::io::Result<String> {
881+
let filename = build_profile_trace_filename(timestamp, result_idx, run_key);
882+
std::fs::write(&filename, trace_data)?;
883+
Ok(filename)
884+
}
885+
839886
#[cfg(test)]
840887
mod tests {
841888
use super::*;
889+
use chrono::TimeZone;
890+
use tempfile::tempdir;
842891

843892
#[test]
844893
fn test_create_client_without_cli_id() {
@@ -932,4 +981,64 @@ mod tests {
932981
std::env::set_var("POPCORN_API_URL", val);
933982
}
934983
}
984+
985+
#[test]
986+
fn test_build_profile_trace_filename_uses_result_index_and_run_key() {
987+
let timestamp = Utc
988+
.with_ymd_and_hms(2026, 3, 27, 9, 38, 46)
989+
.single()
990+
.unwrap();
991+
992+
let filename = build_profile_trace_filename(timestamp, 0, "profile3");
993+
994+
assert_eq!(filename, "profile_20260327_093846_result0_profile3.zip");
995+
}
996+
997+
#[test]
998+
fn test_build_profile_trace_filename_sanitizes_run_key() {
999+
let timestamp = Utc
1000+
.with_ymd_and_hms(2026, 3, 27, 9, 38, 46)
1001+
.single()
1002+
.unwrap();
1003+
1004+
let filename = build_profile_trace_filename(timestamp, 1, "profile:1/a b");
1005+
1006+
assert_eq!(
1007+
filename,
1008+
"profile_20260327_093846_result1_profile_1_a_b.zip"
1009+
);
1010+
}
1011+
1012+
#[test]
1013+
fn test_build_profile_trace_filename_uses_default_run_key_when_empty() {
1014+
let timestamp = Utc
1015+
.with_ymd_and_hms(2026, 3, 27, 9, 38, 46)
1016+
.single()
1017+
.unwrap();
1018+
1019+
let filename = build_profile_trace_filename(timestamp, 2, "");
1020+
1021+
assert_eq!(filename, "profile_20260327_093846_result2_profile.zip");
1022+
}
1023+
1024+
#[test]
1025+
fn test_write_profile_trace_file_writes_expected_contents() {
1026+
let temp_dir = tempdir().unwrap();
1027+
let original_dir = std::env::current_dir().unwrap();
1028+
let timestamp = Utc
1029+
.with_ymd_and_hms(2026, 3, 27, 9, 38, 46)
1030+
.single()
1031+
.unwrap();
1032+
let trace_data = b"trace-bytes";
1033+
1034+
std::env::set_current_dir(temp_dir.path()).unwrap();
1035+
1036+
let filename = write_profile_trace_file(trace_data, timestamp, 3, "profile/3").unwrap();
1037+
let written_path = temp_dir.path().join(&filename);
1038+
1039+
assert_eq!(filename, "profile_20260327_093846_result3_profile_3.zip");
1040+
assert_eq!(std::fs::read(&written_path).unwrap(), trace_data);
1041+
1042+
std::env::set_current_dir(original_dir).unwrap();
1043+
}
9351044
}

0 commit comments

Comments
 (0)