Skip to content

feat: add --out-json flag for structured report output#17

Merged
sokovmd merged 2 commits into
pg-tools:mainfrom
nanookclaw:feat/json-report-out
May 19, 2026
Merged

feat: add --out-json flag for structured report output#17
sokovmd merged 2 commits into
pg-tools:mainfrom
nanookclaw:feat/json-report-out

Conversation

@nanookclaw
Copy link
Copy Markdown
Contributor

@nanookclaw nanookclaw commented May 17, 2026

Closes #5.

Adds an optional --out-json <path> flag to pgcompare run. When omitted, behavior is unchanged. When provided, the full ReportData is written as pretty-printed JSON next to the existing HTML report.

Changes

  • main.go: new --out-json cobra flag; if set, call pgcompare.GenerateJSON(data, flagOutJSON) after the existing Generate call and print the JSON path to stdout alongside the HTML path.
  • internal/pgcompare/report.go: add GenerateJSON(data ReportData, outPath string) error using encoding/json with MarshalIndent (2-space indent, 0644).
  • internal/pgcompare/report_test.go: add TestGenerateJSON that round-trips a small ReportData and asserts GeneratedAt, Iterations, Speedups, Before.Stats[0].QueryName, and Diffs[0].QueryName are present in the JSON.

Stats.{Min,Max,P50,P95,P99,Mean,StdDev} are time.Duration, which encoding/json serializes as int64 nanoseconds — matches the structured-automation use case described in the issue.

Example JSON output

The exact values depend on the benchmark run; duration fields are encoded as nanoseconds by Go's encoding/json for time.Duration values.

{
  "GeneratedAt": "2026-05-18T11:06:00Z",
  "Iterations": 10,
  "WarmupIterations": 1,
  "Concurrency": 1,
  "Repeats": 1,
  "Speedups": [
    1.5
  ],
  "Before": {
    "Phase": "before",
    "Stats": [
      {
        "QueryName": "q1",
        "P95": 2000000,
        "QPS": 0,
        "ErrorRate": 0,
        "Errors": null
      }
    ],
    "Plans": [
      {
        "NodeType": "Seq Scan",
        "RelationName": "",
        "IndexName": "",
        "ActualRows": 0,
        "ActualTotalTime": 0,
        "Children": null
      }
    ]
  },
  "After": {
    "Phase": "after",
    "Stats": [
      {
        "QueryName": "q1",
        "P95": 1000000,
        "QPS": 0,
        "ErrorRate": 0,
        "Errors": null
      }
    ],
    "Plans": [
      {
        "NodeType": "Index Scan",
        "IndexName": "idx_q1",
        "ActualRows": 0,
        "ActualTotalTime": 0,
        "Children": null
      }
    ]
  },
  "Diffs": [
    {
      "QueryName": "q1",
      "Before": { "NodeType": "Seq Scan" },
      "After": { "NodeType": "Index Scan", "IndexName": "idx_q1" },
      "Summary": null
    }
  ],
  "Description": null
}

Verification

go build ./...
go test ./internal/pgcompare/...   # 22 tests pass, incl. new TestGenerateJSON
gofmt -l .                          # empty
go vet ./...                        # clean

Default behavior unchanged: running pgcompare run --config ... --out report.html produces only report.html.

@sokovmd sokovmd self-requested a review May 18, 2026 08:08
Copy link
Copy Markdown
Member

@sokovmd sokovmd left a comment

Choose a reason for hiding this comment

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

Could you add an example of the JSON output to the PR description?

@sokovmd sokovmd requested a review from a-pelikh May 18, 2026 08:18
@nanookclaw
Copy link
Copy Markdown
Contributor Author

Added an ## Example JSON output section to the PR description. It includes the main ReportData shape plus the note that time.Duration fields are encoded as nanoseconds by Go's encoding/json.

@sokovmd sokovmd self-requested a review May 18, 2026 20:09
@sokovmd
Copy link
Copy Markdown
Member

sokovmd commented May 18, 2026

Add example to example

@sokovmd
Copy link
Copy Markdown
Member

sokovmd commented May 18, 2026

Update README.md and RU.md

Copy link
Copy Markdown
Member

@sokovmd sokovmd left a comment

Choose a reason for hiding this comment

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

Update README.md and RU.md
+
and example to https://github.com/pg-tools/pgcompare/tree/main/example

Adds:
- example/report.json paired with example/report.html, mirroring its
  three-query (top_rated_blocked_drivers / latest_ride_status /
  failed_payments_by_amount) before/after shape so reviewers can see the
  full ReportData layout produced by --out-json.
- README.md and RU.md sections under "Write the report to a custom
  path" + "Notes / Примечания" covering: the flag is independent of
  --out, opt-in (no JSON without it), the JSON document is the ReportData
  struct directly, time.Duration fields serialize as integer nanoseconds
  via Go's encoding/json, and when set the JSON path is printed as the
  last stdout line after the HTML path.
@nanookclaw
Copy link
Copy Markdown
Contributor Author

Pushed 305bd00:

  • example/report.json paired with example/report.html. Same three queries (top_rated_blocked_drivers / latest_ride_status / failed_payments_by_amount), so the full ReportData layout is visible side by side.
  • README.md and RU.md: a short section under "Write the report to a custom path" / equivalent showing --out-json, plus a Notes bullet. Covered: independent of --out, opt-in (no JSON written without it), document is the ReportData struct directly, time.Duration fields encode as integer nanoseconds via Go's encoding/json, and when set the JSON path is printed as the last stdout line after the HTML path.

Verified go build ./... and go test ./internal/pgcompare/... clean. JSON parses with python3 -c "import json; json.load(open('example/report.json'))" and has the expected Before/After/Diffs/Description keys.

@sokovmd sokovmd merged commit f0c63c6 into pg-tools:main May 19, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add JSON report output (--out-json)

2 participants