diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 7a40a922837d..1e635a114040 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -281,6 +281,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/udplog - receiver/vcenter diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 90126dd48049..a3657bf810cb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -275,6 +275,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/udplog - receiver/vcenter diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml index f1d39931d1a9..bc7e05e5b530 100644 --- a/.github/ISSUE_TEMPLATE/other.yaml +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -275,6 +275,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/udplog - receiver/vcenter diff --git a/.github/ISSUE_TEMPLATE/unmaintained.yaml b/.github/ISSUE_TEMPLATE/unmaintained.yaml index 34d58a058c6f..972436b23585 100644 --- a/.github/ISSUE_TEMPLATE/unmaintained.yaml +++ b/.github/ISSUE_TEMPLATE/unmaintained.yaml @@ -280,6 +280,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/udplog - receiver/vcenter diff --git a/receiver/tcpcheckreceiver/Makefile b/receiver/tcpcheckreceiver/Makefile new file mode 100644 index 000000000000..ded7a36092dc --- /dev/null +++ b/receiver/tcpcheckreceiver/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/receiver/tcpcheckreceiver/README.md b/receiver/tcpcheckreceiver/README.md new file mode 100644 index 000000000000..4b4652d5626d --- /dev/null +++ b/receiver/tcpcheckreceiver/README.md @@ -0,0 +1,44 @@ +# TCP Check Receiver + + +| Status | | +| ------------- |-----------| +| Stability | [alpha]: metrics | +| Distributions | [contrib] | +| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Ftcpcheck%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ftcpcheck) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Ftcpcheck%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Ftcpcheck) | +| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@yanfeng1992](https://www.github.com/yanfeng1992) | + +[alpha]: https://github.com/open-telemetry/opentelemetry-collector#alpha +[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib + + +This receiver creates stats by connecting to an TCP server. + + +## Configuration + +The following settings are required: +- `endpoint` + + + +The following settings are optional: + +- `collection_interval` (default = `60s`): This receiver collects metrics on an interval. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. + + +### Example Configuration + +```yaml +receivers: + sshcheck: + endpoint: localhost:80 + collection_interval: 60s +``` + +The full list of settings exposed for this receiver are documented [here](./config.go) with detailed sample configurations [here](./testdata/config.yaml). + +## Metrics + +Details about the metrics produced by this receiver can be found in [metadata.yaml](./metadata.yaml) + diff --git a/receiver/tcpcheckreceiver/config.go b/receiver/tcpcheckreceiver/config.go new file mode 100644 index 000000000000..3c1e8fce543f --- /dev/null +++ b/receiver/tcpcheckreceiver/config.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "errors" + "net" + "strings" + + "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/multierr" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/metadata" +) + +// Predefined error responses for configuration validation failures +var ( + errMissingEndpoint = errors.New(`"endpoint" not specified in config`) + errInvalidEndpoint = errors.New(`"endpoint" is invalid`) + errConfigNotTCPCheck = errors.New("config was not a TCP check receiver config") +) + +type Config struct { + scraperhelper.ControllerConfig `mapstructure:",squash"` + configtcp.TCPClientSettings `mapstructure:",squash"` + MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"` +} + +func (c Config) Validate() (err error) { + if c.TCPClientSettings.Endpoint == "" { + err = multierr.Append(err, errMissingEndpoint) + } else if strings.Contains(c.TCPClientSettings.Endpoint, " ") { + err = multierr.Append(err, errInvalidEndpoint) + } else if _, _, splitErr := net.SplitHostPort(c.TCPClientSettings.Endpoint); splitErr != nil { + err = multierr.Append(splitErr, errInvalidEndpoint) + } + return +} diff --git a/receiver/tcpcheckreceiver/config_test.go b/receiver/tcpcheckreceiver/config_test.go new file mode 100644 index 000000000000..fdae547f167d --- /dev/null +++ b/receiver/tcpcheckreceiver/config_test.go @@ -0,0 +1,105 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/multierr" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" +) + +// check that OTel Collector patterns are implemented +func TestCheckConfig(t *testing.T) { + t.Parallel() + if err := componenttest.CheckConfigStruct(&Config{}); err != nil { + t.Fatal(err) + } +} + +// test the validate function for config +func TestValidate(t *testing.T) { + t.Parallel() + testCases := []struct { + desc string + cfg *Config + expectedErr error + }{ + { + desc: "missing endpoint", + cfg: &Config{ + TCPClientSettings: configtcp.TCPClientSettings{ + Endpoint: "", + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: multierr.Combine(errMissingEndpoint), + }, + { + desc: "invalid endpoint", + cfg: &Config{ + TCPClientSettings: configtcp.TCPClientSettings{ + Endpoint: "badendpoint . cuz spaces:443", + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: multierr.Combine(errInvalidEndpoint), + }, + { + desc: "no error", + cfg: &Config{ + TCPClientSettings: configtcp.TCPClientSettings{ + Endpoint: "localhost:8080", + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: error(nil), + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + actualErr := tc.cfg.Validate() + if tc.expectedErr != nil { + require.EqualError(t, actualErr, tc.expectedErr.Error()) + } else { + require.NoError(t, actualErr) + } + }) + } +} + +func TestLoadConfig(t *testing.T) { + // load test config + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + rcvrs, err := cm.Sub("receivers") + require.NoError(t, err) + tcpconf, err := rcvrs.Sub("tcpcheck") + require.NoError(t, err) + // unmarshal to receiver config + actualConfig, ok := NewFactory().CreateDefaultConfig().(*Config) + require.True(t, ok) + require.NoError(t, tcpconf.Unmarshal(actualConfig)) + + // set expected config + expectedConfig, ok := NewFactory().CreateDefaultConfig().(*Config) + require.True(t, ok) + + expectedConfig.ControllerConfig = scraperhelper.ControllerConfig{ + InitialDelay: time.Second, + CollectionInterval: 60 * time.Second, + } + expectedConfig.TCPClientSettings = configtcp.TCPClientSettings{ + Endpoint: "localhost:80", + Timeout: 10 * time.Second, + } + require.Equal(t, expectedConfig, actualConfig) +} diff --git a/receiver/tcpcheckreceiver/doc.go b/receiver/tcpcheckreceiver/doc.go new file mode 100644 index 000000000000..e07c7a302f24 --- /dev/null +++ b/receiver/tcpcheckreceiver/doc.go @@ -0,0 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +//go:generate mdatagen metadata.yaml diff --git a/receiver/tcpcheckreceiver/documentation.md b/receiver/tcpcheckreceiver/documentation.md new file mode 100644 index 000000000000..4731405d0def --- /dev/null +++ b/receiver/tcpcheckreceiver/documentation.md @@ -0,0 +1,55 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# tcpcheck + +## Default Metrics + +The following metrics are emitted by default. Each of them can be disabled by applying the following configuration: + +```yaml +metrics: + : + enabled: false +``` + +### tcpcheck.duration + +Measures the duration of TCP connection. + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| ms | Gauge | Int | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| tcp.endpoint | Full TCO endpoint | Any Str | + +### tcpcheck.error + +Records errors occurring during TCP check. + +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | +| ---- | ----------- | ---------- | ----------------------- | --------- | +| {error} | Sum | Int | Cumulative | false | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| error.message | Error message recorded during check | Any Str | + +### tcpcheck.status + +1 if the TCP client successfully connected, otherwise 0. + +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | +| ---- | ----------- | ---------- | ----------------------- | --------- | +| 1 | Sum | Int | Cumulative | false | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| tcp.endpoint | Full TCO endpoint | Any Str | diff --git a/receiver/tcpcheckreceiver/factory.go b/receiver/tcpcheckreceiver/factory.go new file mode 100644 index 000000000000..e196b74f87b3 --- /dev/null +++ b/receiver/tcpcheckreceiver/factory.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/scraperhelper" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/metadata" +) + +// NewFactory creates a factory for tcpcheckreceiver receiver. +func NewFactory() receiver.Factory { + return receiver.NewFactory( + metadata.Type, + createDefaultConfig, + receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability)) +} + +func createDefaultConfig() component.Config { + cfg := scraperhelper.NewDefaultControllerConfig() + cfg.CollectionInterval = 10 * time.Second + + return &Config{ + ControllerConfig: cfg, + TCPClientSettings: configtcp.TCPClientSettings{ + Timeout: 10 * time.Second, + }, + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + } +} + +func createMetricsReceiver(_ context.Context, params receiver.Settings, rConf component.Config, consumer consumer.Metrics) (receiver.Metrics, error) { + cfg, ok := rConf.(*Config) + if !ok { + return nil, errConfigNotTCPCheck + } + + tcpCheckScraper := newScraper(cfg, params) + scraper, err := scraperhelper.NewScraper(metadata.Type.String(), tcpCheckScraper.scrape, scraperhelper.WithStart(tcpCheckScraper.start)) + if err != nil { + return nil, err + } + + return scraperhelper.NewScraperControllerReceiver(&cfg.ControllerConfig, params, consumer, scraperhelper.AddScraper(scraper)) +} diff --git a/receiver/tcpcheckreceiver/factory_test.go b/receiver/tcpcheckreceiver/factory_test.go new file mode 100644 index 000000000000..a0128a58c377 --- /dev/null +++ b/receiver/tcpcheckreceiver/factory_test.go @@ -0,0 +1,84 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + "go.opentelemetry.io/collector/receiver/scraperhelper" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/metadata" +) + +func TestNewFactory(t *testing.T) { + t.Parallel() + testCases := []struct { + desc string + testFunc func(*testing.T) + }{ + { + desc: "creates a new factory with correct type", + testFunc: func(t *testing.T) { + factory := NewFactory() + require.EqualValues(t, metadata.Type, factory.Type()) + }, + }, + { + desc: "creates a new factory with default config", + testFunc: func(t *testing.T) { + factory := NewFactory() + var expectedCfg component.Config = &Config{ + ControllerConfig: scraperhelper.ControllerConfig{ + CollectionInterval: 10 * time.Second, + InitialDelay: time.Second, + }, + TCPClientSettings: configtcp.TCPClientSettings{ + Timeout: 10 * time.Second, + }, + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + } + + require.Equal(t, expectedCfg, factory.CreateDefaultConfig()) + }, + }, + { + desc: "creates a new factory and CreateMetricsReceiver returns no error", + testFunc: func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + _, err := factory.CreateMetricsReceiver( + context.Background(), + receivertest.NewNopSettings(), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + }, + }, + { + desc: "creates a new factory and CreateMetricsReceiver returns error with incorrect config", + testFunc: func(t *testing.T) { + factory := NewFactory() + _, err := factory.CreateMetricsReceiver( + context.Background(), + receivertest.NewNopSettings(), + nil, + consumertest.NewNop(), + ) + require.ErrorIs(t, err, errConfigNotTCPCheck) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, tc.testFunc) + } +} diff --git a/receiver/tcpcheckreceiver/generated_component_test.go b/receiver/tcpcheckreceiver/generated_component_test.go new file mode 100644 index 000000000000..711845fc9062 --- /dev/null +++ b/receiver/tcpcheckreceiver/generated_component_test.go @@ -0,0 +1,69 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package tcpcheckreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +func TestComponentFactoryType(t *testing.T) { + require.Equal(t, "tcpcheck", NewFactory().Type().String()) +} + +func TestComponentConfigStruct(t *testing.T) { + require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) +} + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + tests := []struct { + name string + createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) + }{ + + { + name: "metrics", + createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateMetricsReceiver(ctx, set, cfg, consumertest.NewNop()) + }, + }, + } + + cm, err := confmaptest.LoadConf("metadata.yaml") + require.NoError(t, err) + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub("tests::config") + require.NoError(t, err) + require.NoError(t, sub.Unmarshal(&cfg)) + + for _, test := range tests { + t.Run(test.name+"-shutdown", func(t *testing.T) { + c, err := test.createFn(context.Background(), receivertest.NewNopSettings(), cfg) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + t.Run(test.name+"-lifecycle", func(t *testing.T) { + firstRcvr, err := test.createFn(context.Background(), receivertest.NewNopSettings(), cfg) + require.NoError(t, err) + host := componenttest.NewNopHost() + require.NoError(t, err) + require.NoError(t, firstRcvr.Start(context.Background(), host)) + require.NoError(t, firstRcvr.Shutdown(context.Background())) + secondRcvr, err := test.createFn(context.Background(), receivertest.NewNopSettings(), cfg) + require.NoError(t, err) + require.NoError(t, secondRcvr.Start(context.Background(), host)) + require.NoError(t, secondRcvr.Shutdown(context.Background())) + }) + } +} diff --git a/receiver/tcpcheckreceiver/generated_package_test.go b/receiver/tcpcheckreceiver/generated_package_test.go new file mode 100644 index 000000000000..d17ea61d82e2 --- /dev/null +++ b/receiver/tcpcheckreceiver/generated_package_test.go @@ -0,0 +1,13 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package tcpcheckreceiver + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/receiver/tcpcheckreceiver/go.mod b/receiver/tcpcheckreceiver/go.mod new file mode 100644 index 000000000000..1a55f93ab5d6 --- /dev/null +++ b/receiver/tcpcheckreceiver/go.mod @@ -0,0 +1,73 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver + +go 1.21.0 + +require ( + github.com/google/go-cmp v0.6.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.106.1 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.106.1 + github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/collector/component v0.106.1 + go.opentelemetry.io/collector/confmap v0.106.1 + go.opentelemetry.io/collector/consumer v0.106.1 + go.opentelemetry.io/collector/consumer/consumertest v0.106.1 + go.opentelemetry.io/collector/extension v0.106.1 + go.opentelemetry.io/collector/extension/auth v0.106.1 + go.opentelemetry.io/collector/pdata v1.12.0 + go.opentelemetry.io/collector/receiver v0.106.1 + go.uber.org/goleak v1.3.0 + go.uber.org/multierr v1.11.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.106.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + go.opentelemetry.io/collector v0.106.1 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.106.1 // indirect + go.opentelemetry.io/collector/consumer/consumerprofiles v0.106.1 // indirect + go.opentelemetry.io/collector/featuregate v1.12.0 // indirect + go.opentelemetry.io/collector/internal/globalgates v0.106.1 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.106.1 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.50.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil => ../../pkg/pdatautil + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest => ../../pkg/pdatatest + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden => ../../pkg/golden diff --git a/receiver/tcpcheckreceiver/go.sum b/receiver/tcpcheckreceiver/go.sum new file mode 100644 index 000000000000..48949fe492b3 --- /dev/null +++ b/receiver/tcpcheckreceiver/go.sum @@ -0,0 +1,156 @@ +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= +github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/collector v0.106.1 h1:ZSQMpFGzFP3RILe1/+K80kCCT2ahn3MKt5e3u0Yz7Rs= +go.opentelemetry.io/collector v0.106.1/go.mod h1:1FabMxWLluLNcC0dq8cI01GaE6t6fYxE6Oxuf8u7AGQ= +go.opentelemetry.io/collector/component v0.106.1 h1:6Xp4tKqnd/JkJDG/C4p1hto+Y5zvk5FwqZIdMCPzZlA= +go.opentelemetry.io/collector/component v0.106.1/go.mod h1:KiVE/5ZayuLlDJTe7mHqHRCn/5LrmF99C7/mKe54mWA= +go.opentelemetry.io/collector/config/configtelemetry v0.106.1 h1:A8nwYnI6brfur5KPFC8GtVX/49pByvVoKSgO4qPXBqg= +go.opentelemetry.io/collector/config/configtelemetry v0.106.1/go.mod h1:WxWKNVAQJg/Io1nA3xLgn/DWLE/W1QOB2+/Js3ACi40= +go.opentelemetry.io/collector/confmap v0.106.1 h1:R7HQIPDRPOEwauBeJUlkT8Elc5f0KQr/s/kQfZi05t0= +go.opentelemetry.io/collector/confmap v0.106.1/go.mod h1:iWdWgvxRYSHdAt5ySgPJq/i6fQMKGNnP5Pt7jOfTXno= +go.opentelemetry.io/collector/consumer v0.106.1 h1:+AQ/Kmoc/g0WP8thwymNkXk1jeWsHDK6XyYfdezcxcc= +go.opentelemetry.io/collector/consumer v0.106.1/go.mod h1:oy6pR/v5o/N9cxsICskyt//bU8k8EG0JeOO1MTDfs5A= +go.opentelemetry.io/collector/consumer/consumerprofiles v0.106.1 h1:uxQjWm2XE7d1OncQDM9tL1ha+otGt1HjoRYIcQRMOfQ= +go.opentelemetry.io/collector/consumer/consumerprofiles v0.106.1/go.mod h1:xQScBf9/PORFaYM6JVPOr7/TcRVEuKcW5XbAXfJByRs= +go.opentelemetry.io/collector/consumer/consumertest v0.106.1 h1:hDdFeVjCLIJ6iLfbiYcV9s+4iboFXbkJ/k3h09qusPw= +go.opentelemetry.io/collector/consumer/consumertest v0.106.1/go.mod h1:WRTYnQ8bYHQrEN6eJZ80oC4pNI7VeDRdsTZI6xs9o5M= +go.opentelemetry.io/collector/extension v0.106.1 h1:HNV2eOxaSDy5gL5KYoPoTLlp6fdeWmuw4paxhi10VUo= +go.opentelemetry.io/collector/extension v0.106.1/go.mod h1:DCOGxD+WnIJiDveKWlvH5WwB9AgqlRUlnKgwhNCLrtQ= +go.opentelemetry.io/collector/extension/auth v0.106.1 h1:IJyY5zeC11H/jlEGVx2bfTW0oVui2k1AjV8DnC6tYhE= +go.opentelemetry.io/collector/extension/auth v0.106.1/go.mod h1:4VcPLz6QTNq3fbY6kH5tvTnF6tWtz4toK2LC1ydpnts= +go.opentelemetry.io/collector/featuregate v1.12.0 h1:l5WbV2vMQd2bL8ubfGrbKNtZaeJRckE12CTHvRe47Tw= +go.opentelemetry.io/collector/featuregate v1.12.0/go.mod h1:PsOINaGgTiFc+Tzu2K/X2jP+Ngmlp7YKGV1XrnBkH7U= +go.opentelemetry.io/collector/internal/globalgates v0.106.1 h1:0NQHTcykmYNDsNKObJ2XocGCv3WUAQZppfP3o6hZUIA= +go.opentelemetry.io/collector/internal/globalgates v0.106.1/go.mod h1:Z5US6O2xkZAtxVSSBnHAPFZwPhFoxlyKLUvS67Vx4gc= +go.opentelemetry.io/collector/pdata v1.12.0 h1:Xx5VK1p4VO0md8MWm2icwC1MnJ7f8EimKItMWw46BmA= +go.opentelemetry.io/collector/pdata v1.12.0/go.mod h1:MYeB0MmMAxeM0hstCFrCqWLzdyeYySim2dG6pDT6nYI= +go.opentelemetry.io/collector/pdata/pprofile v0.106.1 h1:nOLo25YnluNi+zAbU7G24RN86cJ1/EZJc6VEayBlOPo= +go.opentelemetry.io/collector/pdata/pprofile v0.106.1/go.mod h1:chr7lMJIzyXkccnPRkIPhyXtqLZLSReZYhwsggOGEfg= +go.opentelemetry.io/collector/pdata/testdata v0.106.1 h1:JUyLAwKD8o/9jgkBi16zOClxOyY028A7XIXHPV4mNmM= +go.opentelemetry.io/collector/pdata/testdata v0.106.1/go.mod h1:ghdz2RDEzsfigW0J+9oqA4fGmQJ/DJYUhE3vYU6JfhM= +go.opentelemetry.io/collector/receiver v0.106.1 h1:9kDLDJmInnz+AzAV9oV/UGMoc1+oI1pwMMs7+uMiJq4= +go.opentelemetry.io/collector/receiver v0.106.1/go.mod h1:3j9asWz7mqsgE77rPaNhlNQhRwgFhRynf0UEPs/4rkM= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/receiver/tcpcheckreceiver/internal/configtcp/configtcp.go b/receiver/tcpcheckreceiver/internal/configtcp/configtcp.go new file mode 100644 index 000000000000..be38f0900eb2 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/configtcp/configtcp.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package configtcp // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" + +import ( + "net" + "time" + + "go.opentelemetry.io/collector/component" +) + +type TCPClientSettings struct { + // Endpoint is always required + Endpoint string `mapstructure:"endpoint"` + Timeout time.Duration `mapstructure:"timeout"` +} + +type Client struct { + net.Conn + Address string + Timeout time.Duration + DialFunc func(network, address string, timeout time.Duration) (net.Conn, error) +} + +// Dial starts a TCP session. +func (c *Client) Dial(endpoint string, timeout time.Duration) (err error) { + c.Conn, err = c.DialFunc("tcp", endpoint, timeout) + if err != nil { + return err + } + return nil +} + +func (tcs *TCPClientSettings) ToClient(_ component.Host, _ component.TelemetrySettings) (*Client, error) { + return &Client{ + Timeout: tcs.Timeout, + Address: tcs.Endpoint, + DialFunc: net.DialTimeout, + }, nil +} diff --git a/receiver/tcpcheckreceiver/internal/configtcp/configtcp_test.go b/receiver/tcpcheckreceiver/internal/configtcp/configtcp_test.go new file mode 100644 index 000000000000..de431f1f184d --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/configtcp/configtcp_test.go @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package configtcp // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" + +import ( + "fmt" + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/extension" + "go.opentelemetry.io/collector/extension/auth/authtest" +) + +type mockHost struct { + component.Host + ext map[component.ID]extension.Extension +} + +func TestAllSSHClientSettings(t *testing.T) { + host := &mockHost{ + ext: map[component.ID]extension.Extension{ + component.MustNewID("testauth"): &authtest.MockClient{}, + }, + } + + endpoint := "localhost:8080" + timeout := time.Second * 5 + + tests := []struct { + name string + settings TCPClientSettings + shouldError bool + }{ + { + name: "valid_settings_endpoint", + settings: TCPClientSettings{ + Endpoint: endpoint, + Timeout: timeout, + }, + shouldError: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tt := componenttest.NewNopTelemetrySettings() + tt.TracerProvider = nil + + client, err := test.settings.ToClient(host, tt) + if test.shouldError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + assert.EqualValues(t, client.Address, test.settings.Endpoint) + assert.EqualValues(t, client.Timeout, test.settings.Timeout) + }) + } +} + +func Test_Client_Dial(t *testing.T) { + host := &mockHost{ + ext: map[component.ID]extension.Extension{ + component.MustNewID("testauth"): &authtest.MockClient{}, + }, + } + + endpoint := "localhost:8080" + timeout := time.Second * 5 + + tests := []struct { + name string + settings TCPClientSettings + dial func(network, address string, timeout time.Duration) (net.Conn, error) + shouldError bool + }{ + { + name: "dial_sets_client_with_DialFunc", + settings: TCPClientSettings{ + Endpoint: endpoint, + Timeout: timeout, + }, + dial: func(network, address string, timeout time.Duration) (net.Conn, error) { + return nil, nil + }, + shouldError: false, + }, + { + name: "dial_returns_error_of_DialFunc", + settings: TCPClientSettings{ + Endpoint: endpoint, + Timeout: timeout, + }, + dial: func(network, address string, timeout time.Duration) (net.Conn, error) { + return nil, fmt.Errorf("dial") + }, + shouldError: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tt := componenttest.NewNopTelemetrySettings() + tt.TracerProvider = nil + + client, err := test.settings.ToClient(host, tt) + assert.NoError(t, err) + + client.DialFunc = test.dial + + err = client.Dial(endpoint, timeout) + if test.shouldError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/receiver/tcpcheckreceiver/internal/metadata/generated_config.go b/receiver/tcpcheckreceiver/internal/metadata/generated_config.go new file mode 100644 index 000000000000..264617bcc31c --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_config.go @@ -0,0 +1,58 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/confmap" +) + +// MetricConfig provides common config for a particular metric. +type MetricConfig struct { + Enabled bool `mapstructure:"enabled"` + + enabledSetByUser bool +} + +func (ms *MetricConfig) Unmarshal(parser *confmap.Conf) error { + if parser == nil { + return nil + } + err := parser.Unmarshal(ms) + if err != nil { + return err + } + ms.enabledSetByUser = parser.IsSet("enabled") + return nil +} + +// MetricsConfig provides config for tcpcheck metrics. +type MetricsConfig struct { + TcpcheckDuration MetricConfig `mapstructure:"tcpcheck.duration"` + TcpcheckError MetricConfig `mapstructure:"tcpcheck.error"` + TcpcheckStatus MetricConfig `mapstructure:"tcpcheck.status"` +} + +func DefaultMetricsConfig() MetricsConfig { + return MetricsConfig{ + TcpcheckDuration: MetricConfig{ + Enabled: true, + }, + TcpcheckError: MetricConfig{ + Enabled: true, + }, + TcpcheckStatus: MetricConfig{ + Enabled: true, + }, + } +} + +// MetricsBuilderConfig is a configuration for tcpcheck metrics builder. +type MetricsBuilderConfig struct { + Metrics MetricsConfig `mapstructure:"metrics"` +} + +func DefaultMetricsBuilderConfig() MetricsBuilderConfig { + return MetricsBuilderConfig{ + Metrics: DefaultMetricsConfig(), + } +} diff --git a/receiver/tcpcheckreceiver/internal/metadata/generated_config_test.go b/receiver/tcpcheckreceiver/internal/metadata/generated_config_test.go new file mode 100644 index 000000000000..4b3c18ca17f8 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_config_test.go @@ -0,0 +1,63 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/confmap/confmaptest" +) + +func TestMetricsBuilderConfig(t *testing.T) { + tests := []struct { + name string + want MetricsBuilderConfig + }{ + { + name: "default", + want: DefaultMetricsBuilderConfig(), + }, + { + name: "all_set", + want: MetricsBuilderConfig{ + Metrics: MetricsConfig{ + TcpcheckDuration: MetricConfig{Enabled: true}, + TcpcheckError: MetricConfig{Enabled: true}, + TcpcheckStatus: MetricConfig{Enabled: true}, + }, + }, + }, + { + name: "none_set", + want: MetricsBuilderConfig{ + Metrics: MetricsConfig{ + TcpcheckDuration: MetricConfig{Enabled: false}, + TcpcheckError: MetricConfig{Enabled: false}, + TcpcheckStatus: MetricConfig{Enabled: false}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := loadMetricsBuilderConfig(t, tt.name) + if diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{})); diff != "" { + t.Errorf("Config mismatch (-expected +actual):\n%s", diff) + } + }) + } +} + +func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + sub, err := cm.Sub(name) + require.NoError(t, err) + cfg := DefaultMetricsBuilderConfig() + require.NoError(t, sub.Unmarshal(&cfg)) + return cfg +} diff --git a/receiver/tcpcheckreceiver/internal/metadata/generated_metrics.go b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics.go new file mode 100644 index 000000000000..704ec3d78c9a --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics.go @@ -0,0 +1,306 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" +) + +type metricTcpcheckDuration struct { + data pmetric.Metric // data buffer for generated metric. + config MetricConfig // metric config provided by user. + capacity int // max observed number of data points added to the metric. +} + +// init fills tcpcheck.duration metric with initial data. +func (m *metricTcpcheckDuration) init() { + m.data.SetName("tcpcheck.duration") + m.data.SetDescription("Measures the duration of TCP connection.") + m.data.SetUnit("ms") + m.data.SetEmptyGauge() + m.data.Gauge().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricTcpcheckDuration) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, tcpEndpointAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("tcp.endpoint", tcpEndpointAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricTcpcheckDuration) updateCapacity() { + if m.data.Gauge().DataPoints().Len() > m.capacity { + m.capacity = m.data.Gauge().DataPoints().Len() + } +} + +// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points. +func (m *metricTcpcheckDuration) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricTcpcheckDuration(cfg MetricConfig) metricTcpcheckDuration { + m := metricTcpcheckDuration{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + +type metricTcpcheckError struct { + data pmetric.Metric // data buffer for generated metric. + config MetricConfig // metric config provided by user. + capacity int // max observed number of data points added to the metric. +} + +// init fills tcpcheck.error metric with initial data. +func (m *metricTcpcheckError) init() { + m.data.SetName("tcpcheck.error") + m.data.SetDescription("Records errors occurring during TCP check.") + m.data.SetUnit("{error}") + m.data.SetEmptySum() + m.data.Sum().SetIsMonotonic(false) + m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + m.data.Sum().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricTcpcheckError) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, errorMessageAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Sum().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("error.message", errorMessageAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricTcpcheckError) updateCapacity() { + if m.data.Sum().DataPoints().Len() > m.capacity { + m.capacity = m.data.Sum().DataPoints().Len() + } +} + +// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points. +func (m *metricTcpcheckError) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricTcpcheckError(cfg MetricConfig) metricTcpcheckError { + m := metricTcpcheckError{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + +type metricTcpcheckStatus struct { + data pmetric.Metric // data buffer for generated metric. + config MetricConfig // metric config provided by user. + capacity int // max observed number of data points added to the metric. +} + +// init fills tcpcheck.status metric with initial data. +func (m *metricTcpcheckStatus) init() { + m.data.SetName("tcpcheck.status") + m.data.SetDescription("1 if the TCP client successfully connected, otherwise 0.") + m.data.SetUnit("1") + m.data.SetEmptySum() + m.data.Sum().SetIsMonotonic(false) + m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + m.data.Sum().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricTcpcheckStatus) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, tcpEndpointAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Sum().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("tcp.endpoint", tcpEndpointAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricTcpcheckStatus) updateCapacity() { + if m.data.Sum().DataPoints().Len() > m.capacity { + m.capacity = m.data.Sum().DataPoints().Len() + } +} + +// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points. +func (m *metricTcpcheckStatus) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricTcpcheckStatus(cfg MetricConfig) metricTcpcheckStatus { + m := metricTcpcheckStatus{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + +// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations +// required to produce metric representation defined in metadata and user config. +type MetricsBuilder struct { + config MetricsBuilderConfig // config of the metrics builder. + startTime pcommon.Timestamp // start time that will be applied to all recorded data points. + metricsCapacity int // maximum observed number of metrics per resource. + metricsBuffer pmetric.Metrics // accumulates metrics data before emitting. + buildInfo component.BuildInfo // contains version information. + metricTcpcheckDuration metricTcpcheckDuration + metricTcpcheckError metricTcpcheckError + metricTcpcheckStatus metricTcpcheckStatus +} + +// metricBuilderOption applies changes to default metrics builder. +type metricBuilderOption func(*MetricsBuilder) + +// WithStartTime sets startTime on the metrics builder. +func WithStartTime(startTime pcommon.Timestamp) metricBuilderOption { + return func(mb *MetricsBuilder) { + mb.startTime = startTime + } +} + +func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...metricBuilderOption) *MetricsBuilder { + mb := &MetricsBuilder{ + config: mbc, + startTime: pcommon.NewTimestampFromTime(time.Now()), + metricsBuffer: pmetric.NewMetrics(), + buildInfo: settings.BuildInfo, + metricTcpcheckDuration: newMetricTcpcheckDuration(mbc.Metrics.TcpcheckDuration), + metricTcpcheckError: newMetricTcpcheckError(mbc.Metrics.TcpcheckError), + metricTcpcheckStatus: newMetricTcpcheckStatus(mbc.Metrics.TcpcheckStatus), + } + + for _, op := range options { + op(mb) + } + return mb +} + +// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity. +func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) { + if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() { + mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len() + } +} + +// ResourceMetricsOption applies changes to provided resource metrics. +type ResourceMetricsOption func(pmetric.ResourceMetrics) + +// WithResource sets the provided resource on the emitted ResourceMetrics. +// It's recommended to use ResourceBuilder to create the resource. +func WithResource(res pcommon.Resource) ResourceMetricsOption { + return func(rm pmetric.ResourceMetrics) { + res.CopyTo(rm.Resource()) + } +} + +// WithStartTimeOverride overrides start time for all the resource metrics data points. +// This option should be only used if different start time has to be set on metrics coming from different resources. +func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption { + return func(rm pmetric.ResourceMetrics) { + var dps pmetric.NumberDataPointSlice + metrics := rm.ScopeMetrics().At(0).Metrics() + for i := 0; i < metrics.Len(); i++ { + switch metrics.At(i).Type() { + case pmetric.MetricTypeGauge: + dps = metrics.At(i).Gauge().DataPoints() + case pmetric.MetricTypeSum: + dps = metrics.At(i).Sum().DataPoints() + } + for j := 0; j < dps.Len(); j++ { + dps.At(j).SetStartTimestamp(start) + } + } + } +} + +// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for +// recording another set of data points as part of another resource. This function can be helpful when one scraper +// needs to emit metrics from several resources. Otherwise calling this function is not required, +// just `Emit` function can be called instead. +// Resource attributes should be provided as ResourceMetricsOption arguments. +func (mb *MetricsBuilder) EmitForResource(rmo ...ResourceMetricsOption) { + rm := pmetric.NewResourceMetrics() + ils := rm.ScopeMetrics().AppendEmpty() + ils.Scope().SetName("otelcol/tcpcheckreceiver") + ils.Scope().SetVersion(mb.buildInfo.Version) + ils.Metrics().EnsureCapacity(mb.metricsCapacity) + mb.metricTcpcheckDuration.emit(ils.Metrics()) + mb.metricTcpcheckError.emit(ils.Metrics()) + mb.metricTcpcheckStatus.emit(ils.Metrics()) + + for _, op := range rmo { + op(rm) + } + + if ils.Metrics().Len() > 0 { + mb.updateCapacity(rm) + rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty()) + } +} + +// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for +// recording another set of metrics. This function will be responsible for applying all the transformations required to +// produce metric representation defined in metadata and user config, e.g. delta or cumulative. +func (mb *MetricsBuilder) Emit(rmo ...ResourceMetricsOption) pmetric.Metrics { + mb.EmitForResource(rmo...) + metrics := mb.metricsBuffer + mb.metricsBuffer = pmetric.NewMetrics() + return metrics +} + +// RecordTcpcheckDurationDataPoint adds a data point to tcpcheck.duration metric. +func (mb *MetricsBuilder) RecordTcpcheckDurationDataPoint(ts pcommon.Timestamp, val int64, tcpEndpointAttributeValue string) { + mb.metricTcpcheckDuration.recordDataPoint(mb.startTime, ts, val, tcpEndpointAttributeValue) +} + +// RecordTcpcheckErrorDataPoint adds a data point to tcpcheck.error metric. +func (mb *MetricsBuilder) RecordTcpcheckErrorDataPoint(ts pcommon.Timestamp, val int64, errorMessageAttributeValue string) { + mb.metricTcpcheckError.recordDataPoint(mb.startTime, ts, val, errorMessageAttributeValue) +} + +// RecordTcpcheckStatusDataPoint adds a data point to tcpcheck.status metric. +func (mb *MetricsBuilder) RecordTcpcheckStatusDataPoint(ts pcommon.Timestamp, val int64, tcpEndpointAttributeValue string) { + mb.metricTcpcheckStatus.recordDataPoint(mb.startTime, ts, val, tcpEndpointAttributeValue) +} + +// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted, +// and metrics builder should update its startTime and reset it's internal state accordingly. +func (mb *MetricsBuilder) Reset(options ...metricBuilderOption) { + mb.startTime = pcommon.NewTimestampFromTime(time.Now()) + for _, op := range options { + op(mb) + } +} diff --git a/receiver/tcpcheckreceiver/internal/metadata/generated_metrics_test.go b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics_test.go new file mode 100644 index 000000000000..9ab75a0531ce --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics_test.go @@ -0,0 +1,149 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver/receivertest" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" +) + +type testDataSet int + +const ( + testDataSetDefault testDataSet = iota + testDataSetAll + testDataSetNone +) + +func TestMetricsBuilder(t *testing.T) { + tests := []struct { + name string + metricsSet testDataSet + resAttrsSet testDataSet + expectEmpty bool + }{ + { + name: "default", + }, + { + name: "all_set", + metricsSet: testDataSetAll, + resAttrsSet: testDataSetAll, + }, + { + name: "none_set", + metricsSet: testDataSetNone, + resAttrsSet: testDataSetNone, + expectEmpty: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + start := pcommon.Timestamp(1_000_000_000) + ts := pcommon.Timestamp(1_000_001_000) + observedZapCore, observedLogs := observer.New(zap.WarnLevel) + settings := receivertest.NewNopSettings() + settings.Logger = zap.New(observedZapCore) + mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, test.name), settings, WithStartTime(start)) + + expectedWarnings := 0 + + assert.Equal(t, expectedWarnings, observedLogs.Len()) + + defaultMetricsCount := 0 + allMetricsCount := 0 + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordTcpcheckDurationDataPoint(ts, 1, "tcp.endpoint-val") + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordTcpcheckErrorDataPoint(ts, 1, "error.message-val") + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordTcpcheckStatusDataPoint(ts, 1, "tcp.endpoint-val") + + res := pcommon.NewResource() + metrics := mb.Emit(WithResource(res)) + + if test.expectEmpty { + assert.Equal(t, 0, metrics.ResourceMetrics().Len()) + return + } + + assert.Equal(t, 1, metrics.ResourceMetrics().Len()) + rm := metrics.ResourceMetrics().At(0) + assert.Equal(t, res, rm.Resource()) + assert.Equal(t, 1, rm.ScopeMetrics().Len()) + ms := rm.ScopeMetrics().At(0).Metrics() + if test.metricsSet == testDataSetDefault { + assert.Equal(t, defaultMetricsCount, ms.Len()) + } + if test.metricsSet == testDataSetAll { + assert.Equal(t, allMetricsCount, ms.Len()) + } + validatedMetrics := make(map[string]bool) + for i := 0; i < ms.Len(); i++ { + switch ms.At(i).Name() { + case "tcpcheck.duration": + assert.False(t, validatedMetrics["tcpcheck.duration"], "Found a duplicate in the metrics slice: tcpcheck.duration") + validatedMetrics["tcpcheck.duration"] = true + assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len()) + assert.Equal(t, "Measures the duration of TCP connection.", ms.At(i).Description()) + assert.Equal(t, "ms", ms.At(i).Unit()) + dp := ms.At(i).Gauge().DataPoints().At(0) + assert.Equal(t, start, dp.StartTimestamp()) + assert.Equal(t, ts, dp.Timestamp()) + assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) + assert.Equal(t, int64(1), dp.IntValue()) + attrVal, ok := dp.Attributes().Get("tcp.endpoint") + assert.True(t, ok) + assert.EqualValues(t, "tcp.endpoint-val", attrVal.Str()) + case "tcpcheck.error": + assert.False(t, validatedMetrics["tcpcheck.error"], "Found a duplicate in the metrics slice: tcpcheck.error") + validatedMetrics["tcpcheck.error"] = true + assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len()) + assert.Equal(t, "Records errors occurring during TCP check.", ms.At(i).Description()) + assert.Equal(t, "{error}", ms.At(i).Unit()) + assert.Equal(t, false, ms.At(i).Sum().IsMonotonic()) + assert.Equal(t, pmetric.AggregationTemporalityCumulative, ms.At(i).Sum().AggregationTemporality()) + dp := ms.At(i).Sum().DataPoints().At(0) + assert.Equal(t, start, dp.StartTimestamp()) + assert.Equal(t, ts, dp.Timestamp()) + assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) + assert.Equal(t, int64(1), dp.IntValue()) + attrVal, ok := dp.Attributes().Get("error.message") + assert.True(t, ok) + assert.EqualValues(t, "error.message-val", attrVal.Str()) + case "tcpcheck.status": + assert.False(t, validatedMetrics["tcpcheck.status"], "Found a duplicate in the metrics slice: tcpcheck.status") + validatedMetrics["tcpcheck.status"] = true + assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len()) + assert.Equal(t, "1 if the TCP client successfully connected, otherwise 0.", ms.At(i).Description()) + assert.Equal(t, "1", ms.At(i).Unit()) + assert.Equal(t, false, ms.At(i).Sum().IsMonotonic()) + assert.Equal(t, pmetric.AggregationTemporalityCumulative, ms.At(i).Sum().AggregationTemporality()) + dp := ms.At(i).Sum().DataPoints().At(0) + assert.Equal(t, start, dp.StartTimestamp()) + assert.Equal(t, ts, dp.Timestamp()) + assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) + assert.Equal(t, int64(1), dp.IntValue()) + attrVal, ok := dp.Attributes().Get("tcp.endpoint") + assert.True(t, ok) + assert.EqualValues(t, "tcp.endpoint-val", attrVal.Str()) + } + } + }) + } +} diff --git a/receiver/tcpcheckreceiver/internal/metadata/generated_status.go b/receiver/tcpcheckreceiver/internal/metadata/generated_status.go new file mode 100644 index 000000000000..22a9a5004c11 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_status.go @@ -0,0 +1,15 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/component" +) + +var ( + Type = component.MustNewType("tcpcheck") +) + +const ( + MetricsStability = component.StabilityLevelAlpha +) diff --git a/receiver/tcpcheckreceiver/internal/metadata/testdata/config.yaml b/receiver/tcpcheckreceiver/internal/metadata/testdata/config.yaml new file mode 100644 index 000000000000..ed8c540f430d --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/testdata/config.yaml @@ -0,0 +1,17 @@ +default: +all_set: + metrics: + tcpcheck.duration: + enabled: true + tcpcheck.error: + enabled: true + tcpcheck.status: + enabled: true +none_set: + metrics: + tcpcheck.duration: + enabled: false + tcpcheck.error: + enabled: false + tcpcheck.status: + enabled: false diff --git a/receiver/tcpcheckreceiver/metadata.yaml b/receiver/tcpcheckreceiver/metadata.yaml new file mode 100644 index 000000000000..4e2959f8b272 --- /dev/null +++ b/receiver/tcpcheckreceiver/metadata.yaml @@ -0,0 +1,47 @@ +type: tcpcheck +scope_name: otelcol/tcpcheckreceiver + +status: + class: receiver + stability: + alpha: [metrics] + distributions: [contrib] + codeowners: + active: [yanfeng1992] + +resource_attributes: + +attributes: + error.message: + description: Error message recorded during check + type: string + tcp.endpoint: + description: Full TCO endpoint + type: string + +metrics: + tcpcheck.status: + description: 1 if the TCP client successfully connected, otherwise 0. + enabled: true + sum: + value_type: int + aggregation_temporality: cumulative + monotonic: false + unit: "1" + attributes: [tcp.endpoint] + tcpcheck.duration: + description: Measures the duration of TCP connection. + enabled: true + gauge: + value_type: int + unit: ms + attributes: [tcp.endpoint] + tcpcheck.error: + description: Records errors occurring during TCP check. + enabled: true + sum: + value_type: int + aggregation_temporality: cumulative + monotonic: false + unit: "{error}" + attributes: [error.message] \ No newline at end of file diff --git a/receiver/tcpcheckreceiver/scraper.go b/receiver/tcpcheckreceiver/scraper.go new file mode 100644 index 000000000000..bf2632419dcb --- /dev/null +++ b/receiver/tcpcheckreceiver/scraper.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "context" + "errors" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/configtcp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/metadata" +) + +var errClientNotInit = errors.New("client not initialized") + +type tcpCheckScraper struct { + *configtcp.Client + *Config + settings component.TelemetrySettings + mb *metadata.MetricsBuilder +} + +// start the scraper by creating a new TCP Client on the scraper +func (s *tcpCheckScraper) start(_ context.Context, host component.Host) error { + var err error + s.Client, err = s.Config.ToClient(host, s.settings) + return err +} + +func (s *tcpCheckScraper) scrapeTCP(now pcommon.Timestamp) error { + var success int64 + + start := time.Now() + err := s.Client.Dial(s.Config.TCPClientSettings.Endpoint, s.Config.TCPClientSettings.Timeout) + if err == nil { + success = 1 + } + s.mb.RecordTcpcheckDurationDataPoint(now, time.Since(start).Milliseconds(), s.Config.TCPClientSettings.Endpoint) + s.mb.RecordTcpcheckStatusDataPoint(now, success, s.Config.TCPClientSettings.Endpoint) + return err +} + +// timeout chooses the shorter between a given deadline and timeout +func timeout(deadline time.Time, timeout time.Duration) time.Duration { + timeToDeadline := time.Until(deadline) + if timeToDeadline < timeout { + return timeToDeadline + } + return timeout +} + +// scrape connects to the endpoint and produces metrics based on the response. +func (s *tcpCheckScraper) scrape(ctx context.Context) (_ pmetric.Metrics, err error) { + var ( + to time.Duration + ) + // check cancellation + select { + case <-ctx.Done(): + return pmetric.NewMetrics(), ctx.Err() + default: + } + + cleanup := func() { + s.Client.Close() + } + + // if the context carries a shorter deadline then timeout that quickly + deadline, ok := ctx.Deadline() + if ok { + to = timeout(deadline, s.Client.Timeout) + s.Client.Timeout = to + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + now := pcommon.NewTimestampFromTime(time.Now()) + if s.Client == nil { + return pmetric.NewMetrics(), errClientNotInit + } + + if err = s.scrapeTCP(now); err != nil { + s.mb.RecordTcpcheckErrorDataPoint(now, int64(1), err.Error()) + } else { + go func() { + <-ctx.Done() + cleanup() + }() + } + + return s.mb.Emit(), nil +} + +func newScraper(conf *Config, settings receiver.Settings) *tcpCheckScraper { + return &tcpCheckScraper{ + Config: conf, + settings: settings.TelemetrySettings, + mb: metadata.NewMetricsBuilder(conf.MetricsBuilderConfig, settings), + } +} diff --git a/receiver/tcpcheckreceiver/scraper_test.go b/receiver/tcpcheckreceiver/scraper_test.go new file mode 100644 index 000000000000..a9db4376697a --- /dev/null +++ b/receiver/tcpcheckreceiver/scraper_test.go @@ -0,0 +1,141 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "bufio" + "context" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/receiver/receivertest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" +) + +type Server struct { + host string + port string + listener net.Listener +} + +func newTCPServer(host string, port string) *Server { + return &Server{ + host: host, + port: port, + } +} + +func (server *Server) runTCPServer(t *testing.T) string { + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", server.host, server.port)) + require.NoError(t, err) + server.listener = listener + go func() { + conn, err := listener.Accept() + require.NoError(t, err) + go handleRequest(conn) + }() + return listener.Addr().String() +} + +func (server *Server) shutdown() { + server.listener.Close() +} + +func handleRequest(conn net.Conn) { + reader := bufio.NewReader(conn) + for { + message, err := reader.ReadString('\n') + if err != nil { + conn.Close() + return + } + fmt.Printf("Message incoming: %s", string(message)) + conn.Write([]byte("Message received.\n")) + conn.Close() + } +} + +func TestTimeout(t *testing.T) { + testCases := []struct { + name string + deadline time.Time + timeout time.Duration + want time.Duration + }{ + { + name: "timeout is shorter", + deadline: time.Now().Add(time.Second), + timeout: time.Second * 2, + want: time.Second, + }, + { + name: "deadline is shorter", + deadline: time.Now().Add(time.Second * 2), + timeout: time.Second, + want: time.Second, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + to := timeout(tc.deadline, tc.timeout) + if to < (tc.want-10*time.Millisecond) || to > tc.want { + t.Fatalf("wanted time within 10 milliseconds: %s, got: %s", time.Second, to) + } + }) + } +} + +func TestScraper(t *testing.T) { + s := newTCPServer("127.0.0.1", "8080") + endpoint := s.runTCPServer(t) + defer s.shutdown() + + testCases := []struct { + name string + filename string + endpoint string + }{ + { + name: "metrics_golden", + filename: "metrics_golden.yaml", + endpoint: endpoint, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + expectedFile := filepath.Join("testdata", "expected_metrics", tc.filename) + expectedMetrics, err := golden.ReadMetrics(expectedFile) + require.NoError(t, err) + f := NewFactory() + cfg := f.CreateDefaultConfig().(*Config) + cfg.ControllerConfig.CollectionInterval = 100 * time.Millisecond + cfg.Endpoint = tc.endpoint + + settings := receivertest.NewNopSettings() + + scraper := newScraper(cfg, settings) + require.NoError(t, scraper.start(context.Background(), componenttest.NewNopHost()), "failed starting scraper") + + actualMetrics, err := scraper.scrape(context.Background()) + require.NoError(t, err, "failed scrape") + require.NoError( + t, + pmetrictest.CompareMetrics( + expectedMetrics, + actualMetrics, + pmetrictest.IgnoreMetricValues("tcpcheck.duration"), + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + ), + ) + }) + } +} diff --git a/receiver/tcpcheckreceiver/testdata/config.yaml b/receiver/tcpcheckreceiver/testdata/config.yaml new file mode 100644 index 000000000000..0da63bc004a7 --- /dev/null +++ b/receiver/tcpcheckreceiver/testdata/config.yaml @@ -0,0 +1,16 @@ +receivers: + tcpcheck: + endpoint: localhost:80 + collection_interval: 60s +exporters: + # NOTE: Prior to v0.86.0 use `logging` instead of `debug`. + debug: + verbosity: detailed +service: + pipelines: + traces: + receivers: [tcpcheck] + exporters: [debug] + telemetry: + logs: + level: debug \ No newline at end of file diff --git a/receiver/tcpcheckreceiver/testdata/expected_metrics/metrics_golden.yaml b/receiver/tcpcheckreceiver/testdata/expected_metrics/metrics_golden.yaml new file mode 100644 index 000000000000..89ab6714ae66 --- /dev/null +++ b/receiver/tcpcheckreceiver/testdata/expected_metrics/metrics_golden.yaml @@ -0,0 +1,32 @@ +resourceMetrics: + - resource: {} + scopeMetrics: + - metrics: + - description: Measures the duration of TCP connection. + gauge: + dataPoints: + - asInt: "36" + attributes: + - key: tcp.endpoint + value: + stringValue: 127.0.0.1:8080 + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + name: tcpcheck.duration + unit: ms + - description: 1 if the TCP client successfully connected, otherwise 0. + name: tcpcheck.status + sum: + aggregationTemporality: 2 + dataPoints: + - asInt: "1" + attributes: + - key: tcp.endpoint + value: + stringValue: 127.0.0.1:8080 + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + unit: "1" + scope: + name: otelcol/tcpcheckreceiver + version: latest diff --git a/versions.yaml b/versions.yaml index eb7bb5a380ac..451f724a2d75 100644 --- a/versions.yaml +++ b/versions.yaml @@ -266,6 +266,7 @@ module-sets: - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/syslogreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcplogreceiver + - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/udplogreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/vcenterreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/wavefrontreceiver