Skip to content

feat(apm): enrichment engine - shared primitives + enricher query/format#2776

Open
jonmcwest wants to merge 1 commit into
mainfrom
06-19-feat_apm_enrichment_engine_shared_primitives_enricher_query_format
Open

feat(apm): enrichment engine - shared primitives + enricher query/format#2776
jonmcwest wants to merge 1 commit into
mainfrom
06-19-feat_apm_enrichment_engine_shared_primitives_enricher_query_format

Conversation

@jonmcwest

@jonmcwest jonmcwest commented Jun 19, 2026

Copy link
Copy Markdown

Problem

Editors and AI agents have no visibility into production latency data for the code they're reading. This adds the foundational layer for APM (tracing-span) enrichment, enabling per-line span statistics from PostHog to be fetched and rendered as inline comments alongside source code.

Changes

Shared types and utilities (@posthog/shared)

  • Added SpanLineStat, SymbolStatsRow, SymbolStatsPeriod, SourceSymbol, ApmWindow, and related interfaces as the cross-package boundary types for APM enrichment.
  • Added APM_STATS_WINDOW (defaulting to 24h) as the single source of truth for the query window, so the date range, label, and comparison label all derive from one value.
  • Added formatMs (compact millisecond formatting) and formatPercentDelta (period-over-period % change, suppressing sub-1% noise and non-finite values).
  • Added APM_LANG_BY_EXT, apmLangForFile, and fileExtension to determine comment style eligibility from a file path.
  • Added APM_ENRICHMENT_FLAG feature flag constant.

Query builder and result mapper (packages/enricher)

  • Added buildSymbolStatsQuery in apm-breakdown.ts, which constructs the TraceSpansSymbolStatsQuery request body. Omits symbols for per-line mode; includes them for per-symbol rollup. Defaults the window to APM_STATS_WINDOW.
  • Added mapSymbolStatsResults to convert raw server rows (nanosecond durations, snake_case fields) into SpanLineStat client shapes (milliseconds, camelCase), preserving server ordering and passing through server-computed deltas.
  • Added getApmLineStats to PostHogApi, which posts to /tracing/spans/symbol-stats/ and returns mapped SpanLineStat[].

Inline comment formatter (packages/enricher)

  • Added formatApmInlineComments in apm-comment-formatter.ts, which annotates source lines with APM metrics (p50/p95/p99, span count, error count, period-over-period deltas) as inline comments. Handles CRLF line endings, out-of-range line numbers, and per-language comment prefixes (# vs //).
  • Extracted commentPrefix into a shared comment-style.ts module so the #-vs-// rule is no longer duplicated between the event/flag and APM formatters.
  • Exported formatApmInlineComments from the enricher's public index.

How did you test this?

Unit tests were added and run for all new logic:

  • apm-breakdown.test.ts: covers buildSymbolStatsQuery (line mode, symbol mode, window defaults, empty symbols) and mapSymbolStatsResults (ns→ms conversion, delta passthrough, ordering, empty input).
  • apm-comment-formatter.test.ts: covers comment injection at the correct line, drill-in hint format, language-specific prefixes, error count visibility, out-of-range stats, count unit promotion (k/M), p99 and window label rendering, period-over-period delta formatting, and CRLF preservation.
  • apm-enrichment.test.ts: covers formatMs rounding behaviour and formatPercentDelta edge cases (null, non-finite, sub-1% suppression, symmetric rounding).

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Copy link
Copy Markdown
Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit f0a6a13.

@jonmcwest jonmcwest changed the title feat(apm): enrichment engine — shared primitives + enricher query/format feat(apm): enrichment engine - shared primitives + enricher query/format Jun 19, 2026
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/shared/src/apm-enrichment.test.ts:23-46
**`formatPercentDelta` tests not parameterised**

`formatMs` in the same file already uses `it.each` correctly, but `formatPercentDelta` spreads related cases across several separate `it` blocks, each asserting 2–3 values inline. Following the project's preference for parameterised tests, all of these could be folded into a single `it.each` table — one row per `(input, expected)` pair — which makes boundary conditions (null, ±0.4, ±0.5, non-finite) easier to scan and extend.

### Issue 2 of 2
packages/enricher/src/apm-comment-formatter.ts:43-45
**Drill-in hint uses basename; may match multiple files with the same name**

`getFileName` strips the full path down to just the filename (e.g., `flag_matching.rs`). The `~` operator in the generated query does a substring/suffix match, so the drill-in can return spans from **every** file named `flag_matching.rs` across the whole project, even though the stats displayed on the line were fetched using the fully-qualified path. In a monorepo with duplicate filenames this produces a broader, potentially misleading span query. Using the full repo-relative `filePath` (passed through `formatApmComment`'s signature) would make the drill-in consistent with the stat lookup.

```suggestion
  parts.push(
    `dig in: query-apm-spans code.filepath~"${filePath}" code.lineno=${s.line}`,
  );
```

Reviews (1): Last reviewed commit: "feat(apm): enrichment engine — shared pr..." | Re-trigger Greptile

Comment thread packages/shared/src/apm-enrichment.test.ts Outdated
Comment thread packages/enricher/src/apm-comment-formatter.ts
Comment thread packages/shared/src/apm-enrichment.ts Outdated
Comment thread packages/shared/src/apm-enrichment.ts Outdated
Comment thread packages/shared/src/apm-enrichment.ts Outdated
Comment thread packages/shared/src/apm-enrichment.ts
@jonmcwest jonmcwest force-pushed the 06-19-feat_apm_enrichment_engine_shared_primitives_enricher_query_format branch from de9f457 to f0a6a13 Compare June 19, 2026 11:57
@jonmcwest jonmcwest marked this pull request as ready for review June 19, 2026 14:44
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/enricher/src/apm-breakdown.ts:56
`p99Ms` is declared optional in `SpanLineStat` (`p99Ms?: number`) so that `undefined` can signal "no p99 data", which `formatApmComment` relies on with the `if (s.p99Ms != null)` guard. But this mapper always converts `r.p99_duration_nano` — including the zero value the server uses when it has no p99 data — to `0`, and `0 != null` is `true`. The result is that any line whose server row has `p99_duration_nano = 0` (e.g. when the server serialises `null` as `0` through the unsafe `as SymbolStatsRow[]` cast, or returns 0 outright) will display "p99 0ms" in the comment, defeating the optional field's design intent.

```suggestion
    p99Ms: r.p99_duration_nano > 0 ? nsToMs(r.p99_duration_nano) : undefined,
```

Reviews (2): Last reviewed commit: "feat(apm): enrichment engine — shared pr..." | Re-trigger Greptile

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.

1 participant