From f0c92844cb37982892c7fc42e2dbafc82f5a1bed Mon Sep 17 00:00:00 2001 From: huangyanfeng Date: Wed, 7 Aug 2024 14:57:52 +0800 Subject: [PATCH] [receiver/tcpcheck] New receiver Signed-off-by: huangyanfeng --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/feature_request.yaml | 1 + .github/ISSUE_TEMPLATE/other.yaml | 1 + .github/ISSUE_TEMPLATE/unmaintained.yaml | 1 + receiver/tcpcheckreceiver/Makefile | 1 + receiver/tcpcheckreceiver/README.md | 44 +++ receiver/tcpcheckreceiver/config.go | 40 +++ receiver/tcpcheckreceiver/config_test.go | 105 ++++++ receiver/tcpcheckreceiver/doc.go | 6 + receiver/tcpcheckreceiver/documentation.md | 55 ++++ receiver/tcpcheckreceiver/factory.go | 53 +++ receiver/tcpcheckreceiver/factory_test.go | 84 +++++ .../generated_component_test.go | 69 ++++ .../generated_package_test.go | 13 + receiver/tcpcheckreceiver/go.mod | 66 ++++ receiver/tcpcheckreceiver/go.sum | 142 ++++++++ .../internal/configtcp/configtcp.go | 44 +++ .../internal/configtcp/configtcp_test.go | 62 ++++ .../internal/metadata/generated_config.go | 58 ++++ .../metadata/generated_config_test.go | 63 ++++ .../internal/metadata/generated_metrics.go | 304 ++++++++++++++++++ .../metadata/generated_metrics_test.go | 147 +++++++++ .../internal/metadata/generated_status.go | 15 + .../internal/metadata/testdata/config.yaml | 17 + receiver/tcpcheckreceiver/metadata.yaml | 42 +++ receiver/tcpcheckreceiver/scraper.go | 107 ++++++ receiver/tcpcheckreceiver/scraper_test.go | 141 ++++++++ .../tcpcheckreceiver/testdata/config.yaml | 16 + .../expected_metrics/metrics_golden.yaml | 31 ++ 29 files changed, 1729 insertions(+) create mode 100644 receiver/tcpcheckreceiver/Makefile create mode 100644 receiver/tcpcheckreceiver/README.md create mode 100644 receiver/tcpcheckreceiver/config.go create mode 100644 receiver/tcpcheckreceiver/config_test.go create mode 100644 receiver/tcpcheckreceiver/doc.go create mode 100644 receiver/tcpcheckreceiver/documentation.md create mode 100644 receiver/tcpcheckreceiver/factory.go create mode 100644 receiver/tcpcheckreceiver/factory_test.go create mode 100644 receiver/tcpcheckreceiver/generated_component_test.go create mode 100644 receiver/tcpcheckreceiver/generated_package_test.go create mode 100644 receiver/tcpcheckreceiver/go.mod create mode 100644 receiver/tcpcheckreceiver/go.sum create mode 100644 receiver/tcpcheckreceiver/internal/configtcp/configtcp.go create mode 100644 receiver/tcpcheckreceiver/internal/configtcp/configtcp_test.go create mode 100644 receiver/tcpcheckreceiver/internal/metadata/generated_config.go create mode 100644 receiver/tcpcheckreceiver/internal/metadata/generated_config_test.go create mode 100644 receiver/tcpcheckreceiver/internal/metadata/generated_metrics.go create mode 100644 receiver/tcpcheckreceiver/internal/metadata/generated_metrics_test.go create mode 100644 receiver/tcpcheckreceiver/internal/metadata/generated_status.go create mode 100644 receiver/tcpcheckreceiver/internal/metadata/testdata/config.yaml create mode 100644 receiver/tcpcheckreceiver/metadata.yaml create mode 100644 receiver/tcpcheckreceiver/scraper.go create mode 100644 receiver/tcpcheckreceiver/scraper_test.go create mode 100644 receiver/tcpcheckreceiver/testdata/config.yaml create mode 100644 receiver/tcpcheckreceiver/testdata/expected_metrics/metrics_golden.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 359cb4a6b06a..95247cf1b159 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -290,6 +290,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 5203d2e7659b..33e61f509e1c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -284,6 +284,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml index 11380d858eaa..1cd2bb54bd7e 100644 --- a/.github/ISSUE_TEMPLATE/other.yaml +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -284,6 +284,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog diff --git a/.github/ISSUE_TEMPLATE/unmaintained.yaml b/.github/ISSUE_TEMPLATE/unmaintained.yaml index ef4baccf3a5c..a48ca4889305 100644 --- a/.github/ISSUE_TEMPLATE/unmaintained.yaml +++ b/.github/ISSUE_TEMPLATE/unmaintained.yaml @@ -289,6 +289,7 @@ body: - receiver/sshcheck - receiver/statsd - receiver/syslog + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog 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..c38c2f79aeb1 --- /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 | +| ---- | ----------- | ---------- | +| ns | 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 | true | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| tcp.endpoint | Full TCO endpoint | Any Str | + +### tcpcheck.status + +1 if the TCP client successfully connected, otherwise 0. + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| 1 | Gauge | Int | + +#### 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..3aa6de0c74c8 --- /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, 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..1a6bf029ab8b --- /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.CreateMetrics( + 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.CreateMetrics( + 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..871877ce6fb9 --- /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.CreateMetrics(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..db25010049a2 --- /dev/null +++ b/receiver/tcpcheckreceiver/go.mod @@ -0,0 +1,66 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver + +go 1.22.0 + +require ( + github.com/google/go-cmp v0.6.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.111.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.111.0 + github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/collector/component v0.111.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/config/confignet v1.17.0 + go.opentelemetry.io/collector/confmap v1.17.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/consumer v0.111.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/consumer/consumertest v0.111.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/extension v0.111.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/extension/auth v0.111.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/pdata v1.17.1-0.20241008154146-ea48c09c31ae + go.opentelemetry.io/collector/receiver v0.111.1-0.20241008154146-ea48c09c31ae + go.uber.org/goleak v1.3.0 + go.uber.org/multierr v1.11.0 + go.uber.org/zap v1.27.0 +) + +require ( + 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.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.6.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/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.111.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.111.0 // indirect + go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0 // indirect + go.opentelemetry.io/collector/internal/globalsignal v0.111.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.111.0 // indirect + go.opentelemetry.io/collector/pipeline v0.111.0 // indirect + go.opentelemetry.io/collector/receiver/receiverprofiles v0.111.0 // indirect + go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect + go.opentelemetry.io/otel/trace v1.30.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // 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..b8af853ed096 --- /dev/null +++ b/receiver/tcpcheckreceiver/go.sum @@ -0,0 +1,142 @@ +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.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/component v0.111.1-0.20241008154146-ea48c09c31ae h1:dXAMqXGJp1vWG7qwS/2sjIyJgmyOSfEOm6Gcmkzp1cQ= +go.opentelemetry.io/collector/component v0.111.1-0.20241008154146-ea48c09c31ae/go.mod h1:iWUfPxpVwZhkI4v3/Gh5wt4iKyJn4lriPFAug8iLXno= +go.opentelemetry.io/collector/config/confignet v1.17.0 h1:cBmDdiPuIVrHiecgCKyXhRYmDOz9Do5IM7O1JhbB3es= +go.opentelemetry.io/collector/config/confignet v1.17.0/go.mod h1:o3v4joAEjvLwntqexg5ixMqRrU1+Vst+jWuCUaBNgOg= +go.opentelemetry.io/collector/config/configtelemetry v0.111.0 h1:Q3TJRM2A3FIDjIvzWa3uFArsdFN0I/0GzcWynHjC+oY= +go.opentelemetry.io/collector/config/configtelemetry v0.111.0/go.mod h1:R0MBUxjSMVMIhljuDHWIygzzJWQyZHXXWIgQNxcFwhc= +go.opentelemetry.io/collector/confmap v1.17.1-0.20241008154146-ea48c09c31ae h1:mYgomPNE0dq1SU1OVnMb/Z8Xbj89jBXnjJexz9M71t0= +go.opentelemetry.io/collector/confmap v1.17.1-0.20241008154146-ea48c09c31ae/go.mod h1:GrIZ12P/9DPOuTpe2PIS51a0P/ZM6iKtByVee1Uf3+k= +go.opentelemetry.io/collector/consumer v0.111.1-0.20241008154146-ea48c09c31ae h1:ps86XqQ6kviggnQ7OnJIHDIoaYYinRGtfKKzcvSuplc= +go.opentelemetry.io/collector/consumer v0.111.1-0.20241008154146-ea48c09c31ae/go.mod h1:G6pdEDo5A92RY9sBTVbdbuv/nFrVJbDKSO6uRzzgaP8= +go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0 h1:w9kGdTaXdwD/ZtbxVOvuYQEFKBX3THQgEz/enQnMt9s= +go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0/go.mod h1:Ebt1jDdrQb3G2sNHrWHNr5wS3UJ9k3h8LHCqUPTbxLY= +go.opentelemetry.io/collector/consumer/consumertest v0.111.1-0.20241008154146-ea48c09c31ae h1:HFj6D19fJYm3KV8QidQmMApmLjzoNkzh8El5OkTGySo= +go.opentelemetry.io/collector/consumer/consumertest v0.111.1-0.20241008154146-ea48c09c31ae/go.mod h1:UDZRrSgaFAwWO6I34fj0KjabVAuBCAnmizsleyIe3I4= +go.opentelemetry.io/collector/extension v0.111.1-0.20241008154146-ea48c09c31ae h1:akinGdBSY01CI/Jme2QKbYnwqRgLLWKx9U+93KLqZI0= +go.opentelemetry.io/collector/extension v0.111.1-0.20241008154146-ea48c09c31ae/go.mod h1:nX8HLUqkaWKJ4gCpPdba/x4+G3emnnjzdmpF6LgtZzg= +go.opentelemetry.io/collector/extension/auth v0.111.1-0.20241008154146-ea48c09c31ae h1:+fhQtSMttCey4XozohoN3BF+I5s+UVyAFIEO16uWLgw= +go.opentelemetry.io/collector/extension/auth v0.111.1-0.20241008154146-ea48c09c31ae/go.mod h1:67BknwUsRrFcpJln0gyrWbmR0kZbUy198Ke65CyeJx4= +go.opentelemetry.io/collector/internal/globalsignal v0.111.0 h1:oq0nSD+7K2Q1Fx5d3s6lPRdKZeTL0FEg4sIaR7ZJzIc= +go.opentelemetry.io/collector/internal/globalsignal v0.111.0/go.mod h1:GqMXodPWOxK5uqpX8MaMXC2389y2XJTa5nPwf8FYDK8= +go.opentelemetry.io/collector/pdata v1.17.1-0.20241008154146-ea48c09c31ae h1:PcwZe1RD8tC4SZExhf0f5HqK+ZuWGsowHaBBU4PiUv0= +go.opentelemetry.io/collector/pdata v1.17.1-0.20241008154146-ea48c09c31ae/go.mod h1:Ox1YVLe87cZDB/TL30i4SUz1cA5s6AM6SpFMfY61ICs= +go.opentelemetry.io/collector/pdata/pprofile v0.111.0 h1:4if6rItcX8a6X4bIh6lwQnlE+ncKXQaIim7F5O7ZA58= +go.opentelemetry.io/collector/pdata/pprofile v0.111.0/go.mod h1:iBwrNFB6za1qspy46ZE41H3MmcxUogn2AuYbrWdoMd8= +go.opentelemetry.io/collector/pdata/testdata v0.111.0 h1:Fqyf1NJ0az+HbsvKSCNw8pfa1Y6c4FhZwlMK4ZulG0s= +go.opentelemetry.io/collector/pdata/testdata v0.111.0/go.mod h1:7SypOzbVtRsCkns6Yxa4GztnkVGkk7b9fW24Ow75q5s= +go.opentelemetry.io/collector/pipeline v0.111.0 h1:qENDGvWWnDXguEfmj8eO+5kr8Y6XFKytU5SuMinz3Ls= +go.opentelemetry.io/collector/pipeline v0.111.0/go.mod h1:ZZMU3019geEU283rTW5M/LkcqLqHp/YI2Nl6/Vp68PQ= +go.opentelemetry.io/collector/receiver v0.111.1-0.20241008154146-ea48c09c31ae h1:/CdVIXj9tjTU+5U2D2O/w5T7vYbWF+D6mW9J09GWqis= +go.opentelemetry.io/collector/receiver v0.111.1-0.20241008154146-ea48c09c31ae/go.mod h1:IB7XEWVIprzSO5Y5RoyPLx3I4ntVGvhOWvoHN1n24IY= +go.opentelemetry.io/collector/receiver/receiverprofiles v0.111.0 h1:oYLAdGMQQR7gB6wVkbV0G4EMsrmiOs3O0qf3hh/3avw= +go.opentelemetry.io/collector/receiver/receiverprofiles v0.111.0/go.mod h1:M/OfdEGnvyB+fSTSW4RPKj5N06FXL8oKSIf60FlrKmM= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= +go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= +go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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..facdc97da0ec --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/configtcp/configtcp.go @@ -0,0 +1,44 @@ +// 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 ( + "context" + "net" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confignet" +) + +type TCPClientSettings struct { + // Endpoint is always required + Endpoint string `mapstructure:"endpoint"` + Timeout time.Duration `mapstructure:"timeout"` +} + +type Client struct { + net.Conn + TCPAddrConfig confignet.TCPAddrConfig +} + +// Dial starts a TCP session. +func (c *Client) Dial() (err error) { + c.Conn, err = c.TCPAddrConfig.Dial(context.Background()) + if err != nil { + return err + } + return nil +} + +func (tcs *TCPClientSettings) ToClient(_ component.Host, _ component.TelemetrySettings) (*Client, error) { + return &Client{ + TCPAddrConfig: confignet.TCPAddrConfig{ + Endpoint: tcs.Endpoint, + DialerConfig: confignet.DialerConfig{ + Timeout: tcs.Timeout, + }, + }, + }, 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..0cee34af3107 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/configtcp/configtcp_test.go @@ -0,0 +1,62 @@ +// 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 ( + "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 TestAllTCPClientSettings(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.TCPAddrConfig.Endpoint, test.settings.Endpoint) + assert.EqualValues(t, client.TCPAddrConfig.DialerConfig.Timeout, test.settings.Timeout) + }) + } +} 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..0d35ffb02238 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics.go @@ -0,0 +1,304 @@ +// 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("ns") + 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(true) + 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, 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 *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.SetEmptyGauge() + m.data.Gauge().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.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 *metricTcpcheckStatus) 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 *metricTcpcheckStatus) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().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, tcpEndpointAttributeValue string) { + mb.metricTcpcheckError.recordDataPoint(mb.startTime, ts, val, tcpEndpointAttributeValue) +} + +// 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..969a68a7ac04 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics_test.go @@ -0,0 +1,147 @@ +// 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, "tcp.endpoint-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, "ns", 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, true, 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()) + 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.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().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()) + 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()) + } + } + }) + } +} 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..bb76dc09bb5b --- /dev/null +++ b/receiver/tcpcheckreceiver/metadata.yaml @@ -0,0 +1,42 @@ +type: tcpcheck +scope_name: otelcol/tcpcheckreceiver + +status: + class: receiver + stability: + alpha: [metrics] + distributions: [contrib] + codeowners: + active: [yanfeng1992] + +resource_attributes: + +attributes: + tcp.endpoint: + description: Full TCO endpoint + type: string + +metrics: + tcpcheck.status: + description: 1 if the TCP client successfully connected, otherwise 0. + enabled: true + gauge: + value_type: int + unit: "1" + attributes: [tcp.endpoint] + tcpcheck.duration: + description: Measures the duration of TCP connection. + enabled: true + gauge: + value_type: int + unit: ns + attributes: [tcp.endpoint] + tcpcheck.error: + description: Records errors occurring during TCP check. + enabled: true + sum: + value_type: int + aggregation_temporality: cumulative + monotonic: true + unit: "{error}" + attributes: [tcp.endpoint] \ No newline at end of file diff --git a/receiver/tcpcheckreceiver/scraper.go b/receiver/tcpcheckreceiver/scraper.go new file mode 100644 index 000000000000..271dc343b041 --- /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() + if err == nil { + success = 1 + } + s.mb.RecordTcpcheckDurationDataPoint(now, time.Since(start).Nanoseconds(), 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.TCPAddrConfig.DialerConfig.Timeout) + s.Client.TCPAddrConfig.DialerConfig.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..477fa4bf455b --- /dev/null +++ b/receiver/tcpcheckreceiver/testdata/expected_metrics/metrics_golden.yaml @@ -0,0 +1,31 @@ +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: ns + - description: 1 if the TCP client successfully connected, otherwise 0. + name: tcpcheck.status + gauge: + 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