Skip to content

Commit a456465

Browse files
Add codegen schedule view
1 parent 6411c91 commit a456465

File tree

6 files changed

+424
-45
lines changed

6 files changed

+424
-45
lines changed

site/src/request_handlers/self_profile.rs

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,80 @@ use crate::server::{Request, Response, ResponseHeaders};
1616

1717
pub async fn handle_self_profile_processed_download(
1818
body: self_profile_raw::Request,
19-
params: HashMap<String, String>,
19+
mut params: HashMap<String, String>,
2020
ctxt: &SiteCtxt,
2121
) -> http::Response<hyper::Body> {
22-
let title = format!(
23-
"{}: {} {}",
24-
&body.commit[..std::cmp::min(7, body.commit.len())],
25-
body.benchmark,
26-
body.run_name
27-
);
22+
let diff_against = params.remove("base_commit");
23+
if params
24+
.get("type")
25+
.map_or(false, |t| t != "codegen-schedule")
26+
&& diff_against.is_some()
27+
{
28+
let mut resp = Response::new("Only codegen_schedule supports diffing right now.".into());
29+
*resp.status_mut() = StatusCode::BAD_REQUEST;
30+
return resp;
31+
}
32+
33+
let title = if let Some(diff_against) = diff_against.as_ref() {
34+
format!(
35+
"{} vs {}: {} {}",
36+
&diff_against[..std::cmp::min(7, diff_against.len())],
37+
&body.commit[..std::cmp::min(7, body.commit.len())],
38+
body.benchmark,
39+
body.run_name
40+
)
41+
} else {
42+
format!(
43+
"{}: {} {}",
44+
&body.commit[..std::cmp::min(7, body.commit.len())],
45+
body.benchmark,
46+
body.run_name
47+
)
48+
};
2849

2950
let start = Instant::now();
3051

31-
let (url, is_tarball) = match handle_self_profile_raw(body, ctxt).await {
32-
Ok(v) => (v.url, v.is_tarball),
52+
let base_data = if let Some(diff_against) = diff_against {
53+
match handle_self_profile_raw(
54+
self_profile_raw::Request {
55+
commit: diff_against,
56+
benchmark: body.benchmark.clone(),
57+
run_name: body.run_name.clone(),
58+
cid: None,
59+
},
60+
ctxt,
61+
)
62+
.await
63+
{
64+
Ok(v) => match get_self_profile_raw_data(&v.url).await {
65+
Ok(v) => Some(v),
66+
Err(e) => return e,
67+
},
68+
Err(e) => {
69+
let mut resp = Response::new(e.into());
70+
*resp.status_mut() = StatusCode::BAD_REQUEST;
71+
return resp;
72+
}
73+
}
74+
} else {
75+
None
76+
};
77+
78+
let data = match handle_self_profile_raw(body, ctxt).await {
79+
Ok(v) => match get_self_profile_raw_data(&v.url).await {
80+
Ok(v) => v,
81+
Err(e) => return e,
82+
},
3383
Err(e) => {
3484
let mut resp = Response::new(e.into());
3585
*resp.status_mut() = StatusCode::BAD_REQUEST;
3686
return resp;
3787
}
3888
};
3989

40-
if is_tarball {
41-
let mut resp =
42-
Response::new("Processing legacy format self-profile data is not supported".into());
43-
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
44-
return resp;
45-
}
46-
47-
let data = match get_self_profile_raw_data(&url).await {
48-
Ok(v) => v,
49-
Err(e) => return e,
50-
};
51-
5290
log::trace!("got data in {:?}", start.elapsed());
5391

54-
let output = match crate::self_profile::generate(&title, data, params) {
92+
let output = match crate::self_profile::generate(&title, base_data, data, params) {
5593
Ok(c) => c,
5694
Err(e) => {
5795
log::error!("Failed to generate json {:?}", e);
@@ -63,8 +101,12 @@ pub async fn handle_self_profile_processed_download(
63101
let mut builder = http::Response::builder()
64102
.header_typed(if output.filename.ends_with("json") {
65103
ContentType::json()
66-
} else {
104+
} else if output.filename.ends_with("svg") {
67105
ContentType::from("image/svg+xml".parse::<mime::Mime>().unwrap())
106+
} else if output.filename.ends_with("html") {
107+
ContentType::html()
108+
} else {
109+
unreachable!()
68110
})
69111
.status(StatusCode::OK);
70112

@@ -507,27 +549,15 @@ pub async fn handle_self_profile_raw(
507549
.map(|(_, cid)| cid)
508550
.collect::<Vec<_>>();
509551

510-
return match fetch(&cids, cid, format!("{}.mm_profdata.sz", url_prefix), false).await {
552+
return match fetch(&cids, cid, format!("{}.mm_profdata.sz", url_prefix)).await {
511553
Ok(fetched) => Ok(fetched),
512-
Err(new_error) => {
513-
match fetch(&cids, cid, format!("{}.tar.sz", url_prefix), true).await {
514-
Ok(fetched) => Ok(fetched),
515-
Err(old_error) => {
516-
// Both files failed to fetch; return the errors for both:
517-
Err(format!(
518-
"mm_profdata download failed: {:?}, tarball download failed: {:?}",
519-
new_error, old_error
520-
))
521-
}
522-
}
523-
}
554+
Err(new_error) => Err(format!("mm_profdata download failed: {:?}", new_error,)),
524555
};
525556

526557
async fn fetch(
527558
cids: &[i32],
528559
cid: i32,
529560
url: String,
530-
is_tarball: bool,
531561
) -> ServerResult<self_profile_raw::Response> {
532562
let resp = reqwest::Client::new()
533563
.head(&url)
@@ -545,7 +575,7 @@ pub async fn handle_self_profile_raw(
545575
cids: cids.to_vec(),
546576
cid,
547577
url,
548-
is_tarball,
578+
is_tarball: false,
549579
})
550580
}
551581
}

site/src/self_profile.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use anyhow::Context;
55
use std::collections::HashMap;
66

7+
mod codegen_schedule;
78
pub mod crox;
89
pub mod flamegraph;
910

@@ -15,6 +16,7 @@ pub struct Output {
1516

1617
pub fn generate(
1718
title: &str,
19+
self_profile_base_data: Option<Vec<u8>>,
1820
self_profile_data: Vec<u8>,
1921
mut params: HashMap<String, String>,
2022
) -> anyhow::Result<Output> {
@@ -38,6 +40,21 @@ pub fn generate(
3840
is_download: false,
3941
})
4042
}
41-
_ => anyhow::bail!("Unknown type, specify type={crox,flamegraph}"),
43+
Some("codegen-schedule") => {
44+
let opt =
45+
serde_json::from_str(&serde_json::to_string(&params).unwrap()).context("params")?;
46+
Ok(Output {
47+
filename: "schedule.html",
48+
data: codegen_schedule::generate(
49+
title,
50+
self_profile_base_data,
51+
self_profile_data,
52+
opt,
53+
)
54+
.context("codegen_schedule")?,
55+
is_download: false,
56+
})
57+
}
58+
_ => anyhow::bail!("Unknown type, specify type={crox,flamegraph,codegen-schedule}"),
4259
}
4360
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<html>
2+
<head>
3+
<meta charset="utf-8">
4+
<title>{{title}}</title>
5+
<script>
6+
window.TOTAL_DURATION_BOTH = {{TOTAL_DURATION_BOTH}};
7+
window.TOTAL_DURATION_BASE = {{TOTAL_DURATION_BASE}};
8+
window.TOTAL_DURATION_NEW = {{TOTAL_DURATION_NEW}};
9+
window.BY_THREAD_BASE = {{BY_THREAD_BASE}};
10+
window.BY_THREAD = {{BY_THREAD}};
11+
</script>
12+
<style>
13+
.legend {
14+
display: inline-block;
15+
width: 1em;
16+
height: 1em;
17+
margin-left: 0.5em;
18+
}
19+
20+
.legend.codegen_module_optimize {
21+
background-color: #aa95e8;
22+
}
23+
24+
.legend.codegen_module {
25+
background-color: #6adb00;
26+
}
27+
28+
.legend.codegen_module_perform_lto {
29+
background-color: #428eff;
30+
}
31+
32+
[mouse-details] {
33+
display: none;
34+
}
35+
36+
#prev {
37+
display: none;
38+
}
39+
</style>
40+
<script src="../schedule.js"></script>
41+
</head>
42+
<body>
43+
<h3>{{title}}</h3>
44+
<p>Full width is {{TOTAL_DURATION_BOTH}}ms</p>
45+
<p>Legend:
46+
<span class="codegen_module legend"></span> codegen_module (MIR lowering)
47+
<span class="codegen_module_optimize legend"></span> codegen_module_optimize (LLVM)
48+
<span class="codegen_module_perform_lto legend"></span> codegen_module_perform_lto (LLVM)
49+
</p>
50+
<canvas id="prev"></canvas>
51+
<canvas id="current"></canvas>
52+
<div mouse-details class="prev">
53+
<p>Details (prev):</p>
54+
<p mouse-details-content-js></p>
55+
</div>
56+
<div mouse-details class="current">
57+
<p>Details (current):</p>
58+
<p mouse-details-content-js></p>
59+
</div>
60+
</body>
61+
</html>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use analyzeme::ProfilingData;
2+
use anyhow::Context;
3+
use hashbrown::HashMap;
4+
use serde::Serializer;
5+
use std::convert::TryInto;
6+
use std::time::Duration;
7+
8+
#[derive(serde::Deserialize, Debug)]
9+
pub struct Opt {
10+
#[serde(default)]
11+
force_width: Option<String>,
12+
}
13+
14+
fn is_interesting(name: &str) -> bool {
15+
match name {
16+
"codegen_module" | "codegen_module_optimize" | "codegen_module_perform_lto" => true,
17+
_ => false,
18+
}
19+
}
20+
21+
fn by_thread(self_profile_data: Vec<u8>) -> anyhow::Result<(u64, HashMap<u32, Vec<Event>>)> {
22+
let data = ProfilingData::from_paged_buffer(self_profile_data)
23+
.map_err(|e| anyhow::format_err!("{:?}", e))?;
24+
25+
let mut start = None;
26+
for event in data.iter().filter(|e| !e.timestamp.is_instant()) {
27+
let full_event = event.to_event();
28+
if is_interesting(&full_event.label) {
29+
start = Some(event.timestamp.start());
30+
break;
31+
}
32+
}
33+
let start = start.ok_or(anyhow::format_err!("no codegen_crate event"))?;
34+
35+
let mut end = start;
36+
let mut by_thread = HashMap::new();
37+
for event in data.iter().filter(|e| !e.timestamp.is_instant()) {
38+
let full_event = event.to_event();
39+
40+
if is_interesting(&full_event.label) {
41+
by_thread
42+
.entry(event.thread_id)
43+
.or_insert_with(Vec::new)
44+
.push(Event {
45+
name: full_event.label.into(),
46+
start: event.timestamp.start().duration_since(start).unwrap(),
47+
end: event.timestamp.end().duration_since(start).unwrap(),
48+
});
49+
end = std::cmp::max(end, event.timestamp.end());
50+
}
51+
}
52+
53+
Ok((
54+
end.duration_since(start).unwrap().as_millis() as u64,
55+
by_thread,
56+
))
57+
}
58+
59+
pub fn generate(
60+
title: &str,
61+
self_profile_base_data: Option<Vec<u8>>,
62+
self_profile_data: Vec<u8>,
63+
opt: Opt,
64+
) -> anyhow::Result<Vec<u8>> {
65+
let (total_duration_new, by_thread_new) = by_thread(self_profile_data)?;
66+
let mut total_duration = total_duration_new;
67+
let prev = if let Some(self_profile_base_data) = self_profile_base_data {
68+
let (total_duration_prev, by_thread_prev) = by_thread(self_profile_base_data)?;
69+
total_duration = std::cmp::max(total_duration_new, total_duration_prev);
70+
Some((total_duration_prev, by_thread_prev))
71+
} else {
72+
None
73+
};
74+
75+
if let Some(force_width) = opt.force_width {
76+
let forced = force_width
77+
.parse::<u64>()
78+
.context(anyhow::format_err!("{} does not parse", force_width))?;
79+
total_duration = std::cmp::max(forced, total_duration);
80+
}
81+
82+
Ok(HTML_TEMPLATE
83+
.replace("{{title}}", title)
84+
.replace("{{TOTAL_DURATION_BOTH}}", &total_duration.to_string())
85+
.replace(
86+
"{{TOTAL_DURATION_BASE}}",
87+
&prev
88+
.as_ref()
89+
.map(|p| p.0.to_string())
90+
.unwrap_or_else(|| String::from("null")),
91+
)
92+
.replace("{{TOTAL_DURATION_NEW}}", &total_duration_new.to_string())
93+
.replace(
94+
"{{BY_THREAD_BASE}}",
95+
&serde_json::to_string(&prev.map(|p| p.1)).unwrap(),
96+
)
97+
.replace(
98+
"{{BY_THREAD}}",
99+
&serde_json::to_string(&by_thread_new).unwrap(),
100+
)
101+
.into_bytes())
102+
}
103+
104+
#[derive(serde::Serialize, Debug)]
105+
pub struct Event {
106+
pub name: String,
107+
#[serde(serialize_with = "as_millis")]
108+
pub start: std::time::Duration,
109+
#[serde(serialize_with = "as_millis")]
110+
pub end: std::time::Duration,
111+
}
112+
113+
fn as_millis<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
114+
s.serialize_u64(d.as_millis().try_into().unwrap())
115+
}
116+
117+
static HTML_TEMPLATE: &str = include_str!("codegen_schedule.html");

0 commit comments

Comments
 (0)