Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ RUN echo "deb https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /et
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
apt-get update -y && \
apt-get install google-cloud-cli -y
# Set timezone to Australia/Sydney.
ENV TZ='Australia/Sydney'
# Set timezone to Anywhere on Earth (UTC-12).
ENV TZ='Etc/GMT+12'


# Install Docker for OSS-Fuzz.
Expand Down
2 changes: 1 addition & 1 deletion ci/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ FROM debian:12
ENV DEBIAN_FRONTEND interactive

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

# Install packages used by the Experiment. Python and Git are required for the experiment.
# Curl, certs, and gnupg are required to install gcloud.
Expand Down
28 changes: 22 additions & 6 deletions report/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@
class BaseExporter:
"""Base class for exporters."""

def __init__(self, results: Results, output_dir: str, base_url: str = ''):
def __init__(self,
results: Results,
output_dir: str,
base_url: str = '',
gcs_dir: str = ''):
self._results = results
self._output_dir = output_dir
self._base_url = base_url.rstrip('/')
self._gcs_dir = gcs_dir
self._headers = [
"Project", "Function Signature", "Sample", "Crash Type", "Compiles",
"Crashes", "Coverage", "Line Coverage Diff", "Reproducer Path"
Expand All @@ -51,6 +56,20 @@ def get_url_path(self) -> str:
class CSVExporter(BaseExporter):
"""Export a report to CSV."""

def _get_reproducer_url(self, benchmark_id: str,
crash_reproduction_path: str) -> str:
"""Get the reproducer URL, using GCS bucket URL for cloud builds."""
if not crash_reproduction_path:
return ""

if self._gcs_dir:
return (f"https://console.cloud.google.com/storage/browser/"
f"oss-fuzz-gcb-experiment-run-logs/Result-reports/"
f"{self._gcs_dir}/results/{benchmark_id}/artifacts/"
f"{crash_reproduction_path}")
return self._get_full_url(
f'results/{benchmark_id}/artifacts/{crash_reproduction_path}')

def generate(self):
"""Generate a CSV file with the results."""
csv_path = os.path.join(self._output_dir, 'crashes.csv')
Expand All @@ -70,18 +89,15 @@ def generate(self):

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

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

for sample in samples:
run_logs = self._results.get_run_logs(benchmark_id, sample.id) or ""
parser = RunLogsParser(run_logs, benchmark_id, sample.id)
crash_reproduction_path = parser.get_crash_reproduction_path()

report_url = self._get_full_url(
f"sample/{benchmark_id}/{sample.id}.html")
reproducer_path = self._get_full_url(
f'results/{benchmark_id}/artifacts/{sample.id}.fuzz_target-F0-01/'
f'{crash_reproduction_path}') if crash_reproduction_path else ""
reproducer_path = self._get_reproducer_url(benchmark_id,
crash_reproduction_path)

writer.writerow({
"Project":
Expand Down
61 changes: 61 additions & 0 deletions report/templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Report Templates

## Overview

An experiment report consists of three main page types: Index, Benchmark, and Sample.

* Index: Aggregated results across all benchmarks and projects
* Benchmark: Results for all samples in a single benchmark
* Sample: Detailed logs and crash analysis for a single sample

All pages extend `base.html` and share common assets. Static files are injected into templates via `web.py`:

* `shared_css_content` - Injected into all pages via base.html
* `base_js_content` - Injected into all pages via base.html
* `{page}_css_content` - Page-specific CSS (index, benchmark, sample)
* `{page}_js_content` - Page-specific JS (index, benchmark, sample)

## File Structure

```
templates/
├── base.html - Base layout with header, navigation, search
├── base.js - Search, TOC, prettifyBenchmarkName, syntax highlighting
├── shared.css - Common table and chart styles
├── macros.html - Reusable Jinja2 UI components
├── index/ - Main experiment summary index page
│ ├── index.html
│ ├── index.js
│ └── index.css
├── benchmark/ - Per-benchmark detail page
│ ├── benchmark.html
│ ├── benchmark.js
│ └── benchmark.css
└── sample/ - Per-sample detail page
├── sample.html
├── sample.js
└── sample.css

```

# Updating Template Code

## Adding New Pages

1. Create `{page}/{page}.html`, `{page}.js`, `{page}.css` in templates/
2. Add `_write_{page}` method in `web.py` following existing pattern
3. Call `self._read_static_file('{page}/{page}.js')` and pass as `{page}_js_content`
4. Add to `generate()` method pipeline

## Extending Search Functionality

* To modify field weights: Adjust `BASE_SEARCH_FIELD_WEIGHTS` in `base.js`
* To add searchable fields: Extend `fields` array in `searchSamples()`
* To update the scoring algorithm: Modify score calculation in `searchSamples()` loop
* To add new fields to the data structure: Update `_build_unified_data()` in `web.py` to include new fields

## Table of Contents

* The table of contents is auto-generated from elements with `.toc-section`, `.toc-subsection`, `.toc-item` CSS classes.
* The builder in `base.js:buildTOC()` creates a hierarchical tree: Sections -> Subsections -> Items.
* To add elements to the table of contents, you can apply the appropriate CSS class to any button/header.
Loading