A small, dependency-free Python tool that turns an Oracle AWR Compare Period report (a.k.a. an AWR Diff report — two snapshot ranges compared side by side) into a concise executive summary in Markdown.
It is built for the single-HTML reports that awrrpt / awrdiff produce when
you compare one snapshot range against another. Those reports are dense
(several thousand HTML tables/rows) and hard to scan. This tool pulls out the
decision-relevant parts and writes them as a clean, paste-ready Markdown
document.
awr_diff_summary.py # the tool (stdlib only, Python 3.8+)
README.md # this file
awrdiff_*.html # input report(s) — AWR Compare Period output
summary.md # example generated output (run the tool to create)
- Python 3.8 or newer. No third-party packages — the standard library
(
html,json,re,argparse,os,sys) is all that is used. - A single AWR Compare Period HTML file (the output of an
awrdiff/awrrptcompare, not a regular single-range AWR report).
That's it. No network access, no database connection, no installation steps.
awr_diff_summary.py above distils Compare-Period reports. For regular
single-range AWR reports (the HTML awrrpt produces, including RAC / global
reports), awr_to_markdown.py performs a faithful format conversion to
Markdown — every section, table, note, ASH block, ADDM task and SQL-text
appendix — so the result can be analysed directly (by a human or an LLM
skill). It reuses the lenient DOM parser from awr_diff_summary.py; it is
also pure stdlib.
# convert (stdout)
python3 awr_to_markdown.py "AWR report.html"
# convert to a file and also dump the structured model as JSON
python3 awr_to_markdown.py "AWR report.html" -o report.md --dump-json report.json
# batch: convert every AWR report in a folder (diff reports are skipped —
# they belong to awr_diff_all.sh); --json also writes the models
./awr_all.sh reports/Sources --json| Option | Meaning |
|---|---|
-o FILE |
write Markdown to FILE (default: stdout) |
--sql-text-chars N |
truncate SQL text inside metric tables to N chars (default 100, 0 = full) |
--max-cell N |
hard cap for any table cell (default 200) |
--dump-json PATH |
dump the structured model (sections → tables/rows) as JSON |
Notes on the conversion:
- Two-row table headers (group row with colspans + leaf row) are flattened:
Wait Time∕Total(s)→Wait Time Total(s); repeated leaf names get numbered suffixes so JSON keys stay unique. - RAC
I#rows (*= cluster-wide totals,1/2per instance, plusSum/Avg/Min/Max/Std) are preserved verbatim. - "Complete List of SQL Text" is emitted as one fenced ```sql block per SQL Id (newlines intact); SQL text in other tables is one-line truncated.
- ADDM tasks pass through verbatim as code blocks — they are in the report's NLS language (Chinese reports produce Chinese ADDM text).
<pre>blocks, descriptive bullet notes and "No data exists for this section" markers are all kept.
Where awr_to_markdown.py converts regular reports, awr_rac_balance.py
consumes their Markdown output when it was produced per RAC instance
and answers two questions: how unbalanced is each cluster? and why?
Pure stdlib, Python 3.8+.
./awr_rac_balance.py reports/HA # folder of *.md reports
./awr_rac_balance.py reports/HA --threshold 1.1 --json model.json
./awr_rac_balance.py reports/HA --db HFPCDB --db HPCBDB- Balance table — per-instance AAS, the two-node DB-Time split, the high/low ratio and key rates (CPU/s, executes/s, redo, transactions, GC blocks received, logons/s, sessions), clusters sorted by ratio.
- Cause detail for every cluster at/above
--threshold: per-service DB Time on each node, and the SQL with the largest absolute elapsed time difference between nodes — which pinpoints jobs or clients that run on one node only (e.g. aDBMS_SCHEDULERchain that never leaves instance 1, or a reporting pool pinned to instance 2).
Reports are deduped by (DB, instance), so duplicate copies of the same
report (*dr.md / *st.md) count once, and ASH reports (no AWR header)
are skipped with a note.
| Option | Meaning |
|---|---|
folder |
directory of converted reports (default reports/HA) |
--json PATH |
also dump the parsed per-instance model as JSON |
--db NAME |
restrict the cause-detail section to this DB (repeatable) |
--threshold R |
ratio at/above which a cluster gets cause detail (default 1.25) |
--top N |
max SQL rows per cluster in cause detail (default 10) |
# print the executive summary to stdout
python3 awr_diff_summary.py awrdiff_ORCLDB01_00001_1_00010.html
# write it to a file
python3 awr_diff_summary.py awrdiff_ORCLDB01_00001_1_00010.html -o summary.md
# more rows per section (default is 8)
python3 awr_diff_summary.py report.html --top 12
# also dump the fully-parsed model as JSON (useful for scripting / dashboards)
python3 awr_diff_summary.py report.html --dump-json model.jsonRun from this directory. The input path can be absolute or relative.
| Option | Default | Description |
|---|---|---|
input |
(required) | Path to the AWR diff HTML report. |
-o, --output |
stdout | Write the Markdown to this file instead of stdout. |
--top N |
8 |
Number of rows to show per top-N section (wait events, SQL, segments, time model, …). |
--dump-json PATH |
off | Additionally serialise the parsed model as JSON to PATH. |
Exit code is 0 on success, non-zero if the input file is missing.
The output is a single Markdown document with numbered sections. Any section that is not present in the source report is omitted automatically (e.g. a non-RAC single-instance report has no RAC section).
- Environment & version — DB name, instance, host, Oracle release, edition, DB role, cluster/CDB, plus the snapshot ranges, window times, elapsed time, total DB time and avg active users for each period. If the two periods are on different Oracle releases, a callout is added warning that the comparison spans a release change.
- Host / hardware — CPUs, cores, sockets, physical memory, OS load and CPU-time breakdown (user/system/idle/IO wait) per period.
- Headline workload (per second) — DB time, CPU, redo, logical/physical reads and writes, user calls, parses, hard parses, transactions — per second and per transaction.
- Memory / cache sizing (MB) — SGA target, buffer cache, shared/large/Java pools, PGA, log buffer.
- Time model — the biggest movers in % of DB time between periods.
- Wait classes — per-class % of DB time and the change.
- Top wait-event movers — the events whose % of DB time changed most, with class and 2nd-period average wait. (Note: idle events can exceed 100% of DB time by design — that is a property of the AWR figures, not a bug.)
- Top timed events (as reported) — the top events exactly as the report lists them, for each period.
- Services — per-service % of DB time and % CPU, with deltas.
- Top SQL — by elapsed time, CPU time and I/O time: SQL id, % share per period, delta, executions, and a truncated SQL text.
- Top segment movers — by physical reads and logical reads:
owner.object (subobject), type, % share and absolute read counts. - Configuration changes (init.ora) — how many explicitly-set parameters
changed between the two end snapshots (visible vs hidden
_parameters), with the visible ones listed (name → old → new). - RAC / cluster — number of instances, buffer-access efficiency and global-cache load (only present on RAC reports).
- Key takeaways — a short, auto-generated bullet list of the most material findings (release change, hardware change, DB-time trend, concurrency, new/dropped top waits and top SQL).
All numbers are taken verbatim from the report; no re-basing or
normalisation is applied. %Diff/Δ means the change from the 1st to the 2nd
period.
# AWR Diff Executive Summary — ORCLDB01
> Compare-period report: 1st snapshot range vs 2nd snapshot range.
## 1. Environment & version
| | 1st period | 2nd period |
| --- | --- | --- |
| DB name | ORCLDB01 | ORCLDB01 |
| Host | dbhost01.example.com | dbhost02.example.com |
| Oracle release | 11.2.0.4.0 | 19.26.0.0.0 |
| Cluster / CDB | YES / NO | YES / NO |
**Version change detected:** 11.2.0.4.0 → 19.26.0.0.0 …
## 2. Host / hardware
| Metric | 1st | 2nd | %Diff |
| --- | --- | --- | --- |
| Number of CPUs | 48 | 128 | +166.7% |
| Physical Memory | 385192.2M | 1038889.7M | +169.7% |
| %Idle Time | 70.64 | 97.25 | +37.7% |
## 14. Key takeaways
- **Release change:** 11.2.0.4.0 → 19.26.0.0.0. Interpret all deltas with this in mind …
- **Hardware change:** CPUs 48 → 128, memory 376 GB → 1015 GB
- **Total DB time** fell 81% (18,000 → 3,340 min over the window).
- **Concurrency:** avg active users 13 → 2.4.
- **Top SQL dropped out in 2nd period:** `22zzbn4msv1g0` (was 32.4% DB time).(See the full summary.md produced by the quick-start command.)
The tool is deliberately self-contained:
- Parse — a minimal DOM built with the standard-library
HTMLParser(nolxml/BeautifulSoup). It tolerates the malformed/unclosed HTML that AWR emits by using a lenient tag stack. - Model tables — every
<table>is captured with its two header rows (group + leaf) and its data rows, giving aTablethat can look a cell up by (group, leaf) or by leaf index. - Locate sections — each report section is found by its heading title (e.g. Top SQL Comparison by Elapsed Time, Time Model Statistics). A few "Report Summary" tables that share the same heading are instead located by a stable data-row label (e.g. Number of CPUs).
- Extract — one small function per section returns a plain dict/list of the values that matter.
- Render — the pieces are turned into Markdown tables and a takeaways list. Missing sections are simply skipped.
Because step 3–4 key off headings and column groups rather than hard-coded positions, the tool copes with small layout differences between AWR versions. If Oracle changes the report structure enough to break a section, only that section's extractor needs updating (the rest of the document still renders).
- Compare-period reports only. A plain single-range AWR report has no "1st vs 2nd" columns and is not the intended input.
- Verbatim figures. The tool reports what the HTML says. It does not validate or recompute totals, and it does not fetch anything from the DB.
- Top-N. Each "movers" section is ranked by the magnitude of the change and
truncated to
--toprows (the SQL CPU/I/O tables are capped a little lower to keep the document readable). - Large files. A full AWR diff HTML is ~3.5 MB; the tool parses it in seconds. The generated summary is a few KB of Markdown.
- Encoding. Files are read as UTF-8 with replacement on bad bytes, so an odd non-UTF-8 report won't crash the run.
- Add a section: write an
e_your_section(rpt)extractor (returndict/list/None), register it inbuild_extras(...), and add a rendering block inrender(...). ReturnNoneto make it auto-omit. - Add a takeaway: append a bullet in
takeaways(rpt, ex). - Emit a different format: the parsed model (see
--dump-json) is a plain JSON-serialisable structure, so you can target HTML/CSV/PDF from it without touching the parsers.
Provided as-is for internal use. No warranty.