diff --git a/components/receivers/nccl_fr/factory.go b/components/receivers/nccl_fr/factory.go index ca5fff94..6afe538f 100644 --- a/components/receivers/nccl_fr/factory.go +++ b/components/receivers/nccl_fr/factory.go @@ -6,34 +6,46 @@ import ( "context" "fmt" - "github.com/tracecoreai/tracecore/internal/consumer" - "github.com/tracecoreai/tracecore/internal/pipeline" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" ) -func componentType() pipeline.Type { return pipeline.MustNewType("nccl_fr") } +// componentType is the kind name registered in components.yaml. +// Wrapped in a function so the MustNewType call is not a top-level side +// effect (mirrors the clockreceiver / kernelevents pattern). +func componentType() component.Type { return component.MustNewType("nccl_fr") } -// Factory is the package-scoped ReceiverFactory for nccl_fr. -var Factory pipeline.ReceiverFactory = &factory{} +// stability is the OCB-surfaced stability level for nccl_fr's logs +// signal. Beta tracks "metrics + label shape pinned; behavior may +// evolve" — same level the receiver has carried since the v0.1.x +// internal/pipeline factory; PR-B2 preserves it across the upstream +// swap. +const stability = component.StabilityLevelBeta -// NewFactory returns the package-var Factory. Required by -// tools/components-gen. -func NewFactory() pipeline.ReceiverFactory { return Factory } - -type factory struct{} - -func (*factory) Type() pipeline.Type { return componentType() } - -func (*factory) CreateDefaultConfig() pipeline.Config { return defaultConfig() } - -func (*factory) CreateMetrics(_ context.Context, _ pipeline.CreateSettings, _ pipeline.Config, _ consumer.Metrics) (pipeline.Receiver, error) { - return nil, pipeline.ErrSignalNotSupported +// NewFactory returns the upstream receiver.Factory for nccl_fr. +// Mirrors the upstream-contrib pattern (otlpreceiver, filelogreceiver) — +// callers construct via `ncclfr.NewFactory()` rather than a package var, +// so each OCB-stitched pipeline gets a freshly-built factory and the +// package surface stays a single exported symbol. +// +// Only the logs signal returns a real Receiver; metrics + traces +// surface upstream's "signal not supported" via receiver.NewFactory's +// default unimplemented behavior. +func NewFactory() receiver.Factory { + return receiver.NewFactory( + componentType(), + createDefaultConfig, + receiver.WithLogs(createLogs, stability), + ) } -func (*factory) CreateTraces(_ context.Context, _ pipeline.CreateSettings, _ pipeline.Config, _ consumer.Traces) (pipeline.Receiver, error) { - return nil, pipeline.ErrSignalNotSupported -} +// createDefaultConfig matches upstream component.CreateDefaultConfigFunc. +func createDefaultConfig() component.Config { return defaultConfig() } -func (*factory) CreateLogs(ctx context.Context, set pipeline.CreateSettings, cfg pipeline.Config, next consumer.Logs) (pipeline.Receiver, error) { +// createLogs is the receiver.CreateLogsFunc wired by WithLogs. +func createLogs(ctx context.Context, set receiver.Settings, cfg component.Config, next consumer.Logs) (receiver.Logs, error) { c, ok := cfg.(*Config) if !ok { return nil, fmt.Errorf("nccl_fr: unexpected config type %T", cfg) @@ -42,18 +54,18 @@ func (*factory) CreateLogs(ctx context.Context, set pipeline.CreateSettings, cfg return nil, fmt.Errorf("nccl_fr: %w", err) } r := newReceiver(set, c, next) - if set.Telemetry.MeterProvider != nil { - if rt, err := newSelfTelemetry(set.ID, set.Telemetry.MeterProvider); err == nil { + if set.MeterProvider != nil { + if rt, err := newSelfTelemetry(set.ID, set.MeterProvider); err == nil { r.telemetry = rt } else { - recordInitError(ctx, set.Telemetry.MeterProvider, + recordInitError(ctx, set.MeterProvider, "receiver", set.ID.String(), reasonInstrumentRegister) - if set.Telemetry.Logger != nil { - set.Telemetry.Logger.Warn("nccl_fr self-telemetry init failed; using noop", "err", err) + if set.Logger != nil { + set.Logger.Warn("nccl_fr self-telemetry init failed; using noop", zap.Error(err)) } } - } else if set.Telemetry.Logger != nil { - set.Telemetry.Logger.Warn("nccl_fr: no MeterProvider; self-telemetry using noop") + } else if set.Logger != nil { + set.Logger.Warn("nccl_fr: no MeterProvider; self-telemetry using noop") } return r, nil } diff --git a/components/receivers/nccl_fr/lifecycle.go b/components/receivers/nccl_fr/lifecycle.go index 5cf74089..46e0ea19 100644 --- a/components/receivers/nccl_fr/lifecycle.go +++ b/components/receivers/nccl_fr/lifecycle.go @@ -14,10 +14,11 @@ import ( "context" "errors" "fmt" - "log/slog" "runtime" "sync" "sync/atomic" + + "go.uber.org/zap" ) // errLifecycleAlreadyStarted is returned by lifecycle.Start when called @@ -38,7 +39,7 @@ type panicCallback func(panicValue any) // caller-ctx deadline) is stashed + returned by every subsequent // Shutdown so deadline failures aren't silently swallowed. type lifecycle struct { - logger *slog.Logger + logger *zap.Logger onPanic panicCallback mu sync.Mutex @@ -50,11 +51,11 @@ type lifecycle struct { } // newLifecycle constructs a lifecycle. logger may be nil (replaced with -// slog.Default for tests). onPanic may be nil — panics are still +// zap.NewNop for tests). onPanic may be nil — panics are still // recovered + logged at ERROR but no callback fires. -func newLifecycle(logger *slog.Logger, onPanic panicCallback) *lifecycle { +func newLifecycle(logger *zap.Logger, onPanic panicCallback) *lifecycle { if logger == nil { - logger = slog.Default() + logger = zap.NewNop() } return &lifecycle{logger: logger, onPanic: onPanic} } @@ -87,7 +88,7 @@ func (l *lifecycle) safeRun(ctx context.Context, run func(context.Context)) { defer l.wg.Done() defer func() { if rec := recover(); rec != nil { - l.logger.Error("nccl_fr lifecycle: run panic recovered", "panic", fmt.Sprintf("%v", rec)) + l.logger.Error("nccl_fr lifecycle: run panic recovered", zap.String("panic", fmt.Sprintf("%v", rec))) if l.onPanic != nil { l.onPanic(rec) } @@ -129,7 +130,7 @@ func (l *lifecycle) Shutdown(ctx context.Context) error { // it here lets operators eyeball whether the leak is plausibly // ours. l.logger.Warn("nccl_fr lifecycle: shutdown deadline elapsed before goroutine exited", - "process_goroutines", runtime.NumGoroutine()) + zap.Int("process_goroutines", runtime.NumGoroutine())) err := fmt.Errorf("nccl_fr lifecycle shutdown: %w", ctx.Err()) l.mu.Lock() l.shutdownErr = err diff --git a/components/receivers/nccl_fr/lifecycle_test.go b/components/receivers/nccl_fr/lifecycle_test.go index a02704a9..659e6b06 100644 --- a/components/receivers/nccl_fr/lifecycle_test.go +++ b/components/receivers/nccl_fr/lifecycle_test.go @@ -5,18 +5,19 @@ package ncclfr import ( "context" "errors" - "log/slog" "sync" "sync/atomic" "testing" "time" + + "go.uber.org/zap" ) // TestLifecycle_StartShutdown pins the happy path: Start spawns the // supplied run function, Shutdown cancels its ctx, run returns, Shutdown // returns nil. func TestLifecycle_StartShutdown(t *testing.T) { - lc := newLifecycle(slog.Default(), nil) + lc := newLifecycle(zap.NewNop(), nil) started := make(chan struct{}) stopped := make(chan struct{}) @@ -43,7 +44,7 @@ func TestLifecycle_StartShutdown(t *testing.T) { // intervening Shutdown returns errLifecycleAlreadyStarted. The sentinel // MUST be errors.Is-comparable so callers don't string-match. func TestLifecycle_StartTwiceErrors(t *testing.T) { - lc := newLifecycle(slog.Default(), nil) + lc := newLifecycle(zap.NewNop(), nil) if err := lc.Start(context.Background(), func(ctx context.Context) { <-ctx.Done() }); err != nil { t.Fatalf("first Start: %v", err) } @@ -57,7 +58,7 @@ func TestLifecycle_StartTwiceErrors(t *testing.T) { // TestLifecycle_ShutdownIdempotent pins: a second Shutdown returns the // first call's error (typically nil), not "double-close" or panic. func TestLifecycle_ShutdownIdempotent(t *testing.T) { - lc := newLifecycle(slog.Default(), nil) + lc := newLifecycle(zap.NewNop(), nil) if err := lc.Start(context.Background(), func(ctx context.Context) { <-ctx.Done() }); err != nil { t.Fatalf("Start: %v", err) } @@ -77,7 +78,7 @@ func TestLifecycle_PanicCallbackFires(t *testing.T) { var called atomic.Int32 var got any var mu sync.Mutex - lc := newLifecycle(slog.Default(), func(v any) { + lc := newLifecycle(zap.NewNop(), func(v any) { called.Add(1) mu.Lock() got = v @@ -118,7 +119,7 @@ func TestLifecycle_PanicCallbackFires(t *testing.T) { // ctx err (wrapping). nccl_fr's run loop honors ctx, so this is purely // a contract pin so future authors don't silently lose the deadline. func TestLifecycle_ShutdownDeadlineReturnsCtxErr(t *testing.T) { - lc := newLifecycle(slog.Default(), nil) + lc := newLifecycle(zap.NewNop(), nil) leak := make(chan struct{}) if err := lc.Start(context.Background(), func(context.Context) { <-leak // ignore ctx diff --git a/components/receivers/nccl_fr/nccl_fr.go b/components/receivers/nccl_fr/nccl_fr.go index 51208117..c518560b 100644 --- a/components/receivers/nccl_fr/nccl_fr.go +++ b/components/receivers/nccl_fr/nccl_fr.go @@ -6,17 +6,18 @@ import ( "context" "errors" "fmt" - "log/slog" "os" "path/filepath" "sync" "time" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" - "github.com/tracecoreai/tracecore/internal/consumer" - "github.com/tracecoreai/tracecore/internal/pipeline" frparser "github.com/tracecoreai/tracecore/pkg/nccl/fr_parser" ) @@ -33,11 +34,13 @@ const schemaURL = "https://raw.githubusercontent.com/TraceCoreAI/tracecore/main/ const degradedStreak = 30 * time.Second // receiver tails a dump directory and emits one OTel log record per -// FlightRecorder ring-buffer entry. -type receiver struct { - pipeline.ComponentState - - set pipeline.CreateSettings +// FlightRecorder ring-buffer entry. Implements upstream receiver.Logs +// (alias for component.Component; the type identity is a documentation +// marker upstream) directly; the lifecycle bookkeeping is delegated to +// the sibling `lifecycle` helper, so this struct only carries scan / +// emit state. +type ncclfrReceiver struct { + set receiver.Settings cfg *Config next consumer.Logs telemetry selfTelemetry @@ -51,8 +54,14 @@ type receiver struct { degraded bool } -func newReceiver(set pipeline.CreateSettings, cfg *Config, next consumer.Logs) *receiver { - r := &receiver{ +// Compile-time assertion: receiver satisfies the upstream receiver.Logs +// interface (component.Component embedded in receiver.Logs). A breaking +// shape change in upstream surfaces here at build time rather than at +// runtime when OCB stitches the pipeline. +var _ receiver.Logs = (*ncclfrReceiver)(nil) + +func newReceiver(set receiver.Settings, cfg *Config, next consumer.Logs) *ncclfrReceiver { + r := &ncclfrReceiver{ set: set, cfg: cfg.withDefaults(), next: next, @@ -66,39 +75,36 @@ func newReceiver(set pipeline.CreateSettings, cfg *Config, next consumer.Logs) * return r } -func (r *receiver) logger() *slog.Logger { - if r.set.Telemetry.Logger != nil { - return r.set.Telemetry.Logger +// logger returns the receiver-scoped zap logger. Falls back to +// zap.NewNop when the runtime didn't supply one, so call sites never +// nil-check before logging. +func (r *ncclfrReceiver) logger() *zap.Logger { + if r.set.Logger != nil { + return r.set.Logger } - return slog.Default() + return zap.NewNop() } -func (r *receiver) Start(ctx context.Context, host pipeline.Host) error { - if err := r.ComponentState.Start(ctx, host); err != nil { - return err - } +func (r *ncclfrReceiver) Start(ctx context.Context, _ component.Host) error { if err := r.lc.Start(ctx, r.run); err != nil { return fmt.Errorf("nccl_fr lifecycle start: %w", err) } r.logger().Info("nccl_fr started", - "dump_dir", r.cfg.DumpDir, - "glob", r.cfg.Glob, - "poll_interval", r.cfg.PollInterval) + zap.String("dump_dir", r.cfg.DumpDir), + zap.String("glob", r.cfg.Glob), + zap.Duration("poll_interval", r.cfg.PollInterval)) return nil } -func (r *receiver) Shutdown(ctx context.Context) error { +func (r *ncclfrReceiver) Shutdown(ctx context.Context) error { if err := r.lc.Shutdown(ctx); err != nil { return fmt.Errorf("nccl_fr lifecycle shutdown: %w", err) } - if err := r.ComponentState.Shutdown(ctx); err != nil { - return err - } r.logger().Info("nccl_fr stopped") return nil } -func (r *receiver) run(ctx context.Context) { +func (r *ncclfrReceiver) run(ctx context.Context) { t := time.NewTicker(r.cfg.PollInterval) defer t.Stop() r.scan(ctx) @@ -112,7 +118,7 @@ func (r *receiver) run(ctx context.Context) { } } -func (r *receiver) scan(ctx context.Context) { +func (r *ncclfrReceiver) scan(ctx context.Context) { entries, err := os.ReadDir(r.cfg.DumpDir) if err != nil { r.handleDirError(err) @@ -151,7 +157,7 @@ func (r *receiver) scan(ctx context.Context) { // failure logs at Warn with operator-actionable hints; subsequent // failures within the streak are silent (the warn rate-limit is // implicit — we log only on the transition). -func (r *receiver) handleDirError(err error) { +func (r *ncclfrReceiver) handleDirError(err error) { r.telemetry.IncError(kindEnumerate) r.mu.Lock() firstFailure := r.firstDirErrAt.IsZero() @@ -167,19 +173,19 @@ func (r *receiver) handleDirError(err error) { if firstFailure { r.logger().Warn("nccl_fr: dump dir unreadable", - "dir", r.cfg.DumpDir, - "err", err, - "hint", "check the path exists, the receiver has read permission, and the writer (PyTorch TORCH_NCCL_DEBUG_INFO_TEMP_FILE) targets this directory") + zap.String("dir", r.cfg.DumpDir), + zap.Error(err), + zap.String("hint", "check the path exists, the receiver has read permission, and the writer (PyTorch TORCH_NCCL_DEBUG_INFO_TEMP_FILE) targets this directory")) } if flip { r.telemetry.SetDegraded(true) r.logger().Warn("nccl_fr: degraded — dump dir unreadable for >=30s", - "dir", r.cfg.DumpDir, - "err", err) + zap.String("dir", r.cfg.DumpDir), + zap.Error(err)) } } -func (r *receiver) handleDirRecovery() { +func (r *ncclfrReceiver) handleDirRecovery() { r.mu.Lock() if r.firstDirErrAt.IsZero() && !r.degraded { r.mu.Unlock() @@ -191,24 +197,24 @@ func (r *receiver) handleDirRecovery() { r.mu.Unlock() if wasDegraded { r.telemetry.SetDegraded(false) - r.logger().Info("nccl_fr: recovered — dump dir readable", "dir", r.cfg.DumpDir) + r.logger().Info("nccl_fr: recovered — dump dir readable", zap.String("dir", r.cfg.DumpDir)) } } -func (r *receiver) processFile(ctx context.Context, path string, size int64) { +func (r *ncclfrReceiver) processFile(ctx context.Context, path string, size int64) { if r.cfg.MaxFileBytes > 0 && size > r.cfg.MaxFileBytes { r.telemetry.IncError(kindEnumerate) r.logger().Warn("nccl_fr: file exceeds max_file_bytes; skipping", - "path", path, - "size", size, - "max", r.cfg.MaxFileBytes, - "hint", "increase max_file_bytes or reduce PyTorch's TORCH_NCCL_TRACE_BUFFER_SIZE") + zap.String("path", path), + zap.Int64("size", size), + zap.Int64("max", r.cfg.MaxFileBytes), + zap.String("hint", "increase max_file_bytes or reduce PyTorch's TORCH_NCCL_TRACE_BUFFER_SIZE")) return } raw, err := os.ReadFile(path) //nolint:gosec // dump dir is operator-configured if err != nil { r.telemetry.IncError(kindRead) - r.logger().Warn("nccl_fr: read file", "path", path, "err", err) + r.logger().Warn("nccl_fr: read file", zap.String("path", path), zap.Error(err)) return } v, err := frparser.ParseWithOptions(raw, frparser.Options{Path: path}) @@ -217,23 +223,23 @@ func (r *receiver) processFile(ctx context.Context, path string, size int64) { case errors.Is(err, frparser.ErrTruncated): // Partial pickle is expected for in-progress writes; the // next mtime bump will trigger a retry. - r.logger().Info("nccl_fr: pickle truncated; retrying when the writer finishes", "path", path, "err", err) + r.logger().Info("nccl_fr: pickle truncated; retrying when the writer finishes", zap.String("path", path), zap.Error(err)) case errors.Is(err, frparser.ErrUnsafeOpcode): r.telemetry.IncError(kindParse) r.logger().Error("nccl_fr: unsafe pickle opcode rejected", - "path", path, - "err", err, - "security.event", true) + zap.String("path", path), + zap.Error(err), + zap.Bool("security.event", true)) default: r.telemetry.IncError(kindParse) - r.logger().Warn("nccl_fr: parse failed", "path", path, "err", err) + r.logger().Warn("nccl_fr: parse failed", zap.String("path", path), zap.Error(err)) } return } records, version, err := frparser.DecodeRecords(v) if err != nil { r.telemetry.IncError(kindParse) - r.logger().Warn("nccl_fr: decode records", "path", path, "err", err) + r.logger().Warn("nccl_fr: decode records", zap.String("path", path), zap.Error(err)) return } if len(records) == 0 { @@ -241,17 +247,17 @@ func (r *receiver) processFile(ctx context.Context, path string, size int64) { } if err := r.emit(ctx, records, version, path); err != nil { r.telemetry.IncError(kindDownstream) - r.logger().Warn("nccl_fr: emit", "path", path, "err", err) + r.logger().Warn("nccl_fr: emit", zap.String("path", path), zap.Error(err)) return } r.telemetry.MarkActivity() } -func (r *receiver) emit(ctx context.Context, records []frparser.Record, version, path string) error { +func (r *ncclfrReceiver) emit(ctx context.Context, records []frparser.Record, version, path string) error { ld := plog.NewLogs() rl := ld.ResourceLogs().AppendEmpty() res := rl.Resource() - r.set.Telemetry.Resource.CopyTo(res) + r.set.Resource.CopyTo(res) if r.cfg.HwID != "" { res.Attributes().PutStr("hw.id", r.cfg.HwID) } @@ -265,7 +271,7 @@ func (r *receiver) emit(ctx context.Context, records []frparser.Record, version, sl := rl.ScopeLogs().AppendEmpty() sl.SetSchemaUrl(schemaURL) scope := sl.Scope() - scope.SetName("github.com/tracecoreai/tracecore/components/receivers/nccl_fr") + scope.SetName(instrumentationScope) for _, rec := range records { lr := sl.LogRecords().AppendEmpty() ts := time.Unix(0, rec.TimeCreatedNs) diff --git a/components/receivers/nccl_fr/nccl_fr_test.go b/components/receivers/nccl_fr/nccl_fr_test.go index 9b3a0470..2cf402b4 100644 --- a/components/receivers/nccl_fr/nccl_fr_test.go +++ b/components/receivers/nccl_fr/nccl_fr_test.go @@ -10,10 +10,12 @@ import ( "testing" "time" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/receivertest" - "github.com/tracecoreai/tracecore/internal/consumer" - "github.com/tracecoreai/tracecore/internal/pipeline" frparser "github.com/tracecoreai/tracecore/pkg/nccl/fr_parser" ) @@ -276,10 +278,16 @@ func TestConfig_Validate(t *testing.T) { // --- helpers --- -func testSettings() pipeline.CreateSettings { - return pipeline.CreateSettings{ - ID: pipeline.MustNewID(pipeline.MustNewType("nccl_fr"), "test"), - } +// testSettings returns receiver.Settings sourced from receivertest's +// upstream nop helper (so BuildInfo + TelemetrySettings track upstream +// without manual updates), with the ID overridden to a stable +// "nccl_fr/test" — selftel assertions assert that literal label, and a +// per-run UUID would defeat them. The wrapper lives here, not in the +// production package, so production code never grows a test-only surface. +func testSettings() receiver.Settings { + set := receivertest.NewNopSettings(componentType()) + set.ID = component.NewIDWithName(componentType(), "test") + return set } type logsSink struct { diff --git a/components/receivers/nccl_fr/selftel.go b/components/receivers/nccl_fr/selftel.go index ea54e7b2..13c207d3 100644 --- a/components/receivers/nccl_fr/selftel.go +++ b/components/receivers/nccl_fr/selftel.go @@ -18,10 +18,9 @@ import ( "sync/atomic" "time" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" - - "github.com/tracecoreai/tracecore/internal/pipeline" ) // kind is a low-cardinality error-class identifier. Mirrors the @@ -85,7 +84,7 @@ var _ selfTelemetry = noopSelfTelemetry{} // `component_id` label on every emission. Registers the same five // instruments the v0.1.x internal selftelemetry package registered, so // scraped metric names + label shape are unchanged. -func newSelfTelemetry(id pipeline.ID, mp metric.MeterProvider) (selfTelemetry, error) { +func newSelfTelemetry(id component.ID, mp metric.MeterProvider) (selfTelemetry, error) { if mp == nil { return nil, errNilMeterProvider } diff --git a/components/receivers/nccl_fr/selftel_test.go b/components/receivers/nccl_fr/selftel_test.go index 738eb048..24d2e246 100644 --- a/components/receivers/nccl_fr/selftel_test.go +++ b/components/receivers/nccl_fr/selftel_test.go @@ -15,8 +15,6 @@ import ( "go.opentelemetry.io/otel/metric/embedded" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" - - "github.com/tracecoreai/tracecore/internal/pipeline" ) // newTestMeterProvider builds an SDK MeterProvider backed by a ManualReader @@ -109,7 +107,7 @@ func TestSelfTelemetry_NoopAlwaysSafe(t *testing.T) { // silently substituting noop — the factory is responsible for the fallback // + the RecordInitError tick. func TestSelfTelemetry_NewReceiver_NilProviderErrors(t *testing.T) { - _, err := newSelfTelemetry(testID(), nil) + _, err := newSelfTelemetry(testSettings().ID, nil) if !errors.Is(err, errNilMeterProvider) { t.Fatalf("err = %v, want errNilMeterProvider", err) } @@ -123,7 +121,7 @@ func TestSelfTelemetry_NewReceiver_NilProviderErrors(t *testing.T) { // metric-name prefix fails here. func TestSelfTelemetry_EmitsErrorsTotal_WithKindAndComponentID(t *testing.T) { mp, rdr := newTestMeterProvider(t) - st, err := newSelfTelemetry(testID(), mp) + st, err := newSelfTelemetry(testSettings().ID, mp) if err != nil { t.Fatalf("newSelfTelemetry: %v", err) } @@ -171,7 +169,7 @@ func TestSelfTelemetry_EmitsErrorsTotal_WithKindAndComponentID(t *testing.T) { // regression in receivers that pass negative debug values). func TestSelfTelemetry_EmitsEmissionsTotal(t *testing.T) { mp, rdr := newTestMeterProvider(t) - st, err := newSelfTelemetry(testID(), mp) + st, err := newSelfTelemetry(testSettings().ID, mp) if err != nil { t.Fatalf("newSelfTelemetry: %v", err) } @@ -201,7 +199,7 @@ func TestSelfTelemetry_EmitsEmissionsTotal(t *testing.T) { // so a future drift back to the internal name fails here. func TestSelfTelemetry_ScopeNameIsReceiverImportPath(t *testing.T) { mp, rdr := newTestMeterProvider(t) - st, err := newSelfTelemetry(testID(), mp) + st, err := newSelfTelemetry(testSettings().ID, mp) if err != nil { t.Fatalf("newSelfTelemetry: %v", err) } @@ -225,7 +223,7 @@ func TestSelfTelemetry_ScopeNameIsReceiverImportPath(t *testing.T) { // recordInitError call must fail this test. func TestRecordInitError_TicksInitErrorsCounter(t *testing.T) { mp, rdr := newTestMeterProvider(t) - recordInitError(context.Background(), mp, "receiver", testID().String(), reasonInstrumentRegister) + recordInitError(context.Background(), mp, "receiver", testSettings().ID.String(), reasonInstrumentRegister) rm := collect(t, rdr) m, ok := findInstrument(rm, "tracecore.selftelemetry.init_errors_total") @@ -277,17 +275,15 @@ func TestFactory_FallsBackToNoopWhenMeterFails(t *testing.T) { mp, rdr := newTestMeterProvider(t) failing := &failingReceiverMP{real: mp} - set := pipeline.CreateSettings{ - ID: pipeline.MustNewID(pipeline.MustNewType("nccl_fr"), "test"), - } - set.Telemetry.MeterProvider = failing + set := testSettings() + set.MeterProvider = failing cfg := defaultConfig() cfg.DumpDir = t.TempDir() - r, err := Factory.CreateLogs(context.Background(), set, cfg, newLogsSink()) + r, err := NewFactory().CreateLogs(context.Background(), set, cfg, newLogsSink()) if err != nil { t.Fatalf("CreateLogs: %v", err) } - recv, ok := r.(*receiver) + recv, ok := r.(*ncclfrReceiver) if !ok { t.Fatalf("receiver type: got %T, want *receiver", r) } @@ -316,10 +312,6 @@ func TestFactory_FallsBackToNoopWhenMeterFails(t *testing.T) { } } -func testID() pipeline.ID { - return pipeline.MustNewID(pipeline.MustNewType("nccl_fr"), "test") -} - func dumpNames(rm metricdata.ResourceMetrics) string { var b strings.Builder for _, sm := range rm.ScopeMetrics { diff --git a/go.mod b/go.mod index f820dfd3..720466c3 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,17 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/collector/component v1.59.0 + go.opentelemetry.io/collector/consumer v1.59.0 go.opentelemetry.io/collector/pdata v1.59.0 + go.opentelemetry.io/collector/receiver v1.59.0 + go.opentelemetry.io/collector/receiver/receivertest v0.153.0 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/prometheus v0.65.0 go.opentelemetry.io/otel/metric v1.43.0 go.opentelemetry.io/otel/sdk/metric v1.43.0 go.uber.org/goleak v1.3.0 + go.uber.org/zap v1.28.0 golang.org/x/sys v0.45.0 golang.org/x/time v0.15.0 gopkg.in/yaml.v3 v3.0.1 @@ -224,26 +229,33 @@ require ( go-simpler.org/sloglint v0.11.0 // indirect go.augendre.info/fatcontext v0.8.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/collector/component/componenttest v0.153.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror v0.153.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.153.0 // indirect go.opentelemetry.io/collector/featuregate v1.59.0 // indirect + go.opentelemetry.io/collector/internal/componentalias v0.153.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.153.0 // indirect + go.opentelemetry.io/collector/pipeline v1.59.0 // indirect + go.opentelemetry.io/collector/receiver/xreceiver v0.153.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.55.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect golang.org/x/term v0.43.0 // indirect golang.org/x/text v0.37.0 // indirect golang.org/x/tools v0.45.0 // indirect golang.org/x/vuln v1.3.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 9e17ed98..986e60df 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9 github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= @@ -177,6 +175,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= @@ -362,8 +362,6 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -521,12 +519,38 @@ go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lm go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/collector/component v1.59.0 h1:WtulkwzsdAOM/LE0cH/IiudUgiyb2ueVDDeEh5HsXzo= +go.opentelemetry.io/collector/component v1.59.0/go.mod h1:5eQCM0tS6qbG2XJ6Bt67kgCxuhCbg4csVv6SywyHZ6w= +go.opentelemetry.io/collector/component/componenttest v0.153.0 h1:u6O1l+9PUNnI22k8q4sF9wgLAUyt+y2Ov7xi1yZ+wE4= +go.opentelemetry.io/collector/component/componenttest v0.153.0/go.mod h1:Ym/d5UxnoHmrCI5LsVANBicikNiqtwjk8uWBM+Z4jDM= +go.opentelemetry.io/collector/consumer v1.59.0 h1:EFDQ8ZTWwHEratWusKgW26W20LeAWdXj235iD9MxpuA= +go.opentelemetry.io/collector/consumer v1.59.0/go.mod h1:wrOSfXkKjzEeHYxhKGukuB9+NFV/CboWwEH1oEXhMtw= +go.opentelemetry.io/collector/consumer/consumererror v0.153.0 h1:x64J2xnSOoOp3yZRWioZ03vNAF19aBioUsRlZ4eC6x8= +go.opentelemetry.io/collector/consumer/consumererror v0.153.0/go.mod h1:5zJ8TYQisuMjWJNaW6S86cHY1rf3DzXOVvsN8pA3Cc8= +go.opentelemetry.io/collector/consumer/consumertest v0.153.0 h1:fCqkOWNVK/qipg4SX60zICs3jhKDjcrtF1oxJR6msCk= +go.opentelemetry.io/collector/consumer/consumertest v0.153.0/go.mod h1:YfVqyCrvEfvsbbrpfiH97fITh4vmIBzZT112tUluqx0= +go.opentelemetry.io/collector/consumer/xconsumer v0.153.0 h1:Jt1Aiiq9zDILzppRTEdZXaA830KBjjP1i2kWL+h5br0= +go.opentelemetry.io/collector/consumer/xconsumer v0.153.0/go.mod h1:IzJ/dTOtEUMjY0DYu6kO946u5jbiDDUG8+sLaLhJ+HY= go.opentelemetry.io/collector/featuregate v1.59.0 h1:pu70/9eWRjAjzGnr3VmqwY+k6fmU3esLp15AqxfBBz0= go.opentelemetry.io/collector/featuregate v1.59.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/internal/componentalias v0.153.0 h1:tr5I5hsJPJBbVPGjvOVVPPK4dLR5ZLqVEckuOfnzzMs= +go.opentelemetry.io/collector/internal/componentalias v0.153.0/go.mod h1:tFSKiuWKJJgfxANyVnbKPVZkOF7N0bFXvCxCEgqMKuo= go.opentelemetry.io/collector/internal/testutil v0.153.0 h1:GJEaPLohao+7wtm08yGf73RGi1rpIHvqzxOb7Xn8sP0= go.opentelemetry.io/collector/internal/testutil v0.153.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= go.opentelemetry.io/collector/pdata v1.59.0 h1:lO1IcaO9+HUVdFh+RATCUG3oTP+uCZzsE7HJ0MjmzRc= go.opentelemetry.io/collector/pdata v1.59.0/go.mod h1:AH6M14C6qhesnUpcvigkvFMiX9KtdSWQENMBNyNhe7I= +go.opentelemetry.io/collector/pdata/pprofile v0.153.0 h1:IurcS9g28cVrtIvc1oUVN8vSm5j0t79Pd8GZgd1PV28= +go.opentelemetry.io/collector/pdata/pprofile v0.153.0/go.mod h1:nX33dLKrwdoz/FQeIs6JKPeZcS1KbU2VIOwH7K0F05c= +go.opentelemetry.io/collector/pdata/testdata v0.153.0 h1:pO/+b8Rz5PG0G+TskdYdQEjhXnNG2bQoiRRMwic6X0I= +go.opentelemetry.io/collector/pdata/testdata v0.153.0/go.mod h1:JVjUfJv9YFL3JUhisxMdLe2DX7we227Ry9JiTgwlEro= +go.opentelemetry.io/collector/pipeline v1.59.0 h1:AyEcAPy5ZuUFpqis9i97WEIAcFh/mEEo90+1wr4urNU= +go.opentelemetry.io/collector/pipeline v1.59.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs= +go.opentelemetry.io/collector/receiver v1.59.0 h1:2jf2guECLyWno3OqNaefjfo+Tt3MSm6uz1WsbIXv5Ao= +go.opentelemetry.io/collector/receiver v1.59.0/go.mod h1:rklWFy0kaMMghuGlrTxqYP96V810HwDVwIgKpP6kn2o= +go.opentelemetry.io/collector/receiver/receivertest v0.153.0 h1:YHvywYcyV1G0I5r/J0OxgOJZPLLyrMAMA0Q7VdBYFxc= +go.opentelemetry.io/collector/receiver/receivertest v0.153.0/go.mod h1:Zkf5hQ9qk9ACx/WBMZIaSri6LIJzFgfRxmScigP05tg= +go.opentelemetry.io/collector/receiver/xreceiver v0.153.0 h1:yz6By4iJzOsvl9GxVOXTn0GoSr5ZxOuvk9+24obhqDQ= +go.opentelemetry.io/collector/receiver/xreceiver v0.153.0/go.mod h1:4cM85JHe5weHwUAc60P/vPKU1uLtrhI46lzDOsSpqz4= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE= @@ -545,16 +569,14 @@ go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:R go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -603,8 +625,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -695,6 +717,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=