diff --git a/cmd/jaeger/internal/all-in-one.yaml b/cmd/jaeger/internal/all-in-one.yaml index 4dad6d883ef..bc29cabba9e 100644 --- a/cmd/jaeger/internal/all-in-one.yaml +++ b/cmd/jaeger/internal/all-in-one.yaml @@ -1,5 +1,5 @@ service: - extensions: [jaeger_storage, jaeger_query, remote_sampling, healthcheckv2] + extensions: [jaeger_storage, jaeger_query, remote_sampling, healthcheckv2, expvar] pipelines: traces: receivers: [otlp, jaeger, zipkin] @@ -33,13 +33,16 @@ extensions: # initial_sampling_probability: 0.1 http: grpc: - + healthcheckv2: use_v2: true http: endpoint: "0.0.0.0:13133" grpc: + expvar: + port: 27777 + receivers: otlp: protocols: diff --git a/cmd/jaeger/internal/components.go b/cmd/jaeger/internal/components.go index 69ab5ff0966..c47aeb8c805 100644 --- a/cmd/jaeger/internal/components.go +++ b/cmd/jaeger/internal/components.go @@ -30,6 +30,7 @@ import ( "go.opentelemetry.io/collector/receiver/otlpreceiver" "github.com/jaegertracing/jaeger/cmd/jaeger/internal/exporters/storageexporter" + "github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/expvar" "github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerquery" "github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage" "github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/remotesampling" @@ -69,6 +70,7 @@ func (b builders) build() (otelcol.Factories, error) { jaegerstorage.NewFactory(), storagecleaner.NewFactory(), remotesampling.NewFactory(), + expvar.NewFactory(), ) if err != nil { return otelcol.Factories{}, err diff --git a/cmd/jaeger/internal/extension/expvar/README.md b/cmd/jaeger/internal/extension/expvar/README.md new file mode 100644 index 00000000000..dec2c078dea --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/README.md @@ -0,0 +1,6 @@ +# expvar extension + +Adds a standard expvar HTTP handler to a port that allows introspection. + +This is a temporary implementaion until upstream ticket is addressed: +https://github.com/open-telemetry/opentelemetry-collector/issues/11081 diff --git a/cmd/jaeger/internal/extension/expvar/config.go b/cmd/jaeger/internal/extension/expvar/config.go new file mode 100644 index 00000000000..8f793570b7f --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/config.go @@ -0,0 +1,17 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "github.com/asaskevich/govalidator" +) + +type Config struct { + Port int `mapstructure:"port"` +} + +func (cfg *Config) Validate() error { + _, err := govalidator.ValidateStruct(cfg) + return err +} diff --git a/cmd/jaeger/internal/extension/expvar/config_test.go b/cmd/jaeger/internal/extension/expvar/config_test.go new file mode 100644 index 00000000000..d03603acd74 --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/config_test.go @@ -0,0 +1,16 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExtensionConfig(t *testing.T) { + config := createDefaultConfig().(*Config) + err := config.Validate() + require.NoError(t, err) +} diff --git a/cmd/jaeger/internal/extension/expvar/extension.go b/cmd/jaeger/internal/extension/expvar/extension.go new file mode 100644 index 00000000000..74dd41cc51c --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/extension.go @@ -0,0 +1,63 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "context" + "errors" + "expvar" + "fmt" + "net/http" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componentstatus" + "go.opentelemetry.io/collector/extension" + "go.uber.org/zap" +) + +var _ extension.Extension = (*expvarExtension)(nil) + +const ( + Port = 27777 +) + +type expvarExtension struct { + config *Config + server *http.Server + telset component.TelemetrySettings +} + +func newExtension(config *Config, telset component.TelemetrySettings) *expvarExtension { + return &expvarExtension{ + config: config, + telset: telset, + } +} + +func (c *expvarExtension) Start(_ context.Context, host component.Host) error { + c.server = &http.Server{ + Addr: fmt.Sprintf(":%d", c.config.Port), + Handler: expvar.Handler(), + ReadHeaderTimeout: 3 * time.Second, + } + c.telset.Logger.Info("Starting expvar server", zap.String("addr", c.server.Addr)) + go func() { + if err := c.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + err = fmt.Errorf("error starting expvar server: %w", err) + componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err)) + } + }() + + return nil +} + +func (c *expvarExtension) Shutdown(ctx context.Context) error { + if c.server != nil { + if err := c.server.Shutdown(ctx); err != nil { + return fmt.Errorf("error shutting down expvar server: %w", err) + } + } + return nil +} diff --git a/cmd/jaeger/internal/extension/expvar/extension_test.go b/cmd/jaeger/internal/extension/expvar/extension_test.go new file mode 100644 index 00000000000..5a61ebc66b7 --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/extension_test.go @@ -0,0 +1,53 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/storagetest" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.uber.org/zap/zaptest" +) + +func TestExpvarExtension(t *testing.T) { + tests := []struct { + name string + status int + }{ + { + name: "good storage", + status: http.StatusOK, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := &Config{ + Port: Port, + } + s := newExtension(config, component.TelemetrySettings{ + Logger: zaptest.NewLogger(t), + }) + require.NoError(t, s.Start(context.Background(), storagetest.NewStorageHost())) + defer s.Shutdown(context.Background()) + + addr := fmt.Sprintf("http://0.0.0.0:%d/", Port) + client := &http.Client{} + require.Eventually(t, func() bool { + r, err := http.NewRequest(http.MethodPost, addr, nil) + require.NoError(t, err) + resp, err := client.Do(r) + require.NoError(t, err) + defer resp.Body.Close() + return test.status == resp.StatusCode + }, 5*time.Second, 100*time.Millisecond) + }) + } +} diff --git a/cmd/jaeger/internal/extension/expvar/factory.go b/cmd/jaeger/internal/extension/expvar/factory.go new file mode 100644 index 00000000000..1236128a9d9 --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/factory.go @@ -0,0 +1,40 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" +) + +// componentType is the name of this extension in configuration. +var componentType = component.MustNewType("expvar") + +// ID is the identifier of this extension. +var ID = component.NewID(componentType) + +func NewFactory() extension.Factory { + return extension.NewFactory( + componentType, + createDefaultConfig, + createExtension, + component.StabilityLevelBeta, + ) +} + +func createDefaultConfig() component.Config { + return &Config{ + Port: Port, + } +} + +func createExtension( + _ context.Context, + set extension.Settings, + cfg component.Config, +) (extension.Extension, error) { + return newExtension(cfg.(*Config), set.TelemetrySettings), nil +} diff --git a/cmd/jaeger/internal/extension/expvar/factory_test.go b/cmd/jaeger/internal/extension/expvar/factory_test.go new file mode 100644 index 00000000000..866c50f80ae --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/factory_test.go @@ -0,0 +1,28 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/extension/extensiontest" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig().(*Config) + require.NotNil(t, cfg, "failed to create default config") + require.NoError(t, componenttest.CheckConfigStruct(cfg)) +} + +func TestCreateExtension(t *testing.T) { + cfg := createDefaultConfig().(*Config) + f := NewFactory() + r, err := f.CreateExtension(context.Background(), extensiontest.NewNopSettings(), cfg) + require.NoError(t, err) + assert.NotNil(t, r) +} diff --git a/cmd/jaeger/internal/extension/expvar/package_test.go b/cmd/jaeger/internal/extension/expvar/package_test.go new file mode 100644 index 00000000000..8819b3ad2f6 --- /dev/null +++ b/cmd/jaeger/internal/extension/expvar/package_test.go @@ -0,0 +1,14 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package expvar + +import ( + "testing" + + "github.com/jaegertracing/jaeger/pkg/testutils" +) + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +}