Skip to content

FUG-128: CI observability — failure dashboarding (BEP → inline report + Grafana + columnar sinks) - #108

Open
issuefleet[bot] wants to merge 8 commits into
mainfrom
agent/fug-128-ci-observability-failure-dashboa
Open

issuefleet[bot] wants to merge 8 commits into
mainfrom
agent/fug-128-ci-observability-failure-dashboa

Conversation

@issuefleet

@issuefleet issuefleet Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

CI Observability: failure dashboarding (FUG-128)

Turns every Bazel-running CI job into queryable failure telemetry, a browsable
HTML report, and a Grafana dashboard — answering the three questions in the
issue: failures by trace/reason, by runner/DUT, and a heatmap by test
case
. Built on the existing FUG-117 Grafana Cloud infrastructure so the only
manual step is credentials (most of which already exist).

What's here

  • tools/ci_observability/bep_report.py — a stdlib-only parser for Bazel's
    Build Event Protocol (--build_event_json_file). It follows each TestResult
    to its test.xml (JUnit) for per-test-case pass/fail + trace, captures
    build-failure targets from failed actions, buckets failures
    (disk/memory/timeout/network/build/assertion/other), and computes a scrubbed
    failure signature so identical failures group across runs. The BEP
    TestResult status is authoritative (bazel's synthesized test.xml for a
    failed test carries no <failure> element — the failure is only in
    test.log, which enriches the trace). Validated against real bazel output for
    both pass and fail paths; 20 unit tests green under bazel.
  • Report of the run — inline + downloadable. Rendered as GitHub-flavored
    markdown into $GITHUB_STEP_SUMMARY so it opens directly on the run's
    Summary tab
    (no zip download), with a collapsible block per target showing
    per-case results and the target's full test.log / build stderr (passing
    targets too) plus the bazel console output. The same report is also uploaded
    as a self-contained styled HTML artifact for offline viewing.
  • Pluggable sinks (sinks.py) — Grafana Cloud Loki (default; reuses the
    FUG-117 credentials), a Tinybird sink (its Events API — the correct
    ingestion path for Tinybird Forward), and a ClickHouse sink (native
    INSERT for ClickHouse Cloud / self-hosted); each no-ops without credentials,
    so it's safe on fork PRs.
  • Reusable composite action .github/actions/bep-report wired (always())
    into every bazel test/build job: test / firmware / build-site
    (test.yaml), hitl_tests (hitl.yaml), test-macos (macos.yaml) — each tagged
    with its runner label (github:linux / github:macos / hitl).
  • Grafana dashboard as code — observability/ci/dashboards/ci-failures.json
    (Loki-backed, three required views + drill-down). grafana-dashboards.yaml
    now syncs it too, reusing the existing "Grafana" environment.
  • Columnar DB — schema/ci_test_results.{sql,datasource}: ClickHouse DDL +
    Tinybird datasource. Recommendation: Tinybird's free "Build" plan (managed
    ClickHouse — free, fully-hosted, well-supported; ClickHouse Cloud is
    trial-only). Loki is the zero-new-account default so the dashboard works the
    moment the (already-existing) Loki secrets are present.

The only manual step: credentials

See tools/ci_observability/README.md → "Configuration — pick a recipe" (two
fully-free options). Recipe A (simplest): copy the GRAFANA_CLOUD_LOKI_*
values (already in the FUG-117 "HITL" environment) to repository secrets and
add a Loki data source — the shipped dashboard lights up. Recipe B
(columnar):
tb deploy the ci_test_results data source and set
TINYBIRD_API_URL/TINYBIRD_TOKEN. The dashboard sync reuses the existing
"Grafana" environment. The HTML report + artifact link need no credentials at
all. Nothing else to stand up.

Testing

  • bazel test //tools/ci_observability:bep_report_test — 20 unit tests
    (BEP parsing, per-case granularity, categorization, signature scrubbing,
    build failures, flaky detection, BEP-status reconciliation, HTML render, sink
    no-op).
  • Parser validated against real bazel test/sh_test BEP output (pass + fail).
  • Workflows and dashboard JSON pass the repo's prek lint suite.

🤖 Generated with Claude Code

Closes-Linear: FUG-128 (https://linear.app/fughilli/issue/FUG-128/ci-observability-failure-dashboarding)

Claude Agent and others added 4 commits August 19, 2026 23:36
Parse bazel's Build Event Protocol (--build_event_json_file) into normalized
per-test-case records: follow each TestResult to its test.xml (JUnit) for
per-case pass/fail + trace, capture build-failure targets from failed actions,
bucket failures (disk/memory/timeout/network/build/assertion/other) and compute
a scrubbed failure signature for trace-level aggregation. Flaky runs (pass after
an earlier fail) surface as FLAKY.

Emits NDJSON records, a self-contained static HTML report (CSS/SVG charts +
collapsible per-target details), and a markdown step-summary. Pluggable sinks
(Grafana Cloud Loki, ClickHouse-compatible columnar DB) that no-op without
credentials. Stdlib-only; 18 unit tests green under bazel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reusable composite action .github/actions/bep-report: parses a bazel
--build_event_json_file into the HTML report, uploads it as an artifact, links
it from the job summary + an ::notice:: at the end of the log, and pushes
per-test-case records to the sinks. Wired (always()) into every bazel-running
job: test/firmware/build-site (test.yaml), hitl_tests (hitl.yaml), test-macos
(macos.yaml), each tagged with its runner label (github:linux|macos, hitl).

Sink creds are read from repository secrets via a workflow-level env block so
every job — with or without a GitHub environment — inherits them; fork PRs get
empty values and the report/sinks no-op.

schema/ci_test_results.{sql,datasource}: the columnar table DDL for the
ClickHouse-compatible sink (Tinybird free Build plan / ClickHouse Cloud /
self-hosted), with the three dashboard reference queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
observability/ci/dashboards/ci-failures.json: Loki-backed dashboard with the
three required views — failures by signature/trace (table), by category (bar),
by runner/DUT (bar + timeseries), and a per-test-case failure heatmap (color-
graded table) plus a recent-failures drill-down. runner/workflow template vars.

grafana-dashboards.yaml now also syncs observability/ci/dashboards/ (reuses the
existing 'Grafana' env creds). Full README covering the pipeline, the columnar
DB decision (Tinybird free Build plan vs Loki default), and the single
credential-setup step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…status

Validated against real bazel output: bazel's synthesized test.xml for a FAILED
test keeps status="run" with no <failure> element (the failure lives only in
test.log), so trusting the XML alone under-reports failures. The BEP
TestResult.status is now authoritative: a failed attempt whose XML shows no
failing case falls back to a synthetic failing case, and a lone generic
failure ('exited with error code 1') is enriched with the test.log tail so the
real cause is visible in the HTML trace. Real per-case JUnit XML (pytest
--junitxml) is left untouched. +2 regression tests (20 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://fughilli.github.io/splanc/pr-preview/pr-108/

Built to branch gh-pages at 2026-08-20 01:21 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Kevin picked Tinybird — but Tinybird Forward ingests via its Events API
(POST /v0/events?name=<ds>), not a raw ClickHouse INSERT endpoint, so the
existing clickhouse sink wouldn't reach it. Add a dedicated push_tinybird sink
(TINYBIRD_API_URL/TOKEN/DATASOURCE, no-ops without creds) and wire the secrets
into all three workflows. clickhouse sink stays for real ClickHouse/Cloud/
self-hosted.

Rewrite the README config section into two explicit fully-free recipes: (A)
Grafana Cloud Loki only — the shipped dashboard, least effort; (B) Tinybird
columnar via the Events API, mapped from the Tinybird quick-start (only the
ci_test_results datasource + an append token; skip the taxi sample, the pipe,
and Tinybird Local). +2 sink tests (22 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kevin confirmed the Grafana Cloud Loki write uses HTTP basic_auth (numeric
instance id + Grafana.com API token) — which push_loki already does. Spell that
out in the Recipe A table so it's unambiguous the same token/URL work unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR feedback (Kevin):

1. Zip link → inline report. GitHub only serves artifacts as .zip (no direct-open
   URL), so the full report is now rendered as GitHub-flavored markdown into
   $GITHUB_STEP_SUMMARY — it opens right on the run's Summary tab, no download.
   The styled HTML stays as an optional artifact download, relabeled as such.

2. Missing logs. Records now carry log_excerpt (the target's test.log tail) for
   EVERY target, pass or fail — so expanding a target always shows its output.
   Build failures with no attributed action fall back to the bazel console
   (captured from BEP progress events), so compiler output is shown instead of a
   bare 'build failed'. A 'Console output' section carries the full bazel log.
   log_excerpt is report-only (REPORT_ONLY_FIELDS) so it never bloats the sinks.

3. Grafana dashboard: confirmed — observability/ci/dashboards/ci-failures.json
   pulls from Loki and aggregates across all runners by default (runner=All),
   with explicit by-runner/DUT panels. (No change needed.)

+4 tests (28 total): passing-target log capture, console capture, build-failure
console fallback, own-stderr, inline markdown report. Validated against real
bazel BEP (passing py_test + failing sh_test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@issuefleet issuefleet Bot changed the title FUG-128: CI observability — failure dashboarding (BEP → HTML report + Grafana + columnar sink) FUG-128: CI observability — failure dashboarding (BEP → inline report + Grafana + columnar sinks) Aug 20, 2026
The hitl_tests lane is the first job with LIVE Loki creds, so push_loki actually
hits the network there. push_loki only caught HTTPError/URLError; an unforeseen
error type (bare socket timeout, SSLError, ConnectionReset) would have escaped
and failed the CI step. Two layers of defense:

- push_all now wraps every sink in a broad try/except so a push can never
  propagate (with a per-sink test that a raising sink is swallowed).
- the bep-report composite action runs the generator out from under set -e and
  swallows any failure (::warning:: + have_report=false), and the upload/link
  steps are continue-on-error. A report/telemetry hiccup can no longer fail a
  build.

This also de-risks the additive change on every wired job. (If the hitl failure
was instead a hardware flake, a re-run clears it; this makes the report step
provably incapable of causing it.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch had an error being deployed

1 failed deployment
HITL — e4fbf9d5 Deployed Aug 20, 2026 by issuefleet[bot] via hitl_tests #265
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.

0 participants