ASH Blocking Sessions — Architecture

How one shell script and one read-only PL/SQL block turn DBA_HIST_ACTIVE_SESS_HISTORY blocking samples into a single self-contained interactive HTML report. No application server, no database objects, no server-side files.

The report pipeline

Everything runs on the machine where the repo lives. sqlplus can connect to a local instance (/ as sysdba) or a remote one (EZConnect/TNS) — the database only ever answers queries; the HTML is assembled client-side, so the report always lands in the local reports/.

Report pipeline run_report.sh runs sqlplus, which prints JSON between markers; awk splits and splices it into the template, producing the report HTML. This machine (shell side) Database session (read-only) run_report.sh POSIX sh — AIX ksh88-safe sqlplus -S -L $CONN @build_report.sql stdout captured to reports/.build.$$/out.log build_report.sql defines begin / end / con_id args sql/00_settings.sql non-interactive settings, SET TAB OFF sql/20_emit_html.sql count guard (>100k refuses) · ASH self-join JSON_ARRAYAGG data + meta (sqlText map) prints CLOBs via DBMS_OUTPUT — SELECT only stdout protocol __META_JSON_BEGIN__ … END__ __DATA_JSON_BEGIN__ … END__ chunk lines: #<≤500 chars># joined with NO newlines awk pass 1 — split strip # sentinels → meta.json / data.json everything else → progress output awk pass 2 — splice replace placeholder lines in assets/template.html with the JSON reports/ash_blocking_*.html self-contained — open in any browser connect + run prints
shell (this machine) database session stdout contract deliverable
Read-only guarantee. The database session only runs SELECTs (DBA_HIST_*, CDB_USERS, CDB_OBJECTS, V$DATABASE) plus session-scoped NLS ALTER SESSIONs. No DDL, no DML, no directory objects, no server-side file access. A named account needs only CREATE SESSION and SELECT_CATALOG_ROLE on a common user in CDB$ROOT.

The stdout contract

The interface between sql/20_emit_html.sql and run_report.sh is plain text on stdout. Three rules make it robust across SQL*Plus quirks and AIX line-length limits — if you change one side, change the other:

Window : 2026-07-02T11:00:00 .. 2026-07-02T12:00:00      <- progress (echoed to user)
Blocking samples found: 206
__META_JSON_BEGIN__
#{"dbName":"CDB1","dbId":1183716798,"beginTime":"2026-07#   <- ≤500-char chunks,
#-02T11:00:00", ...                                     #      # sentinels
__META_JSON_END__
__DATA_JSON_BEGIN__
#[{"t":"2026-07-02T11:00:05","sid":34, ...              #
__DATA_JSON_END__

On the template side, the two placeholders sit alone on their own lines — that line-based layout is the splice contract (verified before splicing):

<script>
  window.ASH_META =
__META_JSON__          <- each line replaced by the concatenated chunks
  ;
  window.ASH_DATA =
__DATA_JSON__
  ;
</script>

Demo / seed flow (test databases only)

Everything under demo/ is deliberately invasive — it manufactures blocking so the report has something to show — and is gated behind a confirmation prompt (type seed; ASH_SEED_FORCE=1 skips it for scripted runs).

Seed flow run_seed.sh confirms, seeds blocking patterns, waits, then forces an AWR snapshot. demo/run_seed.sh confirmation gate, then sqlplus × 2 seed_blocking.sql recreate ASH_TEST in PDB1, submit DBMS_SCHEDULER jobs patterns A–D: TX fan-in/chain, TM, UL sleep N secs ASH samples the locks take_snap.sql CREATE_SNAPSHOT flushes ASH → DBA_HIST_ASH …then it prints a ready-to-paste run_report.sh invocation covering the seed window.

The report itself

assets/template.html is a single file: one IIFE, ECharts from CDN, data injected at build time (no fetches at runtime). It renders key-findings KPIs and auto-generated verdict sentences, a per-session Gantt timeline grouped by wait chain (filled cell = waiting, outlined = holding a lock), and a click-to-drill blocking tree at any 10-second ASH sample. reports/ash_blocking_demo.html is the same code with a rich synthetic dataset inlined, for offline UI work.

Repository layout

PathRole
run_report.shEntry point. Runs sqlplus, splits/splices the JSON, writes reports/<file>.html. Production-safe.
build_report.sqlSQL*Plus driver: settings + args + emitter.
sql/00_settings.sqlNon-interactive session settings. SET TAB OFF lives here.
sql/20_emit_html.sqlThe emitter: ASH query → JSON → chunked stdout. Read-only.
assets/template.htmlReport UI (source of truth). Placeholder lines are the splice contract.
reports/Generated reports (git-ignored) + the curated offline demo.
demo/Seed tooling — test databases only, confirmation-gated.
docs/architecture.htmlThis page.
Invariants worth protecting (details in CLAUDE.md): the stdout protocol is a two-sided contract; SET TAB OFF and the # sentinels are load-bearing; placeholders stay alone on their own lines; shell scripts stay POSIX (AIX ksh88 — no bashisms); the 100k-sample guard protects against multi-day windows; and the TM seed pattern keeps its own table so it doesn't collapse the TX chains.