Skip to content

Add stats-to-mapping CLI for regenerating upstream apm-server field mappings#20999

Closed
carsonip wants to merge 5 commits intoelastic:mainfrom
carsonip:stats-to-mapping-go
Closed

Add stats-to-mapping CLI for regenerating upstream apm-server field mappings#20999
carsonip wants to merge 5 commits intoelastic:mainfrom
carsonip:stats-to-mapping-go

Conversation

@carsonip
Copy link
Copy Markdown
Member

Motivation/summary

The five files that hold apm-server's field mappings live in three external repositories:

When apm-server adds, removes, or renames a metric, those five files must be hand-edited in three separate repos. The previously closed #13638 added a Python script that automated the regen by reading a single /stats snapshot and rewriting each file in place. That PR was closed without merging, leaving us back at hand-editing.

This PR ports the Python script to Go and homes it in apm-server at cmd/stats-to-mapping, so a developer can regenerate the upstream mappings by piping apm-server:5066/stats into the binary with the five file paths as arguments. No Python venv, no ruamel.yaml dependency.

This is the consumer side of the contract that #20993 (eager monitoring counters) restored: with that PR merged, a single /stats capture from a freshly started apm-server enumerates every counter the server defines, so stats-to-mapping produces a complete mapping set without needing to drive every error path during capture.

What this changes

  • New cmd/stats-to-mapping Go program. Same five-file contract as the closed Python script. Output is intentionally not byte-equal to the Python original — the Go implementation byte-splices the unchanged regions of input files (preserving comments, quoting, and surrounding whitespace) and re-emits the modified subtrees with stdlib JSON / YAML, sorted alphabetically.
  • goType recognises non-integer JSON numbers and emits type: float. The only such value today is apm-server.sampling.tail.storage.disk_usage_threshold_pct (introduced in #20464); upstream already types it as float, so this matches existing convention.
  • Golden-file tests in cmd/stats-to-mapping/testdata/. The committed stats.json was captured from a TBS-enabled apm-server built from #20993's eager-monitoring-counters branch, so the goldens reflect the full enumerated metric set the tool will see in production once that PR ships.
  • README at cmd/stats-to-mapping/README.md covering build, invocation, supported files, and how the goldens were generated.

Checklist

  • Update CHANGELOG.asciidoc — n/a, dev tooling, not a user-facing apm-server change.
  • Documentation has been updated — README is in cmd/stats-to-mapping/.

How to test these changes

go test ./cmd/stats-to-mapping/...
go vet ./cmd/stats-to-mapping/...

End-to-end against fresh upstream files:

go build -o /tmp/stats-to-mapping ./cmd/stats-to-mapping

# Capture stats from a running apm-server (TBS-enabled if you want the tail.storage.* fields):
curl -s http://127.0.0.1:5066/stats > /tmp/stats.json

# Run against local checkouts of the three upstream repos:
/tmp/stats-to-mapping \
  ~/elastic/elasticsearch/x-pack/plugin/core/template-resources/src/main/resources/monitoring-beats.json \
  ~/elastic/elasticsearch/x-pack/plugin/core/template-resources/src/main/resources/monitoring-beats-mb.json \
  ~/elastic/beats/metricbeat/module/beat/_meta/fields.yml \
  ~/elastic/beats/metricbeat/module/beat/stats/_meta/fields.yml \
  ~/elastic/integrations/packages/elastic_agent/data_stream/apm_server_metrics/fields/beat-stats-fields.yml \
  < /tmp/stats.json

# Inspect with `git diff` in each upstream repo.

Related issues

Supersedes the closed #13638. Unblocks the periodic upstream-mapping-sync work in #15533 and #13475. Best paired with #20993 so the captured /stats snapshot is the complete enumeration.

carsonip and others added 4 commits April 27, 2026 21:20
Reads APM Server /stats JSON from stdin and updates the upstream
Elasticsearch monitoring templates, Metricbeat fields.yml files, and the
elastic_agent integration package in-place to expose those fields. Replaces
the Python script proposed in elastic#13638 with a Go equivalent that produces
byte-identical output, removing the ruamel.yaml setup step from the
developer workflow.

Tests are golden-file based and pin upstream snapshots of the five target
files; outputs were generated once with the upstream Python script (with a
seek(0) writeback fix) and committed alongside the inputs. No Python or
shell regen scripts live in the repo — see cmd/stats-to-mapping/README.md
for the regen procedure.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
JSON output now follows the same byte-splicing model already used for YAML:
read the file, find the byte range of the modified member's value with a
small JSON-aware byte navigator, replace it with a fresh JSON encoding of
the new subtree. The parent object's other children stay in their on-disk
order because we only swap values, never reorder the parent.

The replacement subtree is built from a plain map[string]any and emitted
via stdlib json.MarshalIndent, which sorts keys alphabetically — fine here
because the apm-server / output subtrees are already alphabetical at every
level in both the stats input and the existing target files. The one
behavioral change: alias entries now emit as
{"path": "...", "type": "alias"} rather than insertion-order
{"type": ..., "path": ...} — semantically identical, regenerated golden.

Net: -189 LOC for the deleted orderedMap module, plus modest trims in
convert.go (drops UseNumber / json.Number; sorted iteration via a
sortedKeys helper) and main_test.go (drops the diagnostic helpers in
favor of just printing the actual-output path on failure). Total moves
1182 -> 1038 LOC, no new deps.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
apm-server changed its /stats endpoint shape in
elastic#15094 (Jan 2025): output
metrics moved from a top-level "output" key to "libbeat.output", string
scalars like "libbeat.output.type" appeared. EA also split
"beat-fields.yml" into a separate "beat-stats-fields.yml"
(elastic/integrations#16560). The tool didn't run against current
apm-server stats.

Three input-shape fixes, mirrored in both Go and the (out-of-band)
Python reference used to regenerate goldens:

  - Read "output" from libbeat.output instead of top-level. If absent,
    skip silently and leave the upstream entries unchanged (warning to
    stderr).
  - Accept string scalars and emit them as type: keyword.
  - EA dispatch now matches "beat-stats-fields.yml" instead of
    "beat-fields.yml". Test fixture renamed accordingly.

Goldens regenerated from the patched Python reference; Go matches
byte-for-byte. Alias entries now emit {type, path} via a typed struct
so field order matches Python (Go's map[string]any sorts alphabetically
which would have flipped them to {path, type}).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
After elastic#20993 (eager monitoring counters) the /stats
endpoint exposes the full enumerated set of apm-server.* metrics from
the moment the server starts, including
apm-server.sampling.tail.storage.disk_usage_threshold_pct, a Float64
gauge that's reported as 0.8 by default. Previously the tool aborted on
non-integer numbers; map them to type "float" instead, matching the
existing upstream typing in monitoring-beats*.json and the Beats /
Integrations fields.yml files.

Refresh the test fixtures:

  - testdata/stats.json: captured from a TBS-enabled apm-server built
    from the eager-monitoring-counters branch. Includes the float
    disk_usage_threshold_pct and the full eager-emitted counter set.
  - testdata/inputs/ea-beat-stats-fields.yml: re-snapshot from
    elastic/integrations main; the other four upstream snapshots were
    already current.
  - testdata/golden/*: regenerated from the new inputs and stats.

Goldens now contain disk_usage_threshold_pct as type: float (typed
files) or as an alias entry (beat-root-fields.yml), and the full set of
otlp.{grpc,http}.{logs,metrics,traces} response error/valid families
that previously only appeared after a matching response.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)

License header indent normalized (tab -> 5 spaces) by the apm-server
header tool. The package doc comment that the same tool stripped from
main.go is preserved in cmd/stats-to-mapping/README.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented Apr 28, 2026

This pull request does not have a backport label. Could you fix it @carsonip? 🙏
To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8.19 is the label to automatically backport to the 8.19 branch.
  • backport-9./d is the label to automatically backport to the 9./d branch. /d is the digit.
  • backport-active-all is the label that automatically backports to all active branches.
  • backport-active-9 is the label that automatically backports to all active minor branches for the 9 major.

@carsonip
Copy link
Copy Markdown
Member Author

Closing this in favor of elastic/apm-tools#245 — moving the tool to apm-tools, since the testdata goldens are ~10k lines and the rest of the active devtools live there. The drift problem this is part of is now tracked in #21000.

@carsonip carsonip closed this Apr 28, 2026
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.

2 participants