Skip to content

Commit 9a53e53

Browse files
flamegraphs by URL
1 parent 63c9f16 commit 9a53e53

File tree

5 files changed

+154
-10
lines changed

5 files changed

+154
-10
lines changed

Cargo.lock

Lines changed: 97 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ bytes = "0.5.6"
4242
url = "2"
4343
analyzeme = { git = "https://github.com/rust-lang/measureme" }
4444
tar = "0.4"
45+
inferno = { version="0.9.1", default-features = false }
46+
mime = "0.3"
4547

4648
[dependencies.collector]
4749
path = "../collector"

site/src/self_profile.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,31 @@ use std::io::Read;
1212
type Response = http::Response<hyper::Body>;
1313

1414
pub mod crox;
15+
pub mod flamegraph;
1516

16-
pub fn generate(pieces: Pieces, mut params: HashMap<String, String>) -> anyhow::Result<Vec<u8>> {
17+
pub fn generate(
18+
pieces: Pieces,
19+
mut params: HashMap<String, String>,
20+
) -> anyhow::Result<(&'static str, Vec<u8>)> {
1721
let removed = params.remove("type");
1822
match removed.as_deref() {
1923
Some("crox") => {
2024
let opt = serde_json::from_str(&serde_json::to_string(&params).unwrap())
2125
.context("crox opts")?;
22-
Ok(crox::generate(pieces, opt).context("crox")?)
26+
Ok((
27+
"chrome_profiler.json",
28+
crox::generate(pieces, opt).context("crox")?,
29+
))
2330
}
24-
_ => anyhow::bail!("Unknown type, specify type={crox}"),
31+
Some("flamegraph") => {
32+
let opt = serde_json::from_str(&serde_json::to_string(&params).unwrap())
33+
.context("flame opts")?;
34+
Ok((
35+
"flamegraph.svg",
36+
flamegraph::generate(pieces, opt).context("flame")?,
37+
))
38+
}
39+
_ => anyhow::bail!("Unknown type, specify type={crox,flamegraph}"),
2540
}
2641
}
2742

site/src/self_profile/flamegraph.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use analyzeme::{collapse_stacks, ProfilingData};
2+
use anyhow::Context;
3+
use inferno::flamegraph::{from_lines, Options as FlamegraphOptions};
4+
5+
#[derive(serde::Deserialize, Debug)]
6+
pub struct Opt {}
7+
8+
pub fn generate(pieces: super::Pieces, _: Opt) -> anyhow::Result<Vec<u8>> {
9+
let profiling_data =
10+
ProfilingData::from_buffers(pieces.string_data, pieces.string_index, pieces.events)
11+
.map_err(|e| anyhow::format_err!("{:?}", e))?;
12+
13+
let recorded_stacks = collapse_stacks(&profiling_data)
14+
.iter()
15+
.map(|(unique_stack, count)| format!("{} {}", unique_stack, count))
16+
.collect::<Vec<_>>();
17+
18+
let mut file = Vec::new();
19+
let mut flamegraph_options = FlamegraphOptions::default();
20+
21+
from_lines(
22+
&mut flamegraph_options,
23+
recorded_stacks.iter().map(|s| s.as_ref()),
24+
&mut file,
25+
)
26+
.context("unable to generate a flamegraph from the collapsed stack data")?;
27+
Ok(file)
28+
}

site/src/server.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -797,8 +797,7 @@ pub async fn handle_self_profile_processed_download(
797797
};
798798
log::trace!("got pieces {:?} in {:?}", pieces, start.elapsed());
799799

800-
let json = crate::self_profile::generate(pieces, params);
801-
let json = match json {
800+
let (filename, json) = match crate::self_profile::generate(pieces, params) {
802801
Ok(c) => c,
803802
Err(e) => {
804803
log::error!("Failed to generate json {:?}", e);
@@ -808,13 +807,18 @@ pub async fn handle_self_profile_processed_download(
808807
}
809808
};
810809
let mut builder = http::Response::builder()
811-
.header_typed(ContentType::json())
810+
.header_typed(if filename.ends_with("json") {
811+
ContentType::json()
812+
} else {
813+
ContentType::from("image/svg+xml".parse::<mime::Mime>().unwrap())
814+
})
812815
.status(StatusCode::OK);
813816

814817
builder.headers_mut().unwrap().insert(
815818
hyper::header::CONTENT_DISPOSITION,
816819
hyper::header::HeaderValue::from_maybe_shared(format!(
817-
"attachment; filename=\"chrome_profiler.json\""
820+
"attachment; filename=\"{}\"",
821+
filename,
818822
))
819823
.expect("valid header"),
820824
);
@@ -1383,7 +1387,7 @@ async fn serve_req(ctx: Arc<Server>, req: Request) -> Result<Response, ServerErr
13831387
Err(e) => return Ok(e),
13841388
}
13851389
}
1386-
if req.uri().path() == "/perf/download-crox-self-profile" {
1390+
if req.uri().path() == "/perf/processed-self-profile" {
13871391
let data: Arc<InputData> = ctx.data.read().as_ref().unwrap().clone();
13881392
match get_self_profile_raw(&req) {
13891393
Ok((parts, v)) => {

0 commit comments

Comments
 (0)