use std::fs;
use std::io::Write;
use std::path::Path;
use serde::Serialize;
use super::ExistingOutputPolicy;
use super::ranked_report::{
CompactFunctionTree, RankedFunction, RankedProfileDocument, RankedProfileMetadata,
RankedSemantic,
};
use super::report_cli::{RankedReportFailure, RankedReportFailurePhase};
const HTML_DOCUMENT_PREFIX: &str = r#"<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>Delta Funnel profile report</title>
<style>
"#;
const REPORT_STYLE: &str = include_str!("report_html.css");
const HTML_PROFILE_PREFIX: &str = r#"</style>
</head>
<body>
<main>
<h1>Delta Funnel profile report</h1>
<p>Semantic durations are exact wall-clock or lifecycle measurements. Function metrics are sampled on-CPU observations, not exact elapsed time.</p>
<div id="summary" class="summary" role="status" aria-live="polite"></div>
<section aria-labelledby="operations-heading">
<h2 id="operations-heading">Operations</h2>
<p>Operation roots are ranked by exact duration. Expand a row to follow exact semantic children into sampled native callsites.</p>
<div class="controls">
<label class="filter-label" for="profile-filter"><span>Filter profile</span>
<input id="profile-filter" type="search" maxlength="200" autocomplete="off" placeholder="Name, symbol, module, or source file"></label>
<button id="clear-filter" class="clear-filter" type="button" disabled>Clear filter</button>
<button id="previous-filter-page" class="filter-page" type="button" hidden>Previous matches</button>
<button id="next-filter-page" class="filter-page" type="button" hidden>Next matches</button>
<label><input id="show-all-frames" type="checkbox"> Show all native frames</label>
<output id="filter-status" class="filter-status" for="profile-filter" role="status" aria-live="polite"></output>
</div>
<div class="controls tree-controls">
<button id="expand-subtree" class="tree-action" type="button">Expand selected subtree</button>
<button id="collapse-subtree" class="tree-action" type="button">Collapse selected subtree</button>
<output id="tree-status" class="filter-status" role="status" aria-live="polite"></output>
</div>
<div class="table-wrap">
<table role="treegrid" aria-label="Ranked operations">
<thead><tr>
<th scope="col" data-sort-column="name"><button class="sort" type="button" data-sort="name" data-label="Operation">Operation</button></th>
<th scope="col">State and time basis</th>
<th scope="col" class="number" data-sort-column="duration"><button class="sort" type="button" data-sort="duration" data-label="Exact duration">Exact duration</button></th>
<th scope="col" class="number">Context %</th>
<th scope="col" class="number" data-sort-column="direct"><button class="sort" type="button" data-sort="direct" data-label="Direct/self CPU samples">Direct/self CPU samples</button></th>
<th scope="col" class="number" data-sort-column="inclusive"><button class="sort" type="button" data-sort="inclusive" data-label="Inclusive CPU samples">Inclusive CPU samples</button></th>
</tr></thead>
<tbody id="operations"></tbody>
</table>
</div>
<output id="render-limit-status" class="render-limit-status" role="status" aria-live="polite"></output>
<p id="operations-empty" class="empty" hidden>No operation records are available.</p>
</section>
<details class="help">
<summary>How to read these metrics</summary>
<p>Exact duration is measured wall-clock or lifecycle time. Parallel semantic children may overlap and are not additive. Direct CPU samples belong to one semantic node. Inclusive CPU samples also include its semantic descendants. Sampling observes on-CPU work and does not prove why a thread was off-CPU.</p>
<p>Self CPU samples were observed directly in one function. Inclusive CPU samples also include sampled callees. Function percentages use direct samples from the owning semantic node as their denominator. Sample counts are statistical observations, not exact function milliseconds.</p>
<p>Eligible samples are the on-CPU samples considered for attribution. Directly attributed samples have one semantic owner. Ambiguous samples have more than one possible owner. Unattributed samples have no semantic owner. Linux sampling does not measure off-CPU waiting time.</p>
<p>Native stack coverage separates resolved leaf symbols, unresolved leaf symbols, unwind failures, and missing callstacks. Profiler samples dropped is a trace-wide Perfetto data-loss diagnostic and is not a function cost.</p>
<p>An incomplete capture preserves only recorded intervals. Missing tail time, exact duration, and terminal state remain unknown. The summary names the retained capture-health findings instead of inferring missing values.</p>
<p>The default compact frame view hides zero-self parents that have one child with the same inclusive sample count. Use Show all native frames to restore the complete captured call chain.</p>
<p>Select a row to use the subtree controls. Arrow Up and Arrow Down move between visible rows. Arrow Right expands a row or moves to its first child. Arrow Left collapses a row or moves to its parent. Sibling groups are paged 100 rows at a time. Bulk subtree actions and the visible table are limited to 1000 rows.</p>
</details>
</main>
<script id="profile-data" type="application/json">"#;
const HTML_SCRIPT_PREFIX: &str = r#"</script>
<script>
"#;
const REPORT_SCRIPT: &str = include_str!("report_html.js");
const HTML_SUFFIX: &str = r#"</script>
</body>
</html>
"#;
#[derive(Serialize)]
struct HtmlProfile<'a> {
metadata: &'a RankedProfileMetadata,
semantics: Vec<HtmlSemantic<'a>>,
functions: Vec<HtmlFunction<'a>>,
}
#[derive(Serialize)]
struct HtmlSemantic<'a> {
#[serde(flatten)]
semantic: &'a RankedSemantic,
display_name: std::borrow::Cow<'a, str>,
}
#[derive(Serialize)]
struct HtmlFunction<'a> {
#[serde(flatten)]
function: &'a RankedFunction,
display_name: String,
compact_parent_function_id: Option<i64>,
compact_hidden: bool,
}
pub(super) fn render_ranked_profile_html(
document: &RankedProfileDocument,
) -> Result<String, RankedReportFailure> {
let compact = CompactFunctionTree::new(&document.functions);
let profile = HtmlProfile {
metadata: &document.metadata,
semantics: document
.semantics
.iter()
.map(|semantic| HtmlSemantic {
semantic,
display_name: semantic.display_name(),
})
.collect(),
functions: document
.functions
.iter()
.map(|function| HtmlFunction {
function,
display_name: function.display_name(),
compact_parent_function_id: compact.parent_function_id(function),
compact_hidden: !compact.contains(function),
})
.collect(),
};
let json = serde_json::to_string(&profile).map_err(|_| {
RankedReportFailure::new(
RankedReportFailurePhase::Serialization,
"json_failed",
"ranked profile data could not be serialized",
)
})?;
let mut html = String::with_capacity(
HTML_DOCUMENT_PREFIX.len()
+ REPORT_STYLE.len()
+ HTML_PROFILE_PREFIX.len()
+ json.len()
+ HTML_SCRIPT_PREFIX.len()
+ REPORT_SCRIPT.len()
+ HTML_SUFFIX.len(),
);
html.push_str(HTML_DOCUMENT_PREFIX);
html.push_str(REPORT_STYLE);
html.push_str(HTML_PROFILE_PREFIX);
push_html_safe_json(&mut html, &json);
html.push_str(HTML_SCRIPT_PREFIX);
html.push_str(REPORT_SCRIPT);
html.push_str(HTML_SUFFIX);
Ok(html)
}
pub(super) fn write_ranked_profile_html(
output: &Path,
html: &str,
existing_output: ExistingOutputPolicy,
) -> Result<(), RankedReportFailure> {
let parent = output
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
fs::create_dir_all(parent).map_err(|_| {
output_failure(
"create_parent_failed",
"report output directory could not be created",
)
})?;
let mut temporary = tempfile::NamedTempFile::new_in(parent).map_err(|_| {
output_failure(
"create_temporary_failed",
"temporary report file could not be created",
)
})?;
temporary.write_all(html.as_bytes()).map_err(|_| {
output_failure("write_failed", "temporary report file could not be written")
})?;
let persisted = match existing_output {
ExistingOutputPolicy::Replace => temporary.persist(output),
ExistingOutputPolicy::Preserve => temporary.persist_noclobber(output),
};
persisted
.map_err(|_| output_failure("persist_failed", "completed report could not be persisted"))?;
Ok(())
}
fn output_failure(kind: &'static str, message: &'static str) -> RankedReportFailure {
RankedReportFailure::new(RankedReportFailurePhase::Output, kind, message)
}
fn push_html_safe_json(output: &mut String, json: &str) {
for character in json.chars() {
match character {
'<' => output.push_str("\\u003c"),
'>' => output.push_str("\\u003e"),
'&' => output.push_str("\\u0026"),
'\u{2028}' => output.push_str("\\u2028"),
'\u{2029}' => output.push_str("\\u2029"),
_ => output.push(character),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ranked_report::{RankedFunction, RankedProfileMetadata, RankedSemantic};
use super::*;
fn metadata() -> RankedProfileMetadata {
RankedProfileMetadata {
capture_complete: true,
semantic_complete: true,
finalization_observed: true,
incomplete_operation_root_count: 0,
truncation_marker_count: 0,
missing_identity_field_count: 0,
missing_terminal_result_count: 0,
crossing_worker_slice_count: 0,
crossing_planning_activity_slice_count: 0,
crossing_execution_activity_slice_count: 0,
invalid_planning_activity_hierarchy_count: 0,
invalid_execution_activity_hierarchy_count: 0,
perf_sample_without_callsite_count: 0,
perf_samples_skipped: 0,
buffer_loss_count: 0,
data_source_loss_count: 0,
flush_failure_count: 0,
schema_version: 3,
sample_frequency_hz: 1000,
sampled_cpu_count: 8,
exact_time_unit: "nanoseconds".to_owned(),
sample_unit: "samples".to_owned(),
eligible_sample_count: 0,
direct_sample_count: 0,
ambiguous_sample_count: 0,
unattributed_sample_count: 0,
resolved_function_sample_count: 0,
unresolved_function_sample_count: 0,
unwind_error_sample_count: 0,
missing_callstack_sample_count: 0,
trace_profiler_dropped_sample_count: 0,
}
}
fn semantic(
semantic_id: i64,
parent_semantic_id: Option<i64>,
name: impl Into<String>,
) -> RankedSemantic {
RankedSemantic {
semantic_id,
parent_semantic_id,
operation_id: 1,
name: name.into(),
semantic_kind: if parent_semantic_id.is_none() {
"operation"
} else {
"stage"
}
.to_owned(),
operation_kind: parent_semantic_id.is_none().then(|| "write".to_owned()),
stage_category: None,
stage_name: None,
activity: None,
start_ns: 0,
end_ns: Some(1_000_000),
duration_ns: Some(1_000_000),
time_semantics: "wall_clock".to_owned(),
result: Some("completed".to_owned()),
is_complete: true,
query_execution_id: None,
query_scope: None,
query_owner: None,
worker_lane_id: None,
worker_kind: None,
node_id: None,
parent_node_id: None,
operator_partition: None,
execution_stream_id: None,
stage_owner_id: None,
direct_sample_count: 0,
inclusive_sample_count: 0,
resolved_function_sample_count: 0,
unresolved_function_sample_count: 0,
unwind_error_sample_count: 0,
missing_callstack_sample_count: 0,
}
}
fn function(
function_id: i64,
parent_function_id: Option<i64>,
name: impl Into<String>,
) -> RankedFunction {
RankedFunction {
semantic_id: 1,
function_id,
parent_function_id,
name: name.into(),
module_name: Some("delta_funnel".to_owned()),
source_file: Some("src/lib.rs".to_owned()),
line_number: Some(42),
self_sample_count: 0,
inclusive_sample_count: 0,
}
}
fn embedded_json(html: &str) -> Result<&str, &'static str> {
html.split_once(HTML_PROFILE_PREFIX)
.and_then(|(_, remainder)| remainder.split_once(HTML_SCRIPT_PREFIX))
.map(|(json, _)| json)
.ok_or("embedded profile data is missing")
}
fn dump_browser_dom(
browser: &std::ffi::OsStr,
html: &str,
) -> std::io::Result<std::process::Output> {
let mut report = tempfile::Builder::new().suffix(".html").tempfile()?;
report.write_all(html.as_bytes())?;
report.flush()?;
std::process::Command::new("timeout")
.arg("30s")
.arg(browser)
.args([
"--headless",
"--no-sandbox",
"--disable-gpu",
"--disable-background-networking",
"--dump-dom",
])
.arg(format!("file://{}", report.path().display()))
.output()
}
#[test]
fn renders_a_safe_self_contained_report() -> Result<(), Box<dyn std::error::Error>> {
let dangerous = "</script><img src=x onerror=alert(1)> & \"quoted\" \u{2028} \u{2029} 函数";
let mut semantic = semantic(1, None, dangerous);
semantic.operation_kind = Some("preview".to_owned());
semantic.end_ns = Some(1);
semantic.duration_ns = Some(1);
semantic.result = Some("ok".to_owned());
semantic.direct_sample_count = 1;
semantic.inclusive_sample_count = 1;
semantic.resolved_function_sample_count = 1;
let mut function = function(1, None, dangerous);
function.module_name = None;
function.source_file = None;
function.line_number = None;
function.self_sample_count = 1;
function.inclusive_sample_count = 1;
let mut profile_metadata = metadata();
profile_metadata.eligible_sample_count = 1;
profile_metadata.direct_sample_count = 1;
profile_metadata.resolved_function_sample_count = 1;
let document = RankedProfileDocument {
metadata: profile_metadata,
semantics: vec![semantic],
functions: vec![function],
};
let html = render_ranked_profile_html(&document)?;
let embedded = embedded_json(&html)?;
assert!(!embedded.contains(['<', '>', '&', '\u{2028}', '\u{2029}']));
assert!(!embedded.to_ascii_lowercase().contains("</script"));
assert!(!html.contains(dangerous));
assert!(!html.contains("https://"));
assert!(!html.contains("http://"));
assert!(html.contains(r#"role="treegrid""#));
assert!(html.contains(r#"aria-label="Ranked operations""#));
assert!(html.contains(r#"id="operations-empty""#));
assert!(html.contains(r#"id="profile-filter""#));
assert!(html.contains("Sampled CPUs"));
assert!(html.contains("Capture health"));
assert!(html.contains("Leaf symbols unresolved"));
assert!(html.contains("Profiler samples dropped"));
assert!(html.contains(r#"maxlength="200""#));
assert!(html.contains(r#"id="previous-filter-page""#));
assert!(html.contains(r#"id="next-filter-page""#));
assert!(html.contains(r#"id="show-all-frames""#));
assert!(html.contains(r#"id="expand-subtree""#));
assert!(html.contains(r#"id="collapse-subtree""#));
assert!(html.contains(r#"id="render-limit-status""#));
assert!(html.contains(r#"data-sort="duration""#));
assert!(!html.contains(r#"id="functions""#));
assert!(html.contains(r#"button.setAttribute("aria-expanded""#));
assert!(html.contains(r#""aria-selected","#));
assert!(html.contains("const maximumBulkSubtreeRows = 1000"));
assert!(html.contains("const maximumRenderedRows = 1000"));
assert!(html.contains("const maximumIndentDepth = 6"));
assert!(html.contains("const siblingPageSize = 100"));
assert!(html.contains("const containsFilter = value =>"));
assert!(html.contains("operationsBody.replaceChildren(fragment)"));
assert!(!html.contains("innerHTML"));
let decoded: serde_json::Value = serde_json::from_str(embedded)?;
assert_eq!(decoded["semantics"][0]["name"], dangerous);
assert_eq!(
decoded["semantics"][0]["display_name"],
format!("{dangerous} (operation 1)")
);
assert_eq!(decoded["functions"][0]["name"], dangerous);
assert_eq!(decoded["functions"][0]["display_name"], dangerous);
assert!(decoded["functions"][0]["compact_parent_function_id"].is_null());
assert_eq!(decoded["functions"][0]["compact_hidden"], false);
Ok(())
}
#[test]
fn renders_a_deterministic_large_tree_fixture() -> Result<(), Box<dyn std::error::Error>> {
let mut semantics = vec![semantic(1, None, "large operation")];
for semantic_id in 2..=257 {
semantics.push(semantic(
semantic_id,
Some(1),
format!("overlapping sibling {semantic_id}"),
));
}
let mut parent_semantic_id = 1;
for semantic_id in 258..=385 {
semantics.push(semantic(
semantic_id,
Some(parent_semantic_id),
format!("deep semantic {semantic_id}"),
));
parent_semantic_id = semantic_id;
}
let mut incomplete = semantic(386, Some(1), "incomplete semantic");
incomplete.semantic_kind = "activity".to_owned();
incomplete.end_ns = None;
incomplete.duration_ns = None;
incomplete.result = None;
incomplete.is_complete = false;
semantics.push(incomplete);
let mut functions = vec![function(1, None, "root function")];
for function_id in 2..=5_001 {
functions.push(function(
function_id,
None,
if function_id == 2 {
"x".repeat(512)
} else {
format!("wide function {function_id}")
},
));
}
let mut parent_function_id = 1;
for function_id in 5_002..=5_129 {
functions.push(function(
function_id,
Some(parent_function_id),
format!("deep function {function_id}"),
));
parent_function_id = function_id;
}
let mut profile_metadata = metadata();
profile_metadata.capture_complete = false;
profile_metadata.semantic_complete = false;
profile_metadata.missing_terminal_result_count = 1;
profile_metadata.perf_sample_without_callsite_count = 3;
profile_metadata.eligible_sample_count = 2;
profile_metadata.ambiguous_sample_count = 1;
profile_metadata.unattributed_sample_count = 1;
let document = RankedProfileDocument {
metadata: profile_metadata,
semantics,
functions,
};
document.validate()?;
let html = render_ranked_profile_html(&document)?;
let decoded: serde_json::Value = serde_json::from_str(embedded_json(&html)?)?;
assert_eq!(decoded["semantics"].as_array().map(Vec::len), Some(386));
assert_eq!(decoded["functions"].as_array().map(Vec::len), Some(5_129));
assert_eq!(decoded["metadata"]["capture_complete"], false);
assert_eq!(decoded["metadata"]["semantic_complete"], false);
assert_eq!(decoded["metadata"]["incomplete_operation_root_count"], 0);
assert_eq!(decoded["metadata"]["missing_identity_field_count"], 0);
assert_eq!(decoded["metadata"]["missing_terminal_result_count"], 1);
assert_eq!(decoded["metadata"]["perf_sample_without_callsite_count"], 3);
assert_eq!(decoded["functions"][0]["name"], "root function");
assert_eq!(
decoded["functions"][1]["name"].as_str().map(str::len),
Some(512)
);
assert!(html.contains("operationsBody.replaceChildren(fragment)"));
Ok(())
}
#[test]
fn browser_distinguishes_same_name_operation_roots() -> Result<(), Box<dyn std::error::Error>> {
let Some(browser) = std::env::var_os("CHROME_BIN").filter(|value| !value.is_empty()) else {
return Ok(());
};
let mut first = semantic(1, None, "Concurrent preview");
first.end_ns = Some(2_000_000);
first.duration_ns = Some(2_000_000);
let mut second = semantic(2, None, "Concurrent preview");
second.operation_id = 2;
let document = RankedProfileDocument {
metadata: metadata(),
semantics: vec![first, second],
functions: vec![],
};
document.validate()?;
let rendered = render_ranked_profile_html(&document)?;
let mut html = rendered
.strip_suffix(HTML_SUFFIX)
.ok_or("report suffix is missing")?
.to_owned();
html.push_str(
r#"</script>
<script>
(() => {
const rows = Array.from(
operationsBody.querySelectorAll('.semantic-row[aria-level="1"]')
);
const labels = rows.map(row =>
row.querySelector(".name-label").textContent
);
if (
labels.join("|") !==
"Concurrent preview (operation 1)|Concurrent preview (operation 2)"
) {
throw new Error("same-name operation rows were not distinguishable");
}
if (treeStatus.textContent !== "Selected: Concurrent preview (operation 1)") {
throw new Error("initial operation selection was not distinguishable");
}
rows[1].click();
if (treeStatus.textContent !== "Selected: Concurrent preview (operation 2)") {
throw new Error("updated operation selection was not distinguishable");
}
document.body.setAttribute("data-concurrent-identities", "passed");
})();
</script>
</body>
</html>
"#,
);
let output = dump_browser_dom(&browser, &html)?;
assert!(
output.status.success(),
"concurrent-operation browser check failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8(output.stdout)?.contains(r#"data-concurrent-identities="passed""#),
"concurrent-operation browser assertions did not complete"
);
Ok(())
}
#[test]
fn browser_distinguishes_write_all_outputs_and_query_owners()
-> Result<(), Box<dyn std::error::Error>> {
let Some(browser) = std::env::var_os("CHROME_BIN").filter(|value| !value.is_empty()) else {
return Ok(());
};
let root = semantic(1, None, "Delta Funnel SQL Server write_all");
let mut first_output = semantic(2, Some(1), "Execute output");
first_output.stage_name = Some("Execute output".to_owned());
first_output.stage_owner_id = Some(1);
let mut second_output = semantic(3, Some(1), "Execute output");
second_output.stage_name = Some("Execute output".to_owned());
second_output.stage_owner_id = Some(2);
let mut west_query = semantic(4, Some(2), "DataFusion query");
west_query.semantic_kind = "query".to_owned();
west_query.query_owner = Some("west_orders".to_owned());
let mut east_query = semantic(5, Some(3), "DataFusion query");
east_query.semantic_kind = "query".to_owned();
east_query.query_owner = Some("east_orders".to_owned());
let document = RankedProfileDocument {
metadata: metadata(),
semantics: vec![root, first_output, second_output, west_query, east_query],
functions: vec![],
};
document.validate()?;
let rendered = render_ranked_profile_html(&document)?;
let mut html = rendered
.strip_suffix(HTML_SUFFIX)
.ok_or("report suffix is missing")?
.to_owned();
html.push_str(
r#"</script>
<script>
(() => {
expandSubtree.click();
const labels = Array.from(
operationsBody.querySelectorAll(".semantic-row .name-label")
).map(label => label.textContent);
for (const expected of [
"Execute output (output 1)",
"Execute output (output 2)",
"DataFusion query (west_orders)",
"DataFusion query (east_orders)"
]) {
if (!labels.includes(expected)) {
throw new Error(`missing shared semantic display label: ${expected}`);
}
}
filterInput.value = "west_orders";
applyFilter();
const filtered = Array.from(
operationsBody.querySelectorAll(".semantic-row .name-label")
).map(label => label.textContent);
if (
!filtered.includes("Execute output (output 1)") ||
!filtered.includes("DataFusion query (west_orders)") ||
filtered.includes("Execute output (output 2)") ||
filtered.includes("DataFusion query (east_orders)")
) {
throw new Error("query-owner filtering did not preserve the exact output branch");
}
document.body.setAttribute("data-write-all-identities", "passed");
})();
</script>
</body>
</html>
"#,
);
let output = dump_browser_dom(&browser, &html)?;
assert!(
output.status.success(),
"write-all identity browser check failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8(output.stdout)?.contains(r#"data-write-all-identities="passed""#),
"write-all identity browser assertions did not complete"
);
Ok(())
}
#[test]
fn exercises_the_viewer_in_a_configured_browser() -> Result<(), Box<dyn std::error::Error>> {
let Some(browser) = std::env::var_os("CHROME_BIN").filter(|value| !value.is_empty()) else {
return Ok(());
};
let mut healthy_metadata = metadata();
healthy_metadata.perf_sample_without_callsite_count = 2;
healthy_metadata.perf_samples_skipped = 1;
let healthy_document = RankedProfileDocument {
metadata: healthy_metadata,
semantics: vec![semantic(1, None, "Healthy operation")],
functions: vec![],
};
healthy_document.validate()?;
let healthy_output =
dump_browser_dom(&browser, &render_ranked_profile_html(&healthy_document)?)?;
assert!(
healthy_output.status.success(),
"healthy report browser failed: {}",
String::from_utf8_lossy(&healthy_output.stderr)
);
let healthy_dom = String::from_utf8(healthy_output.stdout)?;
assert!(
!healthy_dom.contains(r#"<span class="summary-label">Sampling health</span>"#),
"sampling-only diagnostics changed the healthy report summary"
);
let mut operation = semantic(1, None, "Root operation");
operation.end_ns = Some(10_000_000);
operation.duration_ns = Some(10_000_000);
let mut zeta = semantic(2, Some(1), "Zeta phase");
zeta.end_ns = Some(2_000_000);
zeta.duration_ns = Some(2_000_000);
let mut alpha = semantic(3, Some(1), "Alpha phase");
alpha.end_ns = Some(1_000_000);
alpha.duration_ns = Some(1_000_000);
let mut zero = semantic(7_000, Some(3), "Zero duration");
zero.end_ns = Some(0);
zero.duration_ns = Some(0);
let mut unknown = semantic(7_001, Some(3), "Unknown duration");
unknown.semantic_kind = "activity".to_owned();
unknown.end_ns = None;
unknown.duration_ns = None;
unknown.result = None;
unknown.is_complete = false;
let mut semantics = vec![operation, zeta, alpha, zero, unknown];
for group in 0..10 {
let group_id = 4 + group * 101;
semantics.push(semantic(group_id, Some(1), format!("Group {group:02}")));
for child in 1..=100 {
semantics.push(semantic(
group_id + child,
Some(group_id),
format!("Group {group:02} child {child:03}"),
));
}
}
let mut deep_parent_id = 3;
for semantic_id in 2_000..2_040 {
semantics.push(semantic(
semantic_id,
Some(deep_parent_id),
format!("Deep semantic {semantic_id}"),
));
deep_parent_id = semantic_id;
}
let mut distributed_cases = semantic(3_000, Some(1), "Distributed cases");
distributed_cases.direct_sample_count = 1;
distributed_cases.inclusive_sample_count = 1;
distributed_cases.resolved_function_sample_count = 1;
semantics[0].inclusive_sample_count = 1;
semantics.push(distributed_cases);
let mut distributed_id = 3_001;
for case in 0..100 {
let mut parent_id = 3_000;
for depth in 0..10 {
let name = if depth == 9 {
format!("distributed target {case:03}")
} else {
format!("Distributed {case:03} context {depth:02}")
};
semantics.push(semantic(distributed_id, Some(parent_id), name));
parent_id = distributed_id;
distributed_id += 1;
}
}
semantics.push(semantic(4_500, Some(3_000), "Distributed overflow child"));
let mut oversized_parent_id = 3_000;
for semantic_id in 5_000..6_000 {
let name = if semantic_id == 5_999 {
"oversized target".to_owned()
} else {
format!("Oversized context {semantic_id}")
};
semantics.push(semantic(semantic_id, Some(oversized_parent_id), name));
oversized_parent_id = semantic_id;
}
let mut functions = (1..=101)
.map(|function_id| {
let worker = match function_id {
1 => " worker 1",
10 => " worker 10",
14 => " worker 14",
_ => "",
};
let detail = if function_id == 1 {
format!("{worker} {}", "x".repeat(480))
} else {
worker.to_owned()
};
function(
function_id,
None,
format!("match function {function_id:03}{detail}"),
)
})
.collect::<Vec<_>>();
let nested_symbol =
"<tokio::runtime::blocking::task::BlockingTask<F> as core::future::Future>::poll";
functions.push(function(102, Some(1), nested_symbol));
functions.push(function(104, Some(1), "second function branch"));
let mut wide_owner_function = function(103, Some(105), "wide owner native root");
wide_owner_function.semantic_id = 3_000;
wide_owner_function.self_sample_count = 1;
wide_owner_function.inclusive_sample_count = 1;
functions.push(wide_owner_function);
let mut runtime_wrapper = function(105, None, "runtime wrapper");
runtime_wrapper.semantic_id = 3_000;
runtime_wrapper.inclusive_sample_count = 1;
functions.push(runtime_wrapper);
let mut profile_metadata = metadata();
profile_metadata.capture_complete = false;
profile_metadata.semantic_complete = false;
profile_metadata.missing_terminal_result_count = 1;
profile_metadata.buffer_loss_count = 2;
profile_metadata.perf_sample_without_callsite_count = 3;
profile_metadata.eligible_sample_count = 1;
profile_metadata.direct_sample_count = 1;
profile_metadata.resolved_function_sample_count = 1;
let document = RankedProfileDocument {
metadata: profile_metadata,
semantics,
functions,
};
document.validate()?;
let rendered = render_ranked_profile_html(&document)?;
let mut html = rendered
.strip_suffix(HTML_SUFFIX)
.ok_or("report suffix is missing")?
.to_owned();
html.push_str(
r#"</script>
<script>
(() => {
const check = (condition, message) => {
if (!condition) throw new Error(message);
};
try {
check(
summary.textContent.includes("CaptureIncomplete") &&
summary.textContent.includes("2 buffer losses") &&
summary.textContent.includes("3 perf samples without callsites"),
"incomplete capture health was not summarized"
);
check(operationsBody.rows.length === 1, "initial render was not lazy");
const rootRow = operationsBody.rows[0];
check(rootRow.getAttribute("aria-selected") === "true", "root was not selected");
rootRow.focus();
rootRow.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowRight",
bubbles: true
}));
check(operationsBody.rows.length === 102, "high-fanout expansion was not paged");
check(
operationsBody.rows[0].getAttribute("aria-expanded") === "true",
"expanded state was not exposed"
);
let pagination = operationsBody.querySelector(".pagination-row");
check(pagination !== null, "sibling pagination was not rendered");
check(
pagination.textContent.includes("1-100 of 114"),
"first sibling page status was incorrect"
);
check(
pagination.getAttribute("aria-label") === "Root operation children pagination",
"pagination row did not identify its sibling group"
);
check(
pagination.querySelectorAll("button")[1].getAttribute("aria-label") ===
"Next page for Root operation children",
"pagination button did not identify its sibling group"
);
const lastFirstPageRow = operationsBody.rows[100];
lastFirstPageRow.focus();
lastFirstPageRow.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowDown",
bubbles: true
}));
check(
document.activeElement === pagination && pagination.tabIndex === 0,
"Arrow Down did not reach sibling pagination"
);
pagination.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowUp",
bubbles: true
}));
check(
document.activeElement === lastFirstPageRow,
"Arrow Up did not leave sibling pagination"
);
pagination.querySelectorAll("button")[1].click();
check(operationsBody.rows.length === 16, "second sibling page was incorrect");
pagination = operationsBody.querySelector(".pagination-row");
check(
pagination.textContent.includes("101-114 of 114"),
"second sibling page status was incorrect"
);
pagination.querySelector("button").click();
check(operationsBody.rows.length === 102, "previous sibling page was incorrect");
const distributedCasesRow = Array.from(
operationsBody.querySelectorAll(".semantic-row")
).find(row => row.textContent.includes("Distributed cases"));
check(distributedCasesRow !== undefined, "wide semantic owner was not rendered");
const distributedCells = distributedCasesRow.querySelectorAll("td");
const coverageDetail = distributedCells[4].querySelector(".detail");
check(
coverageDetail.title === coverageDetail.textContent,
"full numeric detail was not available on hover"
);
check(
getComputedStyle(coverageDetail).overflow === "hidden" &&
getComputedStyle(coverageDetail).textOverflow === "ellipsis" &&
getComputedStyle(coverageDetail).whiteSpace === "nowrap",
"numeric detail was not constrained to one line"
);
check(
coverageDetail.scrollWidth > coverageDetail.clientWidth,
"long numeric detail did not actually overflow"
);
check(
coverageDetail.getBoundingClientRect().right <=
distributedCells[4].getBoundingClientRect().right &&
distributedCells[4].getBoundingClientRect().right <=
distributedCells[5].getBoundingClientRect().left,
"numeric detail overlapped the adjacent cell"
);
distributedCasesRow.querySelector(".disclosure").click();
const wideOwnerFunction = Array.from(
operationsBody.querySelectorAll(".function-row")
).find(row => row.textContent.includes("wide owner native root"));
check(
wideOwnerFunction !== undefined,
"wide semantic owner did not reveal its native function root"
);
check(
!operationsBody.textContent.includes("runtime wrapper"),
"compact view retained a zero-cost single-child frame"
);
selectedNode = {
kind: "function",
value: functionsByKey.get("f:3000:103")
};
showAllFrames.click();
check(
selectedNode.kind === "semantic" &&
selectedNode.value.semantic_id === 3000,
"all-frames view did not retain the selected semantic context"
);
const runtimeWrapper = Array.from(
operationsBody.querySelectorAll(".function-row")
).find(row => row.textContent.includes("runtime wrapper"));
check(runtimeWrapper !== undefined, "all-frames view omitted a captured frame");
check(
!operationsBody.textContent.includes("wide owner native root"),
"all-frames view flattened a captured parent-child edge"
);
runtimeWrapper.querySelector(".disclosure").click();
check(
operationsBody.textContent.includes("wide owner native root"),
"all-frames view did not expand the captured child"
);
selectedNode = {
kind: "function",
value: functionsByKey.get("f:3000:105")
};
showAllFrames.click();
check(
selectedNode.kind === "semantic" &&
selectedNode.value.semantic_id === 3000 &&
operationsBody.textContent.includes("wide owner native root") &&
!operationsBody.textContent.includes("runtime wrapper"),
"returning to compact view did not restore a valid selection and tree"
);
const wideSemanticPagination = Array.from(
operationsBody.querySelectorAll(".pagination-row")
).find(row =>
row.getAttribute("aria-label") ===
"Distributed cases semantic children pagination"
);
check(
wideSemanticPagination !== undefined &&
wideSemanticPagination.textContent.includes("1-100 of 102"),
"wide semantic children were not paged independently"
);
check(
wideOwnerFunction.rowIndex < wideSemanticPagination.rowIndex,
"native function roots were hidden behind semantic pagination"
);
distributedCasesRow.querySelector(".disclosure").click();
let functionRootRow = Array.from(
operationsBody.querySelectorAll(".function-row")
).find(row => row.textContent.includes("match function 001"));
check(functionRootRow !== undefined, "nested function root was not rendered");
const functionName = functionRootRow.querySelector(".name-label");
check(
functionName.title === functionName.textContent,
"full function symbol was not available on hover"
);
check(
getComputedStyle(functionName).whiteSpace === "nowrap" &&
getComputedStyle(functionName).textOverflow === "ellipsis",
"function symbol was not constrained to one line"
);
check(
functionName.scrollWidth > functionName.clientWidth,
"long function symbol did not actually overflow"
);
const shortFunctionRow = Array.from(
operationsBody.querySelectorAll(".function-row")
).find(row => row.textContent.includes("match function 002"));
check(
functionRootRow.getBoundingClientRect().height ===
shortFunctionRow.getBoundingClientRect().height,
"long function symbol increased its row height"
);
functionRootRow.focus();
functionRootRow.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowRight",
bubbles: true
}));
const nestedFunctionRow = Array.from(
operationsBody.querySelectorAll(".function-row")
).find(row => row.textContent.includes("BlockingTask::poll"));
check(nestedFunctionRow !== undefined, "nested function was not expanded");
const nestedFunctionName = nestedFunctionRow.querySelector(".name-label");
check(
nestedFunctionName.textContent === "BlockingTask::poll" &&
nestedFunctionName.title.includes("tokio::runtime::blocking"),
"compact function name did not preserve the full symbol on hover"
);
nestedFunctionRow.click();
check(
treeStatus.textContent === "Selected: BlockingTask::poll",
"selected function status did not use the compact name"
);
check(
nestedFunctionRow.getAttribute("aria-level") === "3",
"nested function hierarchy was incorrect"
);
functionRootRow = Array.from(
operationsBody.querySelectorAll(".function-row")
).find(row => row.textContent.includes("match function 001"));
functionRootRow.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowLeft",
bubbles: true
}));
check(
!operationsBody.textContent.includes("BlockingTask::poll"),
"nested function was not collapsed"
);
document.querySelector('[data-sort="name"]').click();
const semanticNames = Array.from(
operationsBody.querySelectorAll('.semantic-row[aria-level="2"] .name-line'),
line => line.querySelector("span:not(.leaf):not(.match-label)").textContent
);
check(
semanticNames.filter(name => name.endsWith(" phase")).join(",") ===
"Alpha phase,Zeta phase",
"name sorting flattened or misordered semantic siblings"
);
expanded.add("s:3");
document.querySelector('[data-sort="duration"]').click();
let durationNames = Array.from(
operationsBody.querySelectorAll('.semantic-row[aria-level="3"] .name-line'),
line => line.querySelector("span:not(.leaf):not(.match-label)").textContent
);
check(
durationNames.join(",") ===
"Deep semantic 2000,Zero duration,Unknown duration",
"descending duration sort treated an unknown duration as zero"
);
document.querySelector('[data-sort="duration"]').click();
durationNames = Array.from(
operationsBody.querySelectorAll('.semantic-row[aria-level="3"] .name-line'),
line => line.querySelector("span:not(.leaf):not(.match-label)").textContent
);
check(
durationNames.join(",") ===
"Zero duration,Deep semantic 2000,Unknown duration",
"ascending duration sort did not keep an unknown duration last"
);
const deepKeys = [
"s:3",
...Array.from({ length: 39 }, (_, offset) => `s:${2000 + offset}`)
];
deepKeys.forEach(key => expanded.add(key));
renderRows();
const deepRow = operationsBody.querySelector('[aria-level="42"]');
check(
deepRow.querySelector(".depth-label").textContent === "Depth 42",
"deep hierarchy was visually flattened"
);
check(
deepRow.querySelector(".name-label").clientWidth > 0,
"deep hierarchy left no visible name width"
);
deepKeys.forEach(key => expanded.delete(key));
renderRows();
const groupKeys = Array.from(
{ length: 10 },
(_, group) => `s:${4 + group * 101}`
);
groupKeys.forEach(key => expanded.add(key));
renderRows();
check(
operationsBody.rows.length === maximumRenderedRows,
"visible row budget was not enforced"
);
check(
renderLimitStatus.textContent.includes("first 1000 visible rows"),
"visible row limit was not explained"
);
groupKeys.forEach(key => expanded.delete(key));
expanded.add(groupKeys[0]);
selectedNode = { kind: "semantic", value: semanticsById.get(4) };
renderRows();
collapseSubtree.click();
check(!expanded.has(groupKeys[0]), "bounded subtree collapse failed");
selectedNode = { kind: "semantic", value: semanticsById.get(1) };
filterInput.value = "worker 1";
applyFilter();
check(filterResults.length === 1, "numeric filter count was incorrect");
check(operationsBody.textContent.includes("worker 1"), "numeric filter missed worker 1");
check(!operationsBody.textContent.includes("worker 10"), "numeric filter matched worker 10");
check(!operationsBody.textContent.includes("worker 14"), "numeric filter matched worker 14");
filterInput.value = "delta_funnel";
applyFilter();
check(
filterResults.length === 104 &&
filterResults.every(result => result.function_id !== undefined),
"module metadata was not searchable"
);
filterInput.value = "src/lib.rs:42";
applyFilter();
check(
filterResults.length === 104 &&
filterResults.every(result => result.function_id !== undefined),
"source metadata was not searchable"
);
filterInput.value = "BlockingTask::poll";
applyFilter();
check(
filterResults.length === 1 &&
operationsBody.textContent.includes("BlockingTask::poll"),
"compact function names were not searchable"
);
const originalSemanticLookup = semanticsById.get;
let oversizedAncestorLookups = 0;
semanticsById.get = key => {
oversizedAncestorLookups += 1;
return originalSemanticLookup.call(semanticsById, key);
};
filterInput.value = "oversized target";
applyFilter();
semanticsById.get = originalSemanticLookup;
check(filterResults.length === 1, "oversized filter count was incorrect");
check(
oversizedAncestorLookups <= maximumRenderedRows,
"oversized filter traversed its complete ancestor chain"
);
check(operationsBody.rows.length === 1, "oversized match was not rendered flat");
check(
operationsBody.querySelector(".match-label").textContent === "Match",
"oversized match was not labeled"
);
check(
renderLimitStatus.textContent.includes("Ancestor context was omitted"),
"omitted oversized context was not explained"
);
filterInput.value = "distributed target";
applyFilter();
check(filterResults.length === 100, "distributed filter count was incorrect");
check(
filterStatus.textContent === "Showing 1-99 of 100 matches.",
"filter page did not account for ancestor context"
);
check(operationsBody.rows.length === 992, "first context-limited page was incorrect");
check(
Array.from(operationsBody.querySelectorAll(".match-label"))
.filter(label => label.textContent === "Match").length === 99,
"first context-limited page omitted a declared match"
);
nextFilterPage.click();
check(
filterStatus.textContent === "Showing 100-100 of 100 matches.",
"second context-limited page status was incorrect"
);
check(operationsBody.rows.length === 12, "second context-limited page was incorrect");
check(
Array.from(operationsBody.querySelectorAll(".match-label"))
.filter(label => label.textContent === "Match").length === 1,
"second context-limited page omitted its match"
);
filterInput.value = "match function";
applyFilter();
check(filterResults.length === 101, "filter match count was incorrect");
check(operationsBody.rows.length === 101, "first filter page was not bounded");
const matchCount = Array.from(
operationsBody.querySelectorAll(".match-label")
).filter(label => label.textContent === "Match").length;
check(matchCount === 100, `first filter page labeled ${matchCount} matches`);
check(
operationsBody.querySelectorAll(".filter-context").length === 1,
"first filter page did not retain its context"
);
nextFilterPage.click();
check(operationsBody.rows.length === 2, "second filter page was incorrect");
check(
filterStatus.textContent === "Showing 101-101 of 101 matches.",
"second filter page status was incorrect"
);
clearFilter.click();
check(operationsBody.rows.length === 102, "clear did not restore expansion state");
const expandedRoot = operationsBody.rows[0];
expandedRoot.focus();
expandedRoot.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowLeft",
bubbles: true
}));
check(operationsBody.rows.length === 1, "ordinary collapse failed");
document.documentElement.dataset.viewerSmoke = "passed";
} catch (error) {
document.documentElement.dataset.viewerSmoke = `failed:${error.message}`;
}
})();
</script>
</body>
</html>
"#,
);
let output = dump_browser_dom(&browser, &html)?;
assert!(
output.status.success(),
"headless browser failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let dom = String::from_utf8(output.stdout)?;
let result = dom
.split_once("data-viewer-smoke=\"")
.and_then(|(_, result)| result.split_once('"'))
.map(|(result, _)| result)
.unwrap_or("missing");
assert_eq!(result, "passed", "browser smoke result");
Ok(())
}
#[test]
fn atomically_replaces_output_and_preserves_it_on_failure() -> std::io::Result<()> {
let directory = tempfile::tempdir()?;
let output = directory.path().join("nested/report.profile.html");
write_ranked_profile_html(
&output,
"first complete report",
ExistingOutputPolicy::Replace,
)
.map_err(|error| std::io::Error::other(error.to_string()))?;
assert_eq!(fs::read_to_string(&output)?, "first complete report");
write_ranked_profile_html(&output, "complete report", ExistingOutputPolicy::Replace)
.map_err(|error| std::io::Error::other(error.to_string()))?;
assert_eq!(fs::read_to_string(&output)?, "complete report");
assert_eq!(
fs::read_dir(output.parent().expect("output has a parent"))?.count(),
1
);
let blocked_output = directory.path().join("existing-output");
fs::create_dir(&blocked_output)?;
fs::write(blocked_output.join("keep-me"), "unchanged")?;
let error = write_ranked_profile_html(
&blocked_output,
"partial report",
ExistingOutputPolicy::Replace,
)
.expect_err("a report cannot replace an existing directory");
assert_eq!(error.phase(), RankedReportFailurePhase::Output);
assert_eq!(error.kind(), "persist_failed");
assert_eq!(
fs::read_to_string(blocked_output.join("keep-me"))?,
"unchanged"
);
let error =
write_ranked_profile_html(&output, "must not replace", ExistingOutputPolicy::Preserve)
.expect_err("no-clobber output must preserve an existing report");
assert_eq!(error.phase(), RankedReportFailurePhase::Output);
assert_eq!(error.kind(), "persist_failed");
assert_eq!(fs::read_to_string(&output)?, "complete report");
Ok(())
}
}