Skip to content

Commit ffa2f64

Browse files
authored
Add artifact paths to UI display for crashed samples, refactor report UI code for maintainability, and add documentation (#1180)
This PR displays the artifact paths to reproduce a specific crash (with inputs and binaries if available) on the crashed sample's report page. Both the GCS and console paths are available.
1 parent 2a165e9 commit ffa2f64

File tree

18 files changed

+2286
-2452
lines changed

18 files changed

+2286
-2452
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ RUN echo "deb https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /et
3737
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
3838
apt-get update -y && \
3939
apt-get install google-cloud-cli -y
40-
# Set timezone to Australia/Sydney.
41-
ENV TZ='Australia/Sydney'
40+
# Set timezone to Anywhere on Earth (UTC-12).
41+
ENV TZ='Etc/GMT+12'
4242

4343

4444
# Install Docker for OSS-Fuzz.

ci/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ FROM debian:12
1616
ENV DEBIAN_FRONTEND interactive
1717

1818
# Set the same timezone as the main Dockerfile
19-
ENV TZ='Australia/Sydney'
19+
ENV TZ='Etc/GMT+12'
2020

2121
# Install packages used by the Experiment. Python and Git are required for the experiment.
2222
# Curl, certs, and gnupg are required to install gcloud.

report/export.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@
2424
class BaseExporter:
2525
"""Base class for exporters."""
2626

27-
def __init__(self, results: Results, output_dir: str, base_url: str = ''):
27+
def __init__(self,
28+
results: Results,
29+
output_dir: str,
30+
base_url: str = '',
31+
gcs_dir: str = ''):
2832
self._results = results
2933
self._output_dir = output_dir
3034
self._base_url = base_url.rstrip('/')
35+
self._gcs_dir = gcs_dir
3136
self._headers = [
3237
"Project", "Function Signature", "Sample", "Crash Type", "Compiles",
3338
"Crashes", "Coverage", "Line Coverage Diff", "Reproducer Path"
@@ -51,6 +56,20 @@ def get_url_path(self) -> str:
5156
class CSVExporter(BaseExporter):
5257
"""Export a report to CSV."""
5358

59+
def _get_reproducer_url(self, benchmark_id: str,
60+
crash_reproduction_path: str) -> str:
61+
"""Get the reproducer URL, using GCS bucket URL for cloud builds."""
62+
if not crash_reproduction_path:
63+
return ""
64+
65+
if self._gcs_dir:
66+
return (f"https://console.cloud.google.com/storage/browser/"
67+
f"oss-fuzz-gcb-experiment-run-logs/Result-reports/"
68+
f"{self._gcs_dir}/results/{benchmark_id}/artifacts/"
69+
f"{crash_reproduction_path}")
70+
return self._get_full_url(
71+
f'results/{benchmark_id}/artifacts/{crash_reproduction_path}')
72+
5473
def generate(self):
5574
"""Generate a CSV file with the results."""
5675
csv_path = os.path.join(self._output_dir, 'crashes.csv')
@@ -70,18 +89,15 @@ def generate(self):
7089

7190
project_name = benchmark_id.split("-")[1]
7291

73-
project_name = benchmark_id.split("-")[1]
74-
7592
for sample in samples:
7693
run_logs = self._results.get_run_logs(benchmark_id, sample.id) or ""
7794
parser = RunLogsParser(run_logs, benchmark_id, sample.id)
7895
crash_reproduction_path = parser.get_crash_reproduction_path()
7996

8097
report_url = self._get_full_url(
8198
f"sample/{benchmark_id}/{sample.id}.html")
82-
reproducer_path = self._get_full_url(
83-
f'results/{benchmark_id}/artifacts/{sample.id}.fuzz_target-F0-01/'
84-
f'{crash_reproduction_path}') if crash_reproduction_path else ""
99+
reproducer_path = self._get_reproducer_url(benchmark_id,
100+
crash_reproduction_path)
85101

86102
writer.writerow({
87103
"Project":

report/templates/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Report Templates
2+
3+
## Overview
4+
5+
An experiment report consists of three main page types: Index, Benchmark, and Sample.
6+
7+
* Index: Aggregated results across all benchmarks and projects
8+
* Benchmark: Results for all samples in a single benchmark
9+
* Sample: Detailed logs and crash analysis for a single sample
10+
11+
All pages extend `base.html` and share common assets. Static files are injected into templates via `web.py`:
12+
13+
* `shared_css_content` - Injected into all pages via base.html
14+
* `base_js_content` - Injected into all pages via base.html
15+
* `{page}_css_content` - Page-specific CSS (index, benchmark, sample)
16+
* `{page}_js_content` - Page-specific JS (index, benchmark, sample)
17+
18+
## File Structure
19+
20+
```
21+
templates/
22+
├── base.html - Base layout with header, navigation, search
23+
├── base.js - Search, TOC, prettifyBenchmarkName, syntax highlighting
24+
├── shared.css - Common table and chart styles
25+
├── macros.html - Reusable Jinja2 UI components
26+
├── index/ - Main experiment summary index page
27+
│ ├── index.html
28+
│ ├── index.js
29+
│ └── index.css
30+
├── benchmark/ - Per-benchmark detail page
31+
│ ├── benchmark.html
32+
│ ├── benchmark.js
33+
│ └── benchmark.css
34+
└── sample/ - Per-sample detail page
35+
├── sample.html
36+
├── sample.js
37+
└── sample.css
38+
39+
```
40+
41+
# Updating Template Code
42+
43+
## Adding New Pages
44+
45+
1. Create `{page}/{page}.html`, `{page}.js`, `{page}.css` in templates/
46+
2. Add `_write_{page}` method in `web.py` following existing pattern
47+
3. Call `self._read_static_file('{page}/{page}.js')` and pass as `{page}_js_content`
48+
4. Add to `generate()` method pipeline
49+
50+
## Extending Search Functionality
51+
52+
* To modify field weights: Adjust `BASE_SEARCH_FIELD_WEIGHTS` in `base.js`
53+
* To add searchable fields: Extend `fields` array in `searchSamples()`
54+
* To update the scoring algorithm: Modify score calculation in `searchSamples()` loop
55+
* To add new fields to the data structure: Update `_build_unified_data()` in `web.py` to include new fields
56+
57+
## Table of Contents
58+
59+
* The table of contents is auto-generated from elements with `.toc-section`, `.toc-subsection`, `.toc-item` CSS classes.
60+
* The builder in `base.js:buildTOC()` creates a hierarchical tree: Sections -> Subsections -> Items.
61+
* To add elements to the table of contents, you can apply the appropriate CSS class to any button/header.

0 commit comments

Comments
 (0)