Skip to content

Commit 66cc048

Browse files
guillesdevertlammerts
authored andcommitted
make phases expandable
1 parent 356a2ad commit 66cc048

1 file changed

Lines changed: 77 additions & 12 deletions

File tree

duckdb/query_graph/__main__.py

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,23 @@
9595
background: var(--doc-codebox-border-color);
9696
}
9797
98+
tbody tr.phase-details-row {
99+
border-bottom: none;
100+
}
101+
102+
tbody tr.phase-details-row:hover {
103+
background: transparent;
104+
}
105+
106+
tbody tr.phase-details-row details summary {
107+
font-size: 12px;
108+
padding: 4px 0;
109+
}
110+
111+
tbody tr.phase-details-row details[open] summary {
112+
margin-bottom: 4px;
113+
}
114+
98115
/* === Chart/Card Section === */
99116
.chart {
100117
padding: 20px;
@@ -242,6 +259,7 @@
242259
color: #1a1a1a !important;
243260
}
244261
262+
/* Fix metric title visibility in dark mode */
245263
.chart .metric-title {
246264
color: #b3b3b3;
247265
}
@@ -315,7 +333,7 @@ def get_f7fff0_shade_hex(fraction: float) -> str:
315333

316334
# Define RGB for light and dark end
317335
light_color = (247, 255, 240) # #f7fff0
318-
dark_color = (200, 255, 150) # slightly darker/more saturated green-yellow
336+
dark_color = (200, 255, 150) # slightly darker/more saturated green-yellow
319337

320338
# Interpolate RGB channels
321339
r = int(light_color[0] + (dark_color[0] - light_color[0]) * fraction)
@@ -325,7 +343,9 @@ def get_f7fff0_shade_hex(fraction: float) -> str:
325343
return f"#{r:02x}{g:02x}{b:02x}"
326344

327345

328-
def get_node_body(name: str, result: str, cpu_time: float, card: int, est: int, result_size: int, extra_info: str) -> str: # noqa: D103
346+
def get_node_body(
347+
name: str, result: str, cpu_time: float, card: int, est: int, result_size: int, extra_info: str
348+
) -> str: # noqa: D103
329349
"""
330350
Generate the HTML body for a single node in the tree.
331351
"""
@@ -389,8 +409,8 @@ def generate_tree_recursive(json_graph: object, cpu_time: float) -> str: # noqa
389409
return node_prefix_html + node_body + children_html + node_suffix_html
390410

391411

392-
# For generating the table in the top left.
393-
def generate_timing_html(graph_json: object, query_timings: object) -> object: # noqa: D103
412+
# For generating the table in the top left with expandable phases
413+
def generate_timing_html(graph_json: object, query_timings: object) -> object:
394414
json_graph = json.loads(graph_json)
395415
gather_timing_information(json_graph, query_timings)
396416
table_head = """
@@ -411,28 +431,73 @@ def generate_timing_html(graph_json: object, query_timings: object) -> object:
411431
all_phases = query_timings.get_phases()
412432
query_timings.add_node_timing(NodeTiming("Execution Time (CPU)", execution_time, None))
413433
all_phases = ["Execution Time (CPU)", *all_phases]
434+
414435
for phase in all_phases:
415436
summarized_phase = query_timings.get_summary_phase_timings(phase)
416437
summarized_phase.calculate_percentage(execution_time)
417438
phase_column = f"<b>{phase}</b>" if phase == "Execution Time (CPU)" else phase
439+
440+
# Main phase row
418441
table_body += f"""
419442
<tr>
420443
<td>{phase_column}</td>
421444
<td>{round(summarized_phase.time, 8)}</td>
422445
<td>{str(summarized_phase.percentage * 100)[:6]}%</td>
423446
</tr>
424447
"""
448+
449+
# Add expandable details for individual nodes (except for Execution Time)
450+
if phase != "Execution Time (CPU)":
451+
phase_timings = query_timings.get_phase_timings(phase)
452+
if len(phase_timings) > 1: # Only show details if there are multiple nodes
453+
table_body += f"""
454+
<tr class="phase-details-row">
455+
<td colspan="3">
456+
<details>
457+
<summary style="cursor: pointer; padding: 4px 0; color: var(--text-secondary-color);">
458+
Show {len(phase_timings)} nodes
459+
</summary>
460+
<table style="margin: 8px 0; width: 100%; border: none; box-shadow: none;">
461+
<tbody>
462+
"""
463+
for node_timing in sorted(phase_timings, key=lambda x: x.time, reverse=True):
464+
node_timing.calculate_percentage(execution_time)
465+
depth_indent = "&nbsp;" * (node_timing.depth * 4)
466+
table_body += f"""
467+
<tr style="background: var(--doc-codebox-background-color);">
468+
<td style="padding: 4px 12px; border: none;">{depth_indent}↳ Depth {node_timing.depth}</td>
469+
<td style="padding: 4px 12px; border: none;">{round(node_timing.time, 8)}</td>
470+
<td style="padding: 4px 12px; border: none;">{str(node_timing.percentage * 100)[:6]}%</td>
471+
</tr>
472+
"""
473+
table_body += """
474+
</tbody>
475+
</table>
476+
</details>
477+
</td>
478+
</tr>
479+
"""
480+
425481
table_body += table_end
426482
return table_head + table_body
427483

484+
428485
def generate_metric_grid_html(graph_json: str) -> str: # noqa: D103
429486
json_graph = json.loads(graph_json)
430487
metrics = {
431-
"Execution Time (s)": f"{float(json_graph.get("latency", "N/A")):.4f}",
432-
"Total GB Read": f"{float(json_graph.get("total_bytes_read", "N/A")) / (1024 ** 3):.4f}" if json_graph.get("total_bytes_read", "N/A") != "N/A" else "N/A",
433-
"Total GB Written": f"{float(json_graph.get("total_bytes_written", "N/A")) / (1024 ** 3):.4f}" if json_graph.get("total_bytes_written", "N/A") != "N/A" else "N/A",
434-
"Peak Memory (GB)": f"{float(json_graph.get("system_peak_buffer_memory", "N/A")) / (1024 ** 3):.4f}" if json_graph.get("system_peak_buffer_memory", "N/A") != "N/A" else "N/A",
435-
"Rows Scanned": f"{json_graph.get("cumulative_rows_scanned", "N/A"):,}" if json_graph.get("cumulative_rows_scanned", "N/A") != "N/A" else "N/A",
488+
"Execution Time (s)": f"{float(json_graph.get('latency', 'N/A')):.4f}",
489+
"Total GB Read": f"{float(json_graph.get('total_bytes_read', 'N/A')) / (1024**3):.4f}"
490+
if json_graph.get("total_bytes_read", "N/A") != "N/A"
491+
else "N/A",
492+
"Total GB Written": f"{float(json_graph.get('total_bytes_written', 'N/A')) / (1024**3):.4f}"
493+
if json_graph.get("total_bytes_written", "N/A") != "N/A"
494+
else "N/A",
495+
"Peak Memory (GB)": f"{float(json_graph.get('system_peak_buffer_memory', 'N/A')) / (1024**3):.4f}"
496+
if json_graph.get("system_peak_buffer_memory", "N/A") != "N/A"
497+
else "N/A",
498+
"Rows Scanned": f"{json_graph.get('cumulative_rows_scanned', 'N/A'):,}"
499+
if json_graph.get("cumulative_rows_scanned", "N/A") != "N/A"
500+
else "N/A",
436501
}
437502
metric_grid_html = """<div class="metrics-grid">"""
438503
for key in metrics.keys():
@@ -445,6 +510,7 @@ def generate_metric_grid_html(graph_json: str) -> str: # noqa: D103
445510
metric_grid_html += "</div>"
446511
return metric_grid_html
447512

513+
448514
def generate_sql_query_html(graph_json: str) -> str: # noqa: D103
449515
json_graph = json.loads(graph_json)
450516
sql_query = json_graph.get("query_name", "N/A")
@@ -459,6 +525,7 @@ def generate_sql_query_html(graph_json: str) -> str: # noqa: D103
459525
"""
460526
return sql_html
461527

528+
462529
def generate_tree_html(graph_json: object) -> str: # noqa: D103
463530
json_graph = json.loads(graph_json)
464531
cpu_time = float(json_graph["cpu_time"])
@@ -485,9 +552,7 @@ def generate_ipython(json_input: str) -> str: # noqa: D103
485552

486553
def generate_style_html(graph_json: str, include_meta_info: bool) -> None: # noqa: D103, FBT001
487554
treeflex_css = '<link rel="stylesheet" href="https://unpkg.com/treeflex/dist/css/treeflex.css">\n'
488-
libraries = (
489-
'<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">\n'
490-
)
555+
libraries = '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">\n'
491556
return {"treeflex_css": treeflex_css, "duckdb_css": qgraph_css, "libraries": libraries, "chart_script": ""}
492557

493558

0 commit comments

Comments
 (0)