Skip to content

feat(pivot): port stdoutexporter off internal pipeline+consumer#202

Merged
trilamsr merged 1 commit into
mainfrom
pr-stdoutexporter-upstream
May 31, 2026
Merged

feat(pivot): port stdoutexporter off internal pipeline+consumer#202
trilamsr merged 1 commit into
mainfrom
pr-stdoutexporter-upstream

Conversation

@trilamsr

Copy link
Copy Markdown
Contributor

Summary

Port components/exporters/stdoutexporter off internal/{pipeline,consumer} onto upstream OTel collector v1.59.0 / v0.153.0 types. Mirrors PR-B2 #201 (nccl_fr receiver port), exporter-flavor. Sibling self-telemetry from PR #186 stays in place — only the pipeline.IDcomponent.ID + pipeline.CreateSettingsexporter.Settings type-swaps in tests are needed.

Type swap

Before (internal) After (upstream)
internal/pipeline.ExporterFactory exporter.Factory
internal/pipeline.CreateSettings exporter.Settings
internal/pipeline.Config component.Config
internal/pipeline.Exporter exporter.Metrics
internal/consumer.{Metrics,Capabilities} consumer.{Metrics,Capabilities}
pipeline.ComponentState (embedded) explicit no-op Start/Shutdown
pipeline.ErrSignalNotSupported (manual returns) upstream's built-in unregistered-signal sentinel
*slog.Logger *zap.Logger (factory-side only — exporter has no log sites)

Factory shape matches upstream-contrib (debugexporter, fileexporter):

func NewFactory() exporter.Factory {
    return exporter.NewFactory(
        componentType(),
        createDefaultConfig,
        exporter.WithMetrics(createMetrics, stability),
    )
}

WithMetrics is the only signal registered. Logs + traces fall through to upstream's built-in "telemetry type is not supported" sentinel — tests pin the surfaced message so a future upstream rename surfaces here at test-time rather than as a silent contract regression.

Root cause

RFC-0013 §migration mandates that internal/pipeline, internal/consumer, and internal/selftelemetry are deleted in PR-F. Every in-tree component must first switch to upstream OTel types so PR-F can land cleanly. PR-B2 #201 did this for the nccl_fr receiver; this PR is the exporter sibling. Not a workaround — this is the migration step itself.

Why drop pipeline.ComponentState

stdoutexporter is purely synchronous (no goroutines, no resources to acquire). The embedded ComponentState only existed to provide free Start/Shutdown/Started/Stopped accessors. Upstream component.Component requires Start/Shutdown (now explicit no-ops) but does NOT require Started/Stopped — those tests are dropped. The lifecycle helper that nccl_fr's port introduced is intentionally absent here: the exporter has no run-loop to bookend.

New adversarial tests added with the swap

  • TestFactory_CreateMetrics_BadConfigType — pins the type-guard in createMetrics so a non-*Config cfg surfaces a clear error rather than panicking on type assertion.
  • TestExporter_StartShutdown_NoOps — pins the no-op contract so a future refactor that introduces a side-effecting Start/Shutdown is forced to add its own test.
  • TestExporter_WriteErrorSurfaces — pins the I/O failure path: a writer that errors must surface a wrapped error from ConsumeMetrics (paired with the existing kindIO selftel coverage).
  • var _ exporter.Factory = NewFactory() — compile-time pin against silent regression back to internal/pipeline.ExporterFactory.

LOC delta

components/exporters/stdoutexporter/  6 files, +221 / -142
go.mod                                +6 / -0
go.sum                                +40 / -2

Net +79 LOC for the exporter package, almost entirely the four new adversarial tests above and the type-swap boilerplate the upstream contract requires (separate componentType + createDefaultConfig + createMetrics funcs vs. one struct with three methods).

Test plan

  • make check — clean (fmt, tidy, lint, vet, mod-verify all green).
  • go test -race -count=1 ./components/exporters/stdoutexporter/... — green.
  • go test ./... — green except three pre-existing flakes (verified by stashing this diff and re-running on origin/main):
    • components/receivers/kernelevents/TestReceiver_SLIBudget (p99 perf budget; flakes ~50% on local Darwin)
    • components/receivers/kernelevents/TestKernelevents_Lifecycle_ConcurrentAddDuringShutdown_NoPanic
    • components/receivers/k8sevents/TestK8sevents_Lifecycle_ConcurrentAddDuringShutdown_NoPanic
  • CI to run the same gates on a fresh checkout.
NONE

Mirrors PR-B2 #201's nccl_fr swap, exporter-flavor:

  internal/pipeline.ExporterFactory  → exporter.Factory
  internal/pipeline.CreateSettings   → exporter.Settings
  internal/pipeline.Config           → component.Config
  internal/pipeline.Exporter         → exporter.Metrics
  internal/consumer.{Metrics,...}    → consumer.{Metrics,...}
  pipeline.ComponentState (embedded) → explicit no-op Start/Shutdown
  *slog.Logger                       → *zap.Logger (factory-side only;
                                       the exporter has no log sites)

Factory pattern matches upstream-contrib (debugexporter, fileexporter):
exporter.NewFactory(type, defaultCfg, exporter.WithMetrics(create, beta)).
WithMetrics is the only signal registered — logs + traces fall through
to upstream's built-in "telemetry type is not supported" sentinel
(tests pin the surfaced message so a future upstream rename surfaces
here at test-time rather than as a silent contract regression).

Sibling self-telemetry (selftel.go) from PR #186 stays in place; only
the pipeline.ID → component.ID + pipeline.CreateSettings →
exporter.Settings type-swaps in the test file are needed.

New adversarial tests added alongside the type-swap:
  - TestFactory_CreateMetrics_BadConfigType  (type-guard pin)
  - TestExporter_StartShutdown_NoOps         (no-op contract pin)
  - TestExporter_WriteErrorSurfaces          (I/O failure path pin)
  - var _ exporter.Factory = NewFactory()    (compile-time pin)

The pipelinetest fixture is dropped from this package; tests now use
exportertest.NewNopSettings(componentType()) directly, matching the
PR-B2 pattern. The Started/Stopped accessor test is also dropped —
upstream component.Component doesn't require those, and the embedded
pipeline.ComponentState that provided them is gone.

`make check` + `go test -race ./components/exporters/stdoutexporter/...`
green. Full `go test ./...` green except three pre-existing flakes in
kernelevents (TestReceiver_SLIBudget perf budget) and k8sevents/
kernelevents (TestX_Lifecycle_ConcurrentAddDuringShutdown_NoPanic) —
all reproduce on origin/main without this diff.

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

Signed-off-by: Tri Lam <tri@maydow.com>
@trilamsr trilamsr enabled auto-merge (squash) May 31, 2026 07:35
@trilamsr trilamsr merged commit 2702193 into main May 31, 2026
14 checks passed
@trilamsr trilamsr deleted the pr-stdoutexporter-upstream branch May 31, 2026 07:42
trilamsr added a commit that referenced this pull request May 31, 2026
## Summary

Reconcile the four pivot-tracking docs
(`docs/rfcs/0013-distro-first-pivot.md`, `CHANGELOG.md`,
`MILESTONES.md`, `docs/migration/v0.1-to-v0.2.md`) with the wave-3
(PR-B1-shape sibling ports) and wave-4 (PR-B2-shape upstream-only ports
+ PR-F.1 + PR-J + PR-L + PR-N) landings. Pure doc sweep — no code or
config touched.

## What changed

### `docs/rfcs/0013-distro-first-pivot.md` §migration

PR sequence rows updated with PR-number citations and landed markers:

- **PR-A2** (landed, #189, 2026-05-30)
- **PR-B2** (landed, #201) — also enumerates sibling-receiver follow-ups
under PR-B2 to dispel the slug collision with #188's PR-B2-labelled dcgm
port: stdoutexporter (#202), pyspy (#203), kernelevents (#208),
containerstdout (#209)
- **PR-F.1** (landed) — fleshed-out delete list
(`internal/{selftelemetry,telemetry}` + `components/receivers/dcgm/` +
`pkg/dcgm/` + one orphan clockreceiver integration test)
- **PR-F.2** re-scoped — now deletes the whole
`internal/{componentstatus,pipeline,pipelinebuilder,consumer,fanout,runtime/lifecycle}`
bundle in one cut once the last three pipeline+consumer-importing
receivers land (#204 k8sevents, #205 clockreceiver, #207 otlphttp). Per
the import-graph state — `internal/componentstatus`'s only non-test
consumer is `internal/pipeline`, so they delete together
- **PR-G** (landed, #182), **PR-H** (landed, #183)
- **PR-I.1a** (in flight — scaffold agent), **PR-I.1b** (pre-staged;
gate satisfied by #201)
- **PR-J** (landed, #195) — kept existing marker
- **PR-K.1** (in flight — separate agent landing)
- **PR-L** (landed, skeleton #179 + body #191) — flagged as living
document
- **PR-N** (landed, #200) — shipped at v0.1.0 ahead of v0.3.0 as a
doc-only update at `docs/migration/v0.2-to-v0.3.md`

### `CHANGELOG.md` [Unreleased]

- Restructured the pivot wave list as **four waves** (was three). Wave 3
enumerates PR-B1-shape sibling ports + support infra (#180-#194/#196).
Wave 4 enumerates PR-B2-shape upstream-only ports + PR-J (#195) + PR-F.1
(#206) + PR-N (#200) + lint/TOCTOU hardening (#198/#210).
- Tightened the PR-F.2 deferred note to point at the three open ports
(#204/#205/#207) as the gate.

### `MILESTONES.md`

- **M1** (pipeline runtime) — status row now cites PR-A2 (#189), PR-F.1
(#206), PR-F.2 gate (#204/#205/#207), PR-E (#180), retains
`internal/config/` (still load-bearing for `tracecore validate`).
- **M2** (self-telemetry) — status row now cites PR-F.1 (#206); flags
`internal/componentstatus` as travelling with `internal/pipeline` in
PR-F.2.
- **M8** (DCGM receiver) — status flipped to *landed-and-replaced*:
cites PR-F.1 (#206) deletion + PR-J (#195)
`docs/integrations/prometheus-scrape.md` recipe. Notes the inert chart
toggle retention until PR-K.3.

### `docs/migration/v0.1-to-v0.2.md`

- §`internal/*` package deletion (PR-F) status flips from "not yet open"
to "PR-F.1 landed (#206), PR-F.2 gated on three open ports".
- Open-items checklist expanded from 5 to 13 entries — tracks every PR
letter the migration guide cares about (A2 / E / F.1 / F.2 / I.1a-c / J
/ K.1-3 / L / N) with PR numbers and links.

## Why now

Tracking docs accumulated drift across wave-3 + wave-4 because every
sibling-port PR (and the support-infra PRs around them) updated the
bottom of `CHANGELOG.md` but did not always touch the upstream
sequencing section in RFC-0013. Per memory rule `[Keeping this document
current]`: status drift is a review blocker. This PR is the consolidated
catch-up; future port PRs include their RFC-row flip in-PR.

## What this PR does NOT change

- No code, no config, no YAML, no chart — only the four tracking docs.
- No new doc gates added; existing gates pass.
- No PRs other than the four named docs are modified.

## Test plan

- [x] `bash scripts/doc-check.sh` clean (33 test refs, 528 links
resolve, comment-noise diff gate clean vs `origin/main`, all 13 gates
green).
- [x] Pre-commit hook (`commitlint` 72-char subject limit + DCO +
AI-trailer gates) passed.
- [x] Pre-push hook (`make ci-fast` equivalent: `golangci-lint`, `go
vet`, `go mod verify`, `no-autoupdate-check`, `doc-check.sh`) passed on
second attempt after `git fetch origin main` populated the worktree's
`origin/main` ref — first push failed because the worktree previously
tracked the (gone) `pr-a2-ocb-main-swap` branch, so `doc-check.sh`'s
comment-noise diff-scope gate exited 128 on the missing ref. Root cause
fixed by the fetch; not a workaround.
- [ ] CI green on this branch.

```release-notes
NONE
```

Signed-off-by: Tri Lam <tri@maydow.com>
Co-authored-by: Tri Lam <tri@maydow.com>
trilamsr added a commit that referenced this pull request May 31, 2026
## Summary

Deletes the seven `internal/*` packages that RFC-0013 §migration step 8
PR-F.2 promised once the upstream-port wave
(#201/#202/#203/#204/#205/#207/#208/#209) cleared every external caller
of the in-tree pipeline runtime.

**Net: -6,888 LOC across 56 deleted files, +80 LOC across 14 modified
files. 70 files total.** This is the final cut of RFC-0013 §migration
step 8 PR-F.

## What deletes

| Path | LOC | Replacement |
|---|---|---|
| `internal/pipeline/` | 4,134 | `go.opentelemetry.io/collector/service`
(OCB-generated `_build/main.go` consumes `builder-config.yaml`). |
| `internal/pipelinebuilder/` | 1,282 | Same — assembly is upstream
`service`. |
| `internal/config/` | 718 | Upstream `confmap` providers (`file`,
`yaml`, `env`). |
| `internal/consumer/` | 87 | Upstream
`go.opentelemetry.io/collector/consumer`. |
| `internal/fanout/` | 366 | Upstream `internal/fanoutconsumer`
(collector module). |
| `internal/componentstatus/` | 16 | Upstream
`component/componentstatus.ReportStatus` (same free-function shape). |
| `internal/runtime/lifecycle/` | 505 | Per-receiver package-local
`lifecycle.go` siblings — already ported during the PR-B1 wave
(#184/#185/#186/#187/#194/#196/#197); the in-tree helper had no
remaining non-test consumer after PR-F.1 + the wave-2 upstream-port PRs.
`kernelevents/lifecycle.go` was inherited from k8sevents (#208). |

## Pre-flight grep evidence

```
$ grep -rn 'tracecoreai/tracecore/internal/(pipeline|consumer|pipelinebuilder|config|fanout|componentstatus|runtime/lifecycle)' --include='*.go' .
(zero matches)
```

## Tooling

- `.golangci.yml` `ignore-interface-regexps` repointed at upstream
`consumer.{Metrics,Traces,Logs}` + `component.Component`. The
in-tree-only same-package-error-wrap exemption stays — the STYLE rule
applies regardless of which interface is forwarded.
- `.github/workflows/chaos.yml` drops the `chaos-pipeline-test` job (the
in-tree `internal/pipeline/chaos_test.go` is gone; upstream `service`
provides the equivalent panic-recovery contract). `harness-determinism`
(failure-inject golden-SHA), `cpu-steal-mpstat`, `pattern-pod-evicted`
jobs preserved.
- `.github/workflows/install-bench.yml` drops the
`internal/{pipeline,runtime,selftelemetry}/**` path-filter rows.
- `go.mod` / `go.sum` unchanged.

## Doc sweep

- `CHANGELOG.md` Unreleased: PR-F.2 landed entry replacing the "PR-F.2
deferred" sentence; "Remaining v0.1.0 work" line updated; one dead
`internal/pipeline/README.md` link in Foundation block rewritten as
"deleted at v0.1.0".
- `docs/rfcs/0013-distro-first-pivot.md` §7 deletion table: both
pipeline-internals and runtime/lifecycle rows updated from "v0.1.0
(audit first…)" / "v0.2.0 (with last consumer)" to "v0.1.0 (landed
PR-F.2)". §migration step 8 reframed.
- `docs/FAILURE-MODES.md` Lifecycle / Data flow / Shutdown timing /
Backend tables rewired from in-tree
`internal/{config,pipeline,fanout}/*_test.go::TestName` pointers to
upstream-delegated wording matching the pattern PR-A2 established.
- `docs/STRATEGY.md` "Post-RFC-0013 status" intro updated; "Stable
interfaces in `internal/pipeline/`" graduation row rewritten to point at
the upstream surface.
- `docs/migration/v0.1-to-v0.2.md` `internal/*` section status banner
flipped from "deferred, still present in RC builds" to "landed, deleted
in v0.2.0 builds".
- `MILESTONES.md` v0.1.0 deletions row extended with boot-path
internals; M1 + M4b + M19 rubric details annotated with the PR-F.2
retirement.
- `README.md` Contributor row repointed at upstream
`go.opentelemetry.io/collector` package docs.
- `AGENTS.md` "Self-telemetry internals" bullet split into "Self-tel
internals" + "Pipeline / boot-path internals" with explicit deletion
status.
- `docs/README.md` table row for `internal/pipeline/README.md` dropped.
- `components/receivers/kernelevents/README.md` lifecycle-sibling
rationale updated to past-tense.
- `tools/failure-inject/README.md` "Testing locally" section drops the
`-tags=chaos ./internal/pipeline/...` invocation.

## Sequencing

This PR is hard-gated on every upstream-port PR landing first:

- #201 nccl_fr (PR-B2)
- #202 stdoutexporter
- #203 pyspy
- #204 k8sevents
- #205 clockreceiver (PR-B3)
- #207 otlphttp
- #208 kernelevents
- #209 containerstdout
- #206 PR-F.1 (selftel / telemetry / dcgm)

All nine merged before this PR opened; this is the moat-deletion payoff.
Remaining v0.1.0 work is PR-K (chart-default flip + `clockreceiver` +
`stdoutexporter` + remaining receiver source deletions, coupled with
test-fixture migration and the `telemetry:` values-key deprecation
cycle).

## Test plan

- [x] `make check` — golangci-lint 0 issues, go vet clean, go mod verify
ok.
- [x] `go build ./...` — clean.
- [x] `go test -count=1 ./...` — green (excluding the known
`kernelevents/TestReceiver_SLIBudget` flake called out in #205's body,
which only triggers under heavy parallel `go test ./...` load; passes
standalone).
- [x] `grep` confirms zero non-internal callers of the deleted packages.
- [x] Doc-check pre-push hook passes after the CHANGELOG dead-link fix.

```release-notes
[CHANGE] internal/{pipeline,pipelinebuilder,config,consumer,fanout,componentstatus,runtime/lifecycle} packages deleted. The OCB-generated boot path off builder-config.yaml replaces them. Third-party importers of internal/* (unlikely pre-1.0; the packages live under internal/ and the Go compiler rejects external imports) lose the pipeline-assembly + lifecycle + config-loader surfaces; receiver authors now wire against upstream go.opentelemetry.io/collector/{component,receiver,consumer,pipeline} directly. See docs/migration/v0.1-to-v0.2.md "internal/* package deletion".
```

---------

Signed-off-by: Tri Lam <tri@maydow.com>
Co-authored-by: Tri Lam <tri@maydow.com>
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