Skip to content

Commit 02e0514

Browse files
committed
Add plot.js and d3
1 parent 7ec9868 commit 02e0514

File tree

6 files changed

+273
-86
lines changed

6 files changed

+273
-86
lines changed

report/parse_logs.py

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def _parse_steps_by_chat_pairs(self, agent_logs: list[LogPart]) -> list[dict]:
190190
current_step_parts = []
191191

192192
for log_part in logs_to_process:
193-
if "agent-step" in log_part.content:
193+
if "agent-step" in log_part.content or "Trial ID:" in log_part.content:
194194
continue
195195

196196
# A chat_response marks the beginning of a new step.
@@ -210,52 +210,77 @@ def _parse_steps_by_chat_pairs(self, agent_logs: list[LogPart]) -> list[dict]:
210210

211211
return steps
212212

213-
def _syntax_highlight_content(self, content: str) -> str:
214-
"""Syntax highlights the content."""
215-
216-
content = html.escape(content)
217-
218-
# Simple pre-formatted blocks
219-
content = re.sub(r'<conclusion>(.*?)</conclusion>',
220-
r'<pre class="conclusion-block">\1</pre>',
221-
content,
222-
flags=re.DOTALL)
223-
content = re.sub(r'<reason>(.*?)</reason>',
224-
r'<pre class="reason-block">\1</pre>',
225-
content,
226-
flags=re.DOTALL)
227-
228-
# Code blocks with language specification
229-
content = re.sub(r'<bash>(.*?)</bash>',
230-
r'<pre><code class="language-bash">\1</code></pre>',
231-
content,
232-
flags=re.DOTALL)
233-
content = re.sub(r'<build_script>(.*?)</build_script>',
234-
r'<pre><code class="language-c++">\1</code></pre>',
235-
content,
236-
flags=re.DOTALL)
237-
content = re.sub(r'<fuzz_target>(.*?)</fuzz_target>',
238-
r'<pre><code class="language-c++">\1</code></pre>',
239-
content,
240-
flags=re.DOTALL)
241-
242-
# Standard output/error with return code
243-
content = re.sub(
244-
r'<stdout>(.*?)</stdout>',
245-
r'<h4>Standard Output</h4><pre><code class="language-bash">\1</code></pre>',
246-
content,
247-
flags=re.DOTALL)
248-
content = re.sub(
249-
r'<stderr>(.*?)</stderr>',
250-
r'<h4>Standard Error</h4><pre><code class="language-bash">\1</code></pre>',
251-
content,
252-
flags=re.DOTALL)
253-
content = re.sub(r'<return_code>(.*?)</return_code>',
254-
r'<h4>Return Code</h4><pre><code>\1</code></pre>',
255-
content,
256-
flags=re.DOTALL)
257-
258-
return content
213+
def _syntax_highlight_content(self, content: str, default_language: str = "") -> str:
214+
"""Syntax highlights content while preserving visible tags."""
215+
216+
# Escape everything first so raw logs are safe to render in HTML
217+
escaped = html.escape(content)
218+
219+
# Helper to simplify substitutions
220+
def _sub(pattern: str, repl: str, text: str) -> str:
221+
return re.sub(pattern, repl, text, flags=re.DOTALL)
222+
223+
def _normalize_lang(lang: str) -> str:
224+
if not lang:
225+
return 'cpp'
226+
lang = lang.strip().lower()
227+
if lang in ['c++', 'cpp', 'cxx']:
228+
return 'cpp'
229+
if lang in ['c']:
230+
return 'c'
231+
if lang in ['python', 'py']:
232+
return 'python'
233+
if lang in ['java']:
234+
return 'java'
235+
if lang in ['rust', 'rs']:
236+
return 'rust'
237+
if lang in ['go', 'golang']:
238+
return 'go'
239+
return 'cpp'
240+
241+
lang_key = _normalize_lang(default_language)
242+
243+
escaped = _sub(r'&lt;conclusion&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/conclusion&gt;',
244+
r'<span class="log-tag">&lt;conclusion&gt;</span>'
245+
r'<pre class="whitespace-pre-wrap break-words overflow-x-auto reason-block">\1</pre>'
246+
r'<span class="log-tag">&lt;/conclusion&gt;</span>', escaped)
247+
escaped = _sub(r'&lt;reason&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/reason&gt;',
248+
r'<span class="log-tag">&lt;reason&gt;</span>'
249+
r'<div class="markdown-block whitespace-pre-wrap break-words overflow-x-auto">\1</div>'
250+
r'<span class="log-tag">&lt;/reason&gt;</span>', escaped)
251+
252+
escaped = _sub(r'&lt;bash&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/bash&gt;',
253+
r'<span class="log-tag">&lt;bash&gt;</span>'
254+
r'<pre class="whitespace-pre-wrap break-words overflow-x-auto"><code class="language-bash">\1</code></pre>'
255+
r'<span class="log-tag">&lt;/bash&gt;</span>',
256+
escaped)
257+
escaped = _sub(r'&lt;build_script&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/build_script&gt;',
258+
r'<span class="log-tag">&lt;build_script&gt;</span>'
259+
r'<pre class="whitespace-pre-wrap break-words overflow-x-auto"><code class="language-cpp">\1</code></pre>'
260+
r'<span class="log-tag">&lt;/build_script&gt;</span>',
261+
escaped)
262+
escaped = _sub(r'&lt;fuzz target&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/fuzz target&gt;',
263+
rf'<span class="log-tag">&lt;fuzz target&gt;</span>'
264+
rf'<pre class="whitespace-pre-wrap break-words overflow-x-auto"><code class="language-{lang_key}">\1</code></pre>'
265+
rf'<span class="log-tag">&lt;/fuzz target&gt;</span>',
266+
escaped)
267+
268+
escaped = _sub(r'&lt;stdout&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/stdout&gt;',
269+
r'<span class="log-tag">&lt;stdout&gt;</span>'
270+
r'<pre class="whitespace-pre-wrap break-words overflow-x-auto"><code class="language-bash">\1</code></pre>'
271+
r'<span class="log-tag">&lt;/stdout&gt;</span>',
272+
escaped)
273+
escaped = _sub(r'&lt;stderr&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/stderr&gt;',
274+
r'<span class="log-tag">&lt;stderr&gt;</span>'
275+
r'<pre class="whitespace-pre-wrap break-words overflow-x-auto"><code class="language-bash">\1</code></pre>'
276+
r'<span class="log-tag">&lt;/stderr&gt;</span>',
277+
escaped)
278+
escaped = _sub(r'&lt;return_code&gt;(\s*[^\s].*?[^\s]\s*|(?:\s*[^\s].*?)?)&lt;/return_code&gt;',
279+
r'<span class="log-tag">&lt;return_code&gt;</span>'
280+
r'<pre class="whitespace-pre-wrap break-words overflow-x-auto"><code>\1</code></pre>'
281+
r'<span class="log-tag">&lt;/return_code&gt;</span>', escaped)
282+
283+
return escaped
259284

260285
def _create_step_data(self, step_number: int,
261286
log_parts: list[LogPart]) -> dict:
@@ -285,20 +310,6 @@ def get_agent_sections(self) -> dict[str, list[LogPart]]:
285310
current_agent = None
286311
agent_counters = {}
287312

288-
# NUMBER OF BASH TOOLS REQUESTED BY LLMS
289-
# system prompt is itself
290-
# subsequent ones will be LLM request tool -> tool execution
291-
292-
# NO REASON IF THERE'S BASH
293-
# JUST NEED CONCLUSION
294-
# SO INSTEAD STEP 4 - BASH REASON CONCLUSION FUZZ TARGET BUILDSCRIPT -> STEP 4 - BASH, CONCLUSION
295-
296-
# bash syntax highlight - shell
297-
# build script - c or c++
298-
# stdout/stderror - console/shell/bash - BASH
299-
300-
# moving to html
301-
302313
for log_part in self._logs:
303314
lines = log_part.content.split('\n')
304315
agent_headers = []

report/templates/base.html

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,14 @@
8787
background-color: #333333;
8888
}
8989
.chat_prompt {
90-
background-color: #fff7f2;
90+
background-color: #e2e8f0;
91+
border: 1px solid #a0aec0;
9192
max-width: 50%;
9293
overflow: scroll;
9394
}
9495
.chat_response {
95-
background-color: #fcfff2;
96+
background-color: #ececec;
97+
border: 1px solid #718096;
9698
max-width: 50%;
9799
overflow: scroll;
98100
margin-left: auto;
@@ -507,6 +509,8 @@ <h2 class="text-lg font-bold">Search Results</h2>
507509

508510
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.10.3/cdn.min.js" defer></script>
509511
<script src="https://cdn.jsdelivr.net/npm/chart.js" defer></script>
512+
<script src="d3.min.js" defer></script>
513+
<script src="plot.min.js" defer></script>
510514

511515
{% if unified_data %}
512516
<script type="application/json" id="unified-data">{{ unified_data|tojson|safe }}</script>
@@ -909,16 +913,51 @@ <h2 class="signature">
909913
});
910914

911915
</script>
912-
913-
</body>
914-
<!-- added highlight.js for syntax highlighting -->
915-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css">
916-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
917-
<!-- some additional language -->
918-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/bash.min.js"></script>
919-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/c.min.js"></script>
920-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/cpp.min.js"></script>
921-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/java.min.js"></script>
922-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
923-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/rust.min.js"></script>
924-
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/go.min.js"></script>
916+
<!-- added highlight.js for syntax highlighting -->
917+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css">
918+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
919+
<!-- additional languages -->
920+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/bash.min.js"></script>
921+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/c.min.js"></script>
922+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/cpp.min.js"></script>
923+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/java.min.js"></script>
924+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
925+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/rust.min.js"></script>
926+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/go.min.js"></script>
927+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.2/marked.min.js"></script>
928+
<script>
929+
window.addEventListener('DOMContentLoaded', function() {
930+
if (window.hljs && typeof window.hljs.highlightAll === 'function') {
931+
window.hljs.highlightAll();
932+
}
933+
934+
function renderMarkdownBlocks(root) {
935+
if (!window.marked) return;
936+
const blocks = (root || document).querySelectorAll('.markdown-block');
937+
blocks.forEach((el) => {
938+
try {
939+
el.innerHTML = window.marked.parse(el.textContent || '');
940+
} catch (_) {}
941+
});
942+
}
943+
944+
renderMarkdownBlocks(document);
945+
if (window.hljs) {
946+
const observer = new MutationObserver((mutations) => {
947+
for (const mutation of mutations) {
948+
for (const node of mutation.addedNodes) {
949+
if (!(node instanceof HTMLElement)) continue;
950+
renderMarkdownBlocks(node);
951+
node.querySelectorAll('pre code:not(.hljs)').forEach((el) => {
952+
try { window.hljs.highlightElement(el); } catch (_) {}
953+
});
954+
}
955+
}
956+
});
957+
try {
958+
observer.observe(document.body, { childList: true, subtree: true });
959+
} catch (_) {}
960+
}
961+
});
962+
</script>
963+
</body>

report/templates/index/index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
languageOpen: true,
3636
crashesFoundOpen: true,
3737
ofgMetricsOpen: true,
38+
chartsOpen: true,
3839
}" class="space-y-2">
3940

4041
<div class="border rounded-lg toc-section">
@@ -489,6 +490,27 @@
489490
</div>
490491
</div>
491492

493+
<div class="container mx-auto mt-8">
494+
<div class="border rounded-lg toc-section">
495+
<button @click="chartsOpen = !chartsOpen"
496+
class="w-full p-4 flex justify-between items-center">
497+
<span class="text-lg font-bold">
498+
Charts
499+
</span>
500+
<svg class="w-5 h-5 transform transition-transform duration-200"
501+
:class="{'rotate-180': chartsOpen}"
502+
fill="none" stroke="currentColor" viewBox="0 0 24 24">
503+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
504+
</svg>
505+
</button>
506+
<div x-show="chartsOpen" class="p-4 border-t">
507+
<div class="chart-row">
508+
<div id="coverage-chart" class="chart-box"></div>
509+
<div id="bugs-chart" class="chart-box"></div>
510+
</div>
511+
</div>
512+
</div>
513+
</div>
492514
</div>
493515

494516
<button class="toc-toggle" id="tocToggle">

report/templates/index/index.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,97 @@
11
document.addEventListener('DOMContentLoaded', function() {
2+
function waitForPlot() {
3+
if (typeof Plot !== 'undefined') {
4+
setTimeout(initializeCharts, 100);
5+
} else {
6+
setTimeout(waitForPlot, 100);
7+
}
8+
}
9+
10+
function getBarY() {
11+
if (Plot.barY) return Plot.barY;
12+
if (Plot.BarY) return Plot.BarY;
13+
if (Plot.rectY) {
14+
return (data, opts) => {
15+
const { x, y, fill, title } = opts || {};
16+
return Plot.rectY(data, { x, y2: y, y1: 0, fill, title });
17+
};
18+
}
19+
return null;
20+
}
21+
22+
function initializeCharts() {
23+
const BarY = getBarY();
24+
if (!BarY) return;
25+
26+
const projectData = Array.from(document.querySelectorAll('#project-summary-table tbody tr.project-data-row')).map(row => {
27+
const cells = row.querySelectorAll('td');
28+
if (cells.length >= 9) {
29+
return {
30+
project: cells[1].dataset.sortValue,
31+
new_lines: parseInt(cells[7].dataset.sortValue) || 0,
32+
existing_lines: parseInt(cells[8].dataset.sortValue) || 0
33+
};
34+
}
35+
return null;
36+
}).filter(Boolean);
37+
38+
if (projectData.length > 0) {
39+
try {
40+
const coveragePlot = Plot.plot({
41+
title: "New vs. Existing Code Coverage by Project",
42+
x: { label: "Project", domain: projectData.map(d => d.project) },
43+
y: { label: "Lines of Code" },
44+
marks: [
45+
BarY(projectData, { x: "project", y: "existing_lines", fill: "#94a3b8", title: "Existing Coverage" }),
46+
BarY(projectData, { x: "project", y: "new_lines", fill: "#3b82f6", title: "New Coverage" })
47+
],
48+
width: 800,
49+
height: 400
50+
});
51+
const el = document.getElementById('coverage-chart');
52+
if (el) { el.innerHTML = ''; el.appendChild(coveragePlot); }
53+
} catch (error) {
54+
const el = document.getElementById('coverage-chart');
55+
if (el) el.innerHTML = '<p class="text-red-500">' + error.message + '</p>';
56+
}
57+
}
58+
59+
const bugsData = {};
60+
projectData.forEach(project => { bugsData[project.project] = 0; });
61+
const bugRows = document.querySelectorAll('#project-summary-table tbody tr.project-data-row');
62+
bugRows.forEach(row => {
63+
const cells = row.querySelectorAll('td');
64+
if (cells.length >= 6) {
65+
const project = cells[1].dataset.sortValue;
66+
const bugs = parseInt(cells[5].dataset.sortValue) || 0;
67+
if (project in bugsData) bugsData[project] += bugs;
68+
}
69+
});
70+
71+
const bugsChartData = Object.entries(bugsData).map(([project, bugs]) => ({ project, bugs }));
72+
if (bugsChartData.length > 0) {
73+
try {
74+
const bugsPlot = Plot.plot({
75+
title: "Bugs Found by Project",
76+
x: { label: "Project", domain: bugsChartData.map(d => d.project) },
77+
y: { label: "Number of Bugs" },
78+
marks: [
79+
BarY(bugsChartData, { x: "project", y: "bugs", fill: "#ef4444", title: "Bugs Found" })
80+
],
81+
width: 800,
82+
height: 400
83+
});
84+
const el = document.getElementById('bugs-chart');
85+
if (el) { el.innerHTML = ''; el.appendChild(bugsPlot); }
86+
} catch (error) {
87+
const el = document.getElementById('bugs-chart');
88+
if (el) el.innerHTML = '<p class="text-red-500">' + error.message + '</p>';
89+
}
90+
}
91+
}
92+
93+
waitForPlot();
94+
295
// Project summary table expand/collapse buttons
396
const projectSummaryExpandAllButton = document.getElementById('project-summary-expand-all');
497
if (projectSummaryExpandAllButton) {

0 commit comments

Comments
 (0)