src/go/plugin/go.d/docs/migrate-v1-to-v2.md
Requirement language follows the root AGENTS.md definitions.
This guide is for migrating an existing go.d collector from framework V1 to framework V2. It is not the starting point
for a new collector; use src/go/plugin/go.d/docs/how-to-write-a-collector.md for new work.
V1 collectors are public integrations. A migration MUST preserve their existing user-visible contracts unless the user explicitly approves a breaking change.
A V1-to-V2 migration is compatibility work first. The clean end state is a V2 collector that behaves like the old
collector from the user's point of view, with the old public contracts preserved and the internal collection path moved
to collectorapi.CollectorV2 and metrix.CollectorStore.
Do not combine a compatibility migration with new enrichment, new topology, new host scopes, config expansion, chart redesign, or framework changes unless that work is required for the migration itself. If the migration reveals useful new work, split it into a later batch.
Create a compatibility manifest before implementation. The manifest can live in the active TODO or SOW. It MUST cover:
context_namespace and any group context namespaces;go.d.conf toggle;src/go/plugin/go.d/config/go.d/sd/ and
src/go/plugin/go.d/discovery/sdext/ references.collectorapi.Register entry;Defaults;Init, Check, Collect, and Cleanup behavior;Once, reconnect, cache, or retry behavior.config_schema.json;.conf;Vars) -- STOP if present; see Chart Variables before implementation;metadata.yaml;taxonomy.yaml;COLLECTORS.md / plugin README entries when affected;SERVICE-DISCOVERY.md, when affected;SECRETS.md, when affected.A V2 migration MUST replace the V1 collection path:
CreateV2;collectorapi.CollectorV2;New() creates metrix.NewCollectorStore();metrix.CollectorStore;Configuration() preserves existing config return behavior;VirtualNode() preserves existing vnode behavior when the V1 collector has one;MetricStore() returns the store;ChartTemplateYAML() returns embedded charts.yaml;Vars, the migration stops until the Chart Variables decision path
is resolved;Collect(ctx) returns error and writes observations to metrix;Collect() map[string]int64 output path and any runtime bridge from V1 maps
into V2 metrix.Charts() and runtime collectorapi.Charts mutation from the production collection
path.Use src/go/plugin/framework/collectorapi/collector.go as the source of truth for the interface.
Temporary V1 logic can be useful while developing the migration. For example, tests can compare V1 map output against V2
metrix observations after mapping both sides through the V1 chart manifest and any V2 chart-identity tooling that
exists for the migration.
Do not invent an unreviewed local snapshot format when the comparison needs compiled chart identity that the framework
does not expose. If the migration needs reusable chart-identity or alert-variable comparison helpers, treat that as a
framework/test-helper prerequisite and follow src/go/plugin/framework/docs/changing-framework-code.md before relying
on it.
That parity bridge is a development tool only. It MUST NOT remain in the final migrated collector runtime path. A finished migration that runs as V1-to-bridge-to-V2 is not a clean end state.
Runtime path means any code reachable from Init(ctx), Check(ctx), Collect(ctx), or Cleanup(ctx) during normal
execution. V1 collection logic MUST NOT remain reachable from that runtime path, regardless of function names or return
types.
If parity helpers are useful long term, keep only _test.go helpers or fixtures under testdata/. Before finishing,
audit imports and prove no non-test file imports the old V1 path or parity bridge.
From src/go, run an import audit for the migrated collector:
go list -deps -test=false ./plugin/go.d/collector/<collector>/... |
rg 'pkg/stm|plugin/go\.d/pkg/oldmetrix'
rg -n 'Collect\(.*map\[string\]int64|map\[string\]int64|collectorapi\.Charts|func .*Charts\(' \
plugin/go.d/collector/<collector> -g '*.go'
The dependency command MUST return no runtime dependencies on old V1-only helpers such as stm, oldmetrix, or any
collector-local parity bridge package. The source grep MUST NOT find a remaining runtime V1 map output, runtime
collectorapi.Charts mutation, or Charts() path. If the old path is hard to identify, delete the temporary bridge and
build/test the collector; the final runtime must still compile without it.
Unless the user approves a breaking change, the migration MUST preserve:
If an existing collector has an accidental bug or inconsistent artifact, record it separately. Fix it in the migration only when preserving the bug would make the V2 collector incorrect or untestable; otherwise split the fix into its own tracked batch in the active TODO/SOW with owner-approved disposition. Do not close a migration with vague deferred items.
Prefer the same file ownership as new V2 collectors:
collector.go # registration, New, public lifecycle, MetricStore, ChartTemplateYAML
init.go # Init helper methods when setup is non-trivial
config.go # Config, defaults, validation
collect.go # Collect orchestration
collect_<area>.go # separate upstream operations when there are several
metrix.go # typed instruments built once
write_metrics.go # observations into metrix
charts.yaml # V2 chart template
*_test.go # focused, table-driven tests
Keep public lifecycle methods in collector.go. Helper methods can move into focused files when that makes ownership
clearer.
Init(ctx) MUST perform setup and validation only. It MUST NOT collect the full metric set just to initialize state.Check(ctx) SHOULD be a cheap probe when the upstream API supports one. If V1 used full collection for autodetection,
preserve the user-visible result while making the V2 path as light as the source allows.Collect(ctx) MUST write observations to metrix and return an error only when the cycle should abort. Fail-soft
partial collection must be deliberate, tested, and logged without per-cycle spam.Cleanup(ctx) MUST preserve existing cleanup behavior and release any V2 Function or client resources added by the
migration.Use typed metrix instruments and charts.yaml.
StateSet for fixed one-active-state values.Counter.ObserveTotal() when the upstream value is a source counter.instances, and label_promotion in charts.yaml, not ad
hoc runtime chart code.When adding labels during migration, verify they are bounded and do not alter chart identity unexpectedly. Labels that improve filtering are acceptable only when they do not break existing chart/dimension contracts.
V1 collectorapi.Chart.Vars have no direct charts.yaml / charttpl support today. Some shipped health alerts depend
on those variables.
If the V1 collector uses Vars, the migration MUST NOT silently drop them. Choose one of these paths before
implementation:
src/go/plugin/framework/docs/changing-framework-code.md;Until one of those paths is approved, migrating a collector that uses chart variables is blocked. Known V1 go.d
collectors using chart variables at the time of writing include postgres, cockroachdb, hdfs, puppet, scaleio,
whoisquery, and zookeeper.
V1 collectors often obsolete dynamic charts immediately with MarkRemove() / MarkNotCreated(). V2 chart templates
expire unseen chart instances through lifecycle.expire_after_cycles and related chartengine policy. Exact immediate V1
timing is not always reproducible in V2.
For every V1 dynamic chart, record the old obsoletion timing and choose the V2 lifecycle policy deliberately. If the timing changes, document the behavioral change and get approval when it affects alerts or user-visible chart lifetime.
V1 collectors often build chart IDs with fmt.Sprintf. V2 templates derive contexts from context_namespace, group
context namespaces, and chart context leaves. V2 autogen also has engine.autogen.max_type_id_len behavior.
The migration MUST prove that generated chart IDs, contexts, and dimensions match the old public contract, or explicitly record and approve any difference.
Migrations MUST keep existing YAML and JSON field names. Do not rename config keys to match new code style.
Do not add public config options as part of a migration unless they are required to preserve existing behavior. A proposed config option MUST name the concrete operator decision it enables; "operators may want to tune it" is not enough. Internal tuning SHOULD use constants. New user choices belong in a later feature batch with schema, stock config, metadata, and docs updated together.
autodetection_retry, update_every, and vnode are job/runtime fields in many existing collectors. Preserve the
migrated collector's current YAML/JSON behavior and keep config.go, config_schema.json, stock config, and metadata
consistent. Do not copy another collector's schema treatment for these fields without checking current framework
expectations.
Host scopes, Functions, and topology are product design choices, not automatic migration side effects.
<name>func/ package behind a narrow Deps
interface. That interface MUST NOT expose, return, or embed *Collector.src/go/pkg/topology/v1 and validate against
src/plugins.d/FUNCTION_TOPOLOGY_SCHEMA.json.Migration tests MUST prove compatibility and V2 behavior.
Current shared helpers can prove schema validation, template compilation, and fixture chart coverage. They do not prove full V1-to-V2 chart identity parity, and they do not prove alert variables unless the migration records the variable source explicitly.
The non-negotiable minimum is alert-observable parity. The migration MUST prove that health alert on: contexts,
referenced dimensions, referenced variables, and dimension algorithms/units used by alerts still resolve after
migration. This is provable today with chart-template checks plus manual alert-variable review.
Exhaustive compiled chart-identity parity is broader: title, family, type, priority, multiplier, divisor, hidden, float
flags, label promotion, and lifecycle policy. If existing exported helpers cannot observe those fields, either add a
framework collecttest helper under src/go/plugin/framework/docs/changing-framework-code.md, or record a manual
comparison recipe with exact fields and evidence. Do not claim exhaustive chart identity parity without one of those
paths.
At minimum:
Init, Check, Collect, and Cleanup lifecycle coverage;BeginCycle, successful commit, and abort on expected hard collection errors;on: context must exist in the compiled template, and every
variable referenced by alert calculations must still be provided by a dimension, variable-equivalent design, or
approved alert change;collecttest.AssertChartCoverage is not a replacement for chart-identity parity. It verifies that emitted series and
the template agree; it can still pass when both writer and template were renamed consistently.
Exhaustive chart-identity parity is required before claiming full compatibility. If the migration lacks a shared helper or recorded manual comparison recipe for the compiled fields listed above, claim only the narrower compatibility that was actually proven.
Until a shared alert-variable helper exists, use a manual grep/review pass for alert variables:
rg -n "\\$[A-Za-z_][A-Za-z0-9_]*" src/health/health.d/<collector>.conf
rg -n "Vars:" src/go/plugin/go.d/collector/<collector> -g '*.go'
Every referenced variable must still be supplied by a dimension, variable-equivalent design, or approved alert change.
Prefer table-driven tests using map[string]struct{} keyed by case name when cases share setup and assertion shape.
When chart-identity parity needs the compiled fields above and no shared helper observes them, this is a concrete form of that "manual comparison recipe": prove context/dimension/value parity by rendering BOTH collectors into one manifest shape, without depending on chart-ID string equality.
Collect() + Charts() and
serialize, per chart: context, type, family, units, priority, and per dimension the name, algorithm, and de-scaled
value (the V1 map[string]int64 value divided by the dimension Div). Commit the goldens under testdata/.ChartTemplateYAML() into a real chartengine,
run a cycle through the real Collect() and metrix store, plan against the store, and read the plan's
create/update actions into the same per-chart structure.value*Div to int64 while V2 stores the true float, so
compare values within a tolerance (for example 1e-3), not for exact equality. Assert that context, family, units,
dimension names, and dimension algorithms match.Compare chart IDs too when the migration preserves them (the default). Omit only chart-ID equality when the user
approved a breaking chart-ID change -- then the preserved contract is the context, and chart-ID-keyed alert examples in
src/health/REFERENCE.md must be updated. A gap (NaN or absent value) MUST fail the comparison loudly; never silently
map it to 0.
The render MUST go through the collector's LIVE ChartTemplateYAML() and store, not a separately rebuilt template -- a
test that rebuilds the template can pass even when the shipped ChartTemplateYAML() is wrong.
Run the narrowest commands that prove the changed contract. Typical migration validation includes:
cd src/go
go test -count=1 ./plugin/go.d/collector/<name>/...
go test -race -count=1 ./plugin/go.d/collector/<name>/...
timeout 15s go run ./cmd/godplugin -m <name> -d
For the load-verification command, success means the module is registered, a job starts, and the command keeps running
until the timeout stops it. Treat unknown module, no jobs started, config-load errors, or an immediate exit before
the timeout as failures. Use -c <config-dir> when the migrated test config lives outside the normal go.d config search
path.
Also run framework or integration checks when the migration touches those contracts:
go test -count=1 ./plugin/framework/charttpl ./plugin/framework/chartenginemetrix changes: go test -count=1 ./pkg/metrix/...src/go/plugin/framework/docs/changing-framework-code.md.agents/skills/integrations-lifecycle/consistency.mdRecord exactly what ran. Full validation MUST NOT be claimed from a narrow command.
Prefer small coherent commits:
charts.yaml while preserving chart identity.If the migration requires a framework change, stop and follow src/go/plugin/framework/docs/changing-framework-code.md
before implementing collector-local glue.