Skip to content

web: add self-contained static HTML timing report (web_save_report)#10087

Merged
maliberty merged 6 commits intoThe-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:web-static
Apr 14, 2026
Merged

web: add self-contained static HTML timing report (web_save_report)#10087
maliberty merged 6 commits intoThe-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:web-static

Conversation

@openroad-ci
Copy link
Copy Markdown
Collaborator

Summary

Add a web_save_report Tcl command that generates a standalone HTML timing report reusing the exact same JS/CSS as the live web viewer.

Type of Change

  • New feature

Impact

Architecture:
The static report substitutes the WebSocket server with a cache
layer. WebSocketManager gains a fromCache(cache) factory that
serves pre-computed responses from window.__STATIC_CACHE__
embedded in the HTML. Cached request types include tech, bounds,
timing_report, slack_histogram, chart_filters, and tile PNGs.
Uncached requests (select, tcl_eval, etc.) reject gracefully.

All 18 JS source files plus a vendored GoldenLayout bundle are
concatenated by embed_report_assets.py into a single <script>
block. Each file is wrapped in an IIFE to isolate const/let
declarations; exported symbols are forwarded to outer-scope vars.

Layout view:
Tiles are pre-rendered at a fixed Leaflet zoom level using the
existing generateTile() infrastructure. The tile layer returns
data URIs from cache instead of creating blob URLs, ensuring
compatibility with file:// origins. Leaflet zoom is locked to
the cached level; pan is allowed.

Timing path overlay:
Per-path overlay PNGs are pre-rendered via renderOverlayPng()
(colored rects + flight lines on a transparent background).
When a timing path is selected, the cache drives a Leaflet
ImageOverlay to display the highlight.

Display controls, timing tables, slack histograms, and clock tree all work identically to the live viewer since they run the same widget code against cached data.

Files:

  • websocket-manager.js: fromCache(), _cacheRequest(), isStaticMode
  • main.js: STATIC_CACHE detection, zoom lock, overlay setup
  • embed_report_assets.py: IIFE wrapping, export-block handling
  • web.cpp: saveReport() with tile/overlay/JSON cache generation
  • web.tcl/web.i/web.h: web_save_report command plumbing
  • tile_generator.cpp/h: renderOverlayPng() for path highlights
  • websocket-tile-layer.js: data URI support alongside blob URLs
  • golden-layout.min.js: vendored ESM bundle from esm.sh
  • BUILD/CMakeLists.txt: embed pipeline with all JS files

Verification

  • I have verified that the local build succeeds (./etc/Build.sh).
  • I have run the relevant tests and they pass.
  • My code follows the repository's formatting guidelines.
  • I have signed my commits (DCO).

Related Issues

[Link issues here]

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to generate a self-contained HTML timing report. This involves embedding JavaScript and CSS assets into a C++ file, serializing various timing data (paths, histograms, filters), and rendering map tiles and path overlays directly into the HTML. The WebSocketManager and related JavaScript components were updated to support a static cache mode for these reports. Review comments highlight the need to value-initialize TileVisibility objects in src/web/src/web.cpp and src/web/src/tile_generator.cpp to prevent non-deterministic behavior. Additionally, the output file for the report should be opened earlier in src/web/src/web.cpp to improve efficiency, and layer names embedded in JavaScript string literals in the same file require proper escaping to avoid invalid JavaScript. Finally, file operations in src/web/src/embed_report_assets.py should explicitly specify UTF-8 encoding for better portability.

Comment thread src/web/src/web.cpp
Comment thread src/web/src/tile_generator.cpp
Comment thread src/web/src/web.cpp Outdated
Comment thread src/web/src/web.cpp Outdated
Comment thread src/web/src/embed_report_assets.py Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

clang-tidy review says "All clean, LGTM! 👍"

@maliberty
Copy link
Copy Markdown
Member

@oharboe please give it a try.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

clang-tidy review says "All clean, LGTM! 👍"

@oharboe
Copy link
Copy Markdown
Collaborator

oharboe commented Apr 9, 2026

$ git log -1
commit 30e70429ab3b72f525b7427e41b410cfdcf24af7 (HEAD)
Author: Matt Liberty <mliberty@precisioninno.com>
Date:   Wed Apr 8 18:59:11 2026 +0000

    web: harden saveReport with early file check, escaping, and encoding
    
    - Open output file before expensive rendering to fail fast on bad paths
    - Escape tile cache keys with json_escape() for valid JavaScript output
    - Specify encoding='utf-8' in embed_report_assets.py for portability
    
    Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
$ bazelisk run test/orfs/gcd:gcd_synth open_synth
[deleted]
+ exec ./test/orfs/gcd/make_gcd_synth_1_synth open_synth
ODB_FILE=./test/orfs/gcd/results/asap7/gcd/base/1_synth.odb ./openroad -no_init -threads 20  external/bazel-orfs++orfs_repositories+docker_orfs/OpenROAD-flow-scripts/flow/scripts/open.tcl
[Warning] Failed to create bazel runfiles: ERROR: external/rules_cc+/cc/runfiles/runfiles.cc(103): cannot find runfiles (argv0="/home/oyvind/OpenROAD-flow-scripts/tools/OpenROAD/tmp/test/orfs/gcd/_main/openroad")
application-specific initialization failed: 
% web_save_report -setup_paths 1000 -hold_paths 1000 test.html
invalid command name "web_save_report"
% 

Add a `web_save_report` Tcl command that generates a standalone HTML
timing report reusing the exact same JS/CSS as the live web viewer.

Architecture:
  The static report substitutes the WebSocket server with a cache
  layer. WebSocketManager gains a `fromCache(cache)` factory that
  serves pre-computed responses from `window.__STATIC_CACHE__`
  embedded in the HTML. Cached request types include tech, bounds,
  timing_report, slack_histogram, chart_filters, and tile PNGs.
  Uncached requests (select, tcl_eval, etc.) reject gracefully.

  All 18 JS source files plus a vendored GoldenLayout bundle are
  concatenated by embed_report_assets.py into a single <script>
  block. Each file is wrapped in an IIFE to isolate const/let
  declarations; exported symbols are forwarded to outer-scope vars.

Layout view:
  Tiles are pre-rendered at a fixed Leaflet zoom level using the
  existing generateTile() infrastructure. The tile layer returns
  data URIs from cache instead of creating blob URLs, ensuring
  compatibility with file:// origins. Leaflet zoom is locked to
  the cached level; pan is allowed.

Timing path overlay:
  Per-path overlay PNGs are pre-rendered via renderOverlayPng()
  (colored rects + flight lines on a transparent background).
  When a timing path is selected, the cache drives a Leaflet
  ImageOverlay to display the highlight.

Display controls, timing tables, slack histograms, and clock tree
all work identically to the live viewer since they run the same
widget code against cached data.

Files:
  - websocket-manager.js: fromCache(), _cacheRequest(), isStaticMode
  - main.js: __STATIC_CACHE__ detection, zoom lock, overlay setup
  - embed_report_assets.py: IIFE wrapping, export-block handling
  - web.cpp: saveReport() with tile/overlay/JSON cache generation
  - web.tcl/web.i/web.h: web_save_report command plumbing
  - tile_generator.cpp/h: renderOverlayPng() for path highlights
  - websocket-tile-layer.js: data URI support alongside blob URLs
  - golden-layout.min.js: vendored ESM bundle from esm.sh
  - BUILD/CMakeLists.txt: embed pipeline with all JS files
Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
- Open output file before expensive rendering to fail fast on bad paths
- Escape tile cache keys with json_escape() for valid JavaScript output
- Specify encoding='utf-8' in embed_report_assets.py for portability

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
The npm_link_all_packages call fails because the pnpm root package
is src/web/test, not src/web. The node_modules target was never
referenced as a dependency, so remove it.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Add missing field(std::string, const char*) overload to JsonBuilder.
Without it, field(getName(), ioTypeToDirection()) resolved to the
bool overload (standard pointer-to-bool conversion beats user-defined
const char*-to-string), writing true instead of "input"/"output"/"inout".

Invert netlistsvg SVG colors in dark mode so strokes and text are
visible against the dark background.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

tempHtml/tempPng wrote to /tmp/web_test_<label>.{html,png} with no
per-process suffix. Concurrent invocations of the same gtest case
(CI parallel jobs / retries / sharding) raced on the same path:
one process's std::ofstream truncated another's in-flight write,
producing a half-written file that was missing varying substrings
depending on flush timing.

Append getpid() to the filename so each process owns a private path.
Verified by reproducing the race with 32 parallel runs of
SaveReportTest.ContainsInlinedJS (100% failure before, 0% after).

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@maliberty maliberty enabled auto-merge April 13, 2026 23:40
@maliberty
Copy link
Copy Markdown
Member

@oharboe please try again

@maliberty maliberty disabled auto-merge April 14, 2026 02:33
@maliberty maliberty merged commit 38fd259 into The-OpenROAD-Project:master Apr 14, 2026
15 of 16 checks passed
@maliberty maliberty deleted the web-static branch April 14, 2026 02:33
@oharboe
Copy link
Copy Markdown
Collaborator

oharboe commented Apr 14, 2026

Very nice! Looks like it is wired up. Of course I want all the features 😆, but I can save a .html page and read the timing reports.

The MVP before users will prefer this is histogram support w/buckets one can click on. Once I have that, I'll wire it up in bazel-orfs and we'll roll it out.

I expect that we'll need some work on file sizes and pruning. bazel-orfs can implement some default heuristics as to how much to save that the user can tune per project to make suitably sized .html files.

😌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants