diff --git a/.chloggen/tcp-checker.yaml b/.chloggen/tcp-checker.yaml new file mode 100644 index 000000000000..96753d669acc --- /dev/null +++ b/.chloggen/tcp-checker.yaml @@ -0,0 +1,27 @@ +#Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: new_component + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: tcpcheckreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Introducing new component tcpcheck receiver + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [34414] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d4a73ec05d4a..8530d1e0b553 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -299,6 +299,7 @@ receiver/sshcheckreceiver/ @open-telemetry receiver/statsdreceiver/ @open-telemetry/collector-contrib-approvers @jmacd @dmitryax receiver/syslogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski @andrzej-stencel receiver/systemdreceiver/ @open-telemetry/collector-contrib-approvers @Hemansh31 @atoulme +receiver/tcpcheckreceiver/ @open-telemetry/collector-contrib-approvers @atoulme @michael-burt @chengchuanpeng receiver/tcplogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski receiver/tlscheckreceiver/ @open-telemetry/collector-contrib-approvers @atoulme @michael-burt receiver/udplogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index e3312d93d0a7..09f1c5dab590 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -304,6 +304,7 @@ body: - receiver/statsd - receiver/syslog - receiver/systemd + - 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 d076b2042f28..c7bf2d44a594 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -298,6 +298,7 @@ body: - receiver/statsd - receiver/syslog - receiver/systemd + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml index fbaf2f14e594..fd942cc8362f 100644 --- a/.github/ISSUE_TEMPLATE/other.yaml +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -298,6 +298,7 @@ body: - receiver/statsd - receiver/syslog - receiver/systemd + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog diff --git a/.github/ISSUE_TEMPLATE/unmaintained.yaml b/.github/ISSUE_TEMPLATE/unmaintained.yaml index 313bddf9bffc..6995d9ccb30f 100644 --- a/.github/ISSUE_TEMPLATE/unmaintained.yaml +++ b/.github/ISSUE_TEMPLATE/unmaintained.yaml @@ -303,6 +303,7 @@ body: - receiver/statsd - receiver/syslog - receiver/systemd + - receiver/tcpcheck - receiver/tcplog - receiver/tlscheck - receiver/udplog diff --git a/internal/tidylist/tidylist.txt b/internal/tidylist/tidylist.txt index 9849d3ba7a44..0cedf3c75d37 100644 --- a/internal/tidylist/tidylist.txt +++ b/internal/tidylist/tidylist.txt @@ -295,6 +295,7 @@ receiver/sqlserverreceiver receiver/sshcheckreceiver receiver/statsdreceiver receiver/systemdreceiver +receiver/tcpcheckreceiver receiver/tcplogreceiver receiver/tlscheckreceiver receiver/udplogreceiver 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..e751ca869ae7 --- /dev/null +++ b/receiver/tcpcheckreceiver/README.md @@ -0,0 +1,48 @@ +# TCP Check Receiver + + +| Status | | +| ------------- |-----------| +| Stability | [development]: 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) | [@atoulme](https://www.github.com/atoulme), [@michael-burt](https://www.github.com/michael-burt), [@chengchuanpeng](https://www.github.com/chengchuanpeng) | + +[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development +[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 + +Targets are + +```yaml +receivers: + tcpcheck: + targets: + - endpoint: example.com:443 + dialer: + timeout: 15s + - endpoint: foobar.com:8080 + dialer: + timeout: 15s + - endpoint: localhost:10901 +``` +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..2613a2d27962 --- /dev/null +++ b/receiver/tcpcheckreceiver/config.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "errors" + "fmt" + "net" + "strconv" + "strings" + + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/scraper/scraperhelper" + "go.uber.org/multierr" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/metadata" +) + +// Predefined error responses for configuration validation failures +var ( + errInvalidEndpoint = errors.New(`"Endpoint" must be in the form of :`) + errMissingTargets = errors.New(`No targets specified`) + errConfigTCPCheck = errors.New(`Invalid Config`) +) + +// Config defines the configuration for the various elements of the receiver agent. +type Config struct { + scraperhelper.ControllerConfig `mapstructure:",squash"` + metadata.MetricsBuilderConfig `mapstructure:",squash"` + Targets []*confignet.TCPAddrConfig `mapstructure:",targets"` +} + +func validatePort(port string) error { + portNum, err := strconv.Atoi(port) + if err != nil { + return fmt.Errorf("provided port is not a number: %s", port) + } + if portNum < 1 || portNum > 65535 { + return fmt.Errorf("provided port is out of valid range (1-65535): %d", portNum) + } + return nil +} + +func validateTarget(cfg *confignet.TCPAddrConfig) error { + var err error + + if cfg.Endpoint == "" { + return errMissingTargets + } + + if strings.Contains(cfg.Endpoint, "://") { + return fmt.Errorf("endpoint contains a scheme, which is not allowed: %s", cfg.Endpoint) + } + + _, port, parseErr := net.SplitHostPort(cfg.Endpoint) + if parseErr != nil { + return fmt.Errorf("%s: %w", errInvalidEndpoint.Error(), parseErr) + } + + portParseErr := validatePort(port) + if portParseErr != nil { + return fmt.Errorf("%s: %w", errInvalidEndpoint.Error(), portParseErr) + } + + return err +} + +func (cfg *Config) Validate() error { + var err error + + if len(cfg.Targets) == 0 { + err = multierr.Append(err, errMissingTargets) + } + + for _, tcpConfig := range cfg.Targets { + err = multierr.Append(err, validateTarget(tcpConfig)) + } + + return err +} diff --git a/receiver/tcpcheckreceiver/config_test.go b/receiver/tcpcheckreceiver/config_test.go new file mode 100644 index 000000000000..2b9e56ab021d --- /dev/null +++ b/receiver/tcpcheckreceiver/config_test.go @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/scraper/scraperhelper" +) + +func TestValidate(t *testing.T) { + testCases := []struct { + desc string + cfg *Config + expectedErr error + }{ + { + desc: "missing targets", + cfg: &Config{ + Targets: []*confignet.TCPAddrConfig{}, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: errMissingTargets, + }, + { + desc: "invalid endpoint", + cfg: &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "endpoint: 12efg", + DialerConfig: confignet.DialerConfig{ + Timeout: 12 * time.Second, + }, + }, + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: fmt.Errorf("%w: %s", errInvalidEndpoint, "provided port is not a number: 12efg"), + }, + { + desc: "invalid config with multiple targets", + cfg: &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "endpoint: 12efg", + }, + { + Endpoint: "https://example.com:80", + }, + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: fmt.Errorf("%w: %s", errInvalidEndpoint, `provided port is not a number: 12efg; endpoint contains a scheme, which is not allowed: https://example.com:80`), + }, + { + desc: "port out of range", + cfg: &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "www.opentelemetry.io:67000", + }, + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: fmt.Errorf("%w: %s", errInvalidEndpoint, `provided port is out of valid range (1-65535): 67000`), + }, + { + desc: "missing port", + cfg: &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "www.opentelemetry.io/docs", + }, + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: fmt.Errorf("%w: %s", errInvalidEndpoint, `address www.opentelemetry.io/docs: missing port in address`), + }, + { + desc: "valid config", + cfg: &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "opentelemetry.io:443", + DialerConfig: confignet.DialerConfig{ + Timeout: 3 * time.Second, + }, + }, + { + Endpoint: "opentelemetry.io:8080", + DialerConfig: confignet.DialerConfig{ + Timeout: 1 * time.Second, + }, + }, + { + Endpoint: "111.222.33.44:10000", + DialerConfig: confignet.DialerConfig{ + Timeout: 5 * time.Second, + }, + }, + }, + ControllerConfig: scraperhelper.NewDefaultControllerConfig(), + }, + expectedErr: 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) + } + }) + } +} 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..a4f9794d5679 --- /dev/null +++ b/receiver/tcpcheckreceiver/documentation.md @@ -0,0 +1,56 @@ +[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 | +| ---- | ----------- | ------ | +| tcpcheck.endpoint | TCP 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 | +| ---- | ----------- | ------ | +| tcpcheck.endpoint | TCP endpoint | Any Str | +| error.code | Error code recorded during check | 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 | +| ---- | ----------- | ------ | +| tcpcheck.endpoint | TCP endpoint | Any Str | diff --git a/receiver/tcpcheckreceiver/factory.go b/receiver/tcpcheckreceiver/factory.go new file mode 100644 index 000000000000..8bcced1a8c71 --- /dev/null +++ b/receiver/tcpcheckreceiver/factory.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + collectorscraper "go.opentelemetry.io/collector/scraper" + "go.opentelemetry.io/collector/scraper/scraperhelper" + + "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, + newDefaultConfig, + receiver.WithMetrics(newReceiver, metadata.MetricsStability)) +} + +func newDefaultConfig() component.Config { + cfg := scraperhelper.NewDefaultControllerConfig() + + return &Config{ + ControllerConfig: cfg, + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + Targets: []*confignet.TCPAddrConfig{}, + } +} + +func newReceiver( + _ context.Context, + settings receiver.Settings, + cfg component.Config, + consumer consumer.Metrics, +) (receiver.Metrics, error) { + tcpCheckConfig, ok := cfg.(*Config) + if !ok { + return nil, errConfigTCPCheck + } + + mp := newScraper(tcpCheckConfig, settings) + s, err := collectorscraper.NewMetrics(mp.scrape) + if err != nil { + return nil, err + } + opt := scraperhelper.AddScraper(metadata.Type, s) + + return scraperhelper.NewMetricsController( + &tcpCheckConfig.ControllerConfig, + settings, + consumer, + opt, + ) +} diff --git a/receiver/tcpcheckreceiver/factory_test.go b/receiver/tcpcheckreceiver/factory_test.go new file mode 100644 index 000000000000..db5b48f1f9ce --- /dev/null +++ b/receiver/tcpcheckreceiver/factory_test.go @@ -0,0 +1,82 @@ +// 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/config/confignet" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + "go.opentelemetry.io/collector/scraper/scraperhelper" + + "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: 60 * time.Second, + InitialDelay: time.Second, + }, + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + Targets: []*confignet.TCPAddrConfig{}, + } + + 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, errConfigTCPCheck) + }, + }, + } + + 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..e107ee7da58e --- /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 { + createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) + name string + }{ + + { + 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 _, tt := range tests { + t.Run(tt.name+"-shutdown", func(t *testing.T) { + c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(), cfg) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + t.Run(tt.name+"-lifecycle", func(t *testing.T) { + firstRcvr, err := tt.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 := tt.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..fd0f696875dc --- /dev/null +++ b/receiver/tcpcheckreceiver/go.mod @@ -0,0 +1,71 @@ +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.119.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.117.0 + github.com/stretchr/testify v1.10.0 + go.opentelemetry.io/collector/component v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/component/componenttest v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/config/confignet v1.25.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/confmap v1.25.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/consumer v1.25.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/consumer/consumertest v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/extension v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/extension/auth/authtest v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/pdata v1.25.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/receiver v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/receiver/receivertest v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/scraper v0.119.1-0.20250210123122-44b3eeda354c + go.opentelemetry.io/collector/scraper/scraperhelper v0.119.1-0.20250210123122-44b3eeda354c + 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.2.1 // 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.2 // 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.119.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror v0.119.1-0.20250210123122-44b3eeda354c // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.119.1-0.20250210123122-44b3eeda354c // indirect + go.opentelemetry.io/collector/extension/auth v0.119.1-0.20250210123122-44b3eeda354c // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.119.1-0.20250210123122-44b3eeda354c // indirect + go.opentelemetry.io/collector/pipeline v0.119.1-0.20250210123122-44b3eeda354c // indirect + go.opentelemetry.io/collector/receiver/xreceiver v0.119.1-0.20250210123122-44b3eeda354c // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.5 // 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..39fb84fe5c50 --- /dev/null +++ b/receiver/tcpcheckreceiver/go.sum @@ -0,0 +1,154 @@ +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.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ= +github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= +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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.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/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/collector/component v0.119.1-0.20250210123122-44b3eeda354c h1:ZXGMlIb8kRtTT+XjfDVXndwIfxJLoSgbsI3UKLMJSG4= +go.opentelemetry.io/collector/component v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:QATB3WRjNkcJSkdb4BaIp0d3nTFayiwEZqIJLy/p3H4= +go.opentelemetry.io/collector/component/componenttest v0.119.1-0.20250210123122-44b3eeda354c h1:PJHoTjFL9upl5mi+ppIseTlebzesalg0+bvzDic/obw= +go.opentelemetry.io/collector/component/componenttest v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:qw0JC0EJRxKb7Dmqrdvg1dDs3Ovjprrry8cpF5Q0vF0= +go.opentelemetry.io/collector/config/confignet v1.25.1-0.20250210123122-44b3eeda354c h1:BRw97E3+i/gseft4cqDdplL+H/pV4kZFnWWnq3AMVZk= +go.opentelemetry.io/collector/config/confignet v1.25.1-0.20250210123122-44b3eeda354c/go.mod h1:ZppUH1hgUJOubawEsxsQ9MzEYFytqo2GnVSS7d4CVxc= +go.opentelemetry.io/collector/confmap v1.25.1-0.20250210123122-44b3eeda354c h1:LO+X9E8QkbObkjZMSSMnuQzAjQOTiF7EnXfmktg2NXw= +go.opentelemetry.io/collector/confmap v1.25.1-0.20250210123122-44b3eeda354c/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= +go.opentelemetry.io/collector/consumer v1.25.1-0.20250210123122-44b3eeda354c h1:Nu8zOAsLB1ChxAgb5DYOYSQkJGuUaHIH1LlmakLcuyk= +go.opentelemetry.io/collector/consumer v1.25.1-0.20250210123122-44b3eeda354c/go.mod h1:wzomc+0FCV4FrgNP4SUF/7P1ux2iE7h4SAxVXk0+rv4= +go.opentelemetry.io/collector/consumer/consumererror v0.119.1-0.20250210123122-44b3eeda354c h1:SSjo+YYRBgsE3fw426HrEku6WNoyP6MMXRycRZRxApE= +go.opentelemetry.io/collector/consumer/consumererror v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:EXh9Sl/YNz3xSgzDdptiUr2UszNPONdHc3RzdfeKQDE= +go.opentelemetry.io/collector/consumer/consumertest v0.119.1-0.20250210123122-44b3eeda354c h1:ErvHovgDePqXULwMtxVQcFGIPm1KqCrz+R8XK7752wI= +go.opentelemetry.io/collector/consumer/consumertest v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:ur4t9L7UlnyqzgtWkoGC9dcJI0UR4u/5r2yz44ivblE= +go.opentelemetry.io/collector/consumer/xconsumer v0.119.1-0.20250210123122-44b3eeda354c h1:AS9BA4G5hPZ8NwZLqV07y4N7Cd0/PT6dWaosKnsVHSc= +go.opentelemetry.io/collector/consumer/xconsumer v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:wwqVjueP0SKyuFKtDyqkzs0nAjQhHbSITEr+7oWV1Nc= +go.opentelemetry.io/collector/extension v0.119.1-0.20250210123122-44b3eeda354c h1:9koiLXtEcu3pNTWokNI7/p+kGoPFb20OB7fPwHY516o= +go.opentelemetry.io/collector/extension v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:qJ07BdPHd7x6vgUeGH8LjC0+0jGD71T0za9vbnzqNHI= +go.opentelemetry.io/collector/extension/auth v0.119.1-0.20250210123122-44b3eeda354c h1:D8vXuJnkJ7aIRko3RytgoLgHOpXnwy7pnQrvHOiObV0= +go.opentelemetry.io/collector/extension/auth v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:6m03tLvjs+IPYJnVG7UxtQSEcR5BcbOYpEGEjGXde4U= +go.opentelemetry.io/collector/extension/auth/authtest v0.119.1-0.20250210123122-44b3eeda354c h1:vofLOOFJ1G3x+kEmhN/4LLQs/gDm0HkcWwA9hkrxnCI= +go.opentelemetry.io/collector/extension/auth/authtest v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:dK3KIcpWt2wubA45pbYezBd06ZFE9J27wq2DefRPEKY= +go.opentelemetry.io/collector/pdata v1.25.1-0.20250210123122-44b3eeda354c h1:noCm8Icz32bpyqS1Oc/dTt515Mr6yqpMElPAF1GeQCc= +go.opentelemetry.io/collector/pdata v1.25.1-0.20250210123122-44b3eeda354c/go.mod h1:ceSqZgTsX5dWzK73YmLBkP87sIEmY1t4d3sf3I2M1VQ= +go.opentelemetry.io/collector/pdata/pprofile v0.119.1-0.20250210123122-44b3eeda354c h1:deYPYyxK9Z5UTJZVNJA/IB6MvYW4DkXlq8i1uhMk7TA= +go.opentelemetry.io/collector/pdata/pprofile v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:B47NfKcz/Go2LMkdyDeOkQYGxK0RSW/RfHrjRYm5pYM= +go.opentelemetry.io/collector/pdata/testdata v0.119.0 h1:a3OiuLYx7CaEQQ8LxMhPIM804eIGlNM6Pd0XoACXWMI= +go.opentelemetry.io/collector/pdata/testdata v0.119.0/go.mod h1:stCgL1fKOVp93mI4ocHy/xBMhkW3TXr8VetH4X86q8g= +go.opentelemetry.io/collector/pipeline v0.119.1-0.20250210123122-44b3eeda354c h1:7wjodUC7ykxg900reVF1svAIv4XNzTAnRyd75dTNTt0= +go.opentelemetry.io/collector/pipeline v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:qE3DmoB05AW0C3lmPvdxZqd/H4po84NPzd5MrqgtL74= +go.opentelemetry.io/collector/receiver v0.119.1-0.20250210123122-44b3eeda354c h1:x3WbeEtQbh3dL7FIwfn/JT2bhJcQ/i8l6oUIDcjIcSk= +go.opentelemetry.io/collector/receiver v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:QCV0M2OClUeBwTB4i8BaMCas70Jdtftimr82Qsw88G0= +go.opentelemetry.io/collector/receiver/receivertest v0.119.1-0.20250210123122-44b3eeda354c h1:MHZ5hsbGPm+z5cAZG/2UVi/c4Vq0GE12PBsNC9qmViw= +go.opentelemetry.io/collector/receiver/receivertest v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:Py0gUIasTT/CSVQa3kQwdNW40ItqNfWWyzdxyXmf9+c= +go.opentelemetry.io/collector/receiver/xreceiver v0.119.1-0.20250210123122-44b3eeda354c h1:NrHBdER8X49GiiGVYen+Z7XZTFqyV9XDiBgqkUyboN8= +go.opentelemetry.io/collector/receiver/xreceiver v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:qj/SpwPXrSbpLcyj2xnKPI2SnyL8slVFBkSRnJK84MM= +go.opentelemetry.io/collector/scraper v0.119.1-0.20250210123122-44b3eeda354c h1:mpv+mrn/SoP+onmOzRCd9A7FgRzfb4BmxTUWjiYlb4M= +go.opentelemetry.io/collector/scraper v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:VhtDISOJAfQTrx3L9OYS6MLrI2qFDheC6Hw+DLnM2QU= +go.opentelemetry.io/collector/scraper/scraperhelper v0.119.1-0.20250210123122-44b3eeda354c h1:M7MIgzjHeyVESxgwwYEnftii6oA0tmdJaoV5nGpd2R8= +go.opentelemetry.io/collector/scraper/scraperhelper v0.119.1-0.20250210123122-44b3eeda354c/go.mod h1:nnbuqNnmtYhjKT+wlj7PB1qvbotE1bN89f1+/YfCBmc= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +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-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/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..19a144398600 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_config_test.go @@ -0,0 +1,62 @@ +// 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) + diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{})) + require.Emptyf(t, diff, "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..2f20d8df5c58 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics.go @@ -0,0 +1,320 @@ +// 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, tcpcheckEndpointAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("tcpcheck.endpoint", tcpcheckEndpointAttributeValue) +} + +// 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, tcpcheckEndpointAttributeValue string, errorCodeAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Sum().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("tcpcheck.endpoint", tcpcheckEndpointAttributeValue) + dp.Attributes().PutStr("error.code", errorCodeAttributeValue) +} + +// 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, tcpcheckEndpointAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("tcpcheck.endpoint", tcpcheckEndpointAttributeValue) +} + +// 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 interface { + apply(*MetricsBuilder) +} + +type metricBuilderOptionFunc func(mb *MetricsBuilder) + +func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) { + mbof(mb) +} + +// WithStartTime sets startTime on the metrics builder. +func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption { + return metricBuilderOptionFunc(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.apply(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 interface { + apply(pmetric.ResourceMetrics) +} + +type resourceMetricsOptionFunc func(pmetric.ResourceMetrics) + +func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) { + rmof(rm) +} + +// 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 resourceMetricsOptionFunc(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 resourceMetricsOptionFunc(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(options ...ResourceMetricsOption) { + rm := pmetric.NewResourceMetrics() + ils := rm.ScopeMetrics().AppendEmpty() + ils.Scope().SetName("github.com/open-telemetry/opentelemetry-collector-contrib/receiver/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 options { + op.apply(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(options ...ResourceMetricsOption) pmetric.Metrics { + mb.EmitForResource(options...) + 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, tcpcheckEndpointAttributeValue string) { + mb.metricTcpcheckDuration.recordDataPoint(mb.startTime, ts, val, tcpcheckEndpointAttributeValue) +} + +// RecordTcpcheckErrorDataPoint adds a data point to tcpcheck.error metric. +func (mb *MetricsBuilder) RecordTcpcheckErrorDataPoint(ts pcommon.Timestamp, val int64, tcpcheckEndpointAttributeValue string, errorCodeAttributeValue string) { + mb.metricTcpcheckError.recordDataPoint(mb.startTime, ts, val, tcpcheckEndpointAttributeValue, errorCodeAttributeValue) +} + +// RecordTcpcheckStatusDataPoint adds a data point to tcpcheck.status metric. +func (mb *MetricsBuilder) RecordTcpcheckStatusDataPoint(ts pcommon.Timestamp, val int64, tcpcheckEndpointAttributeValue string) { + mb.metricTcpcheckStatus.recordDataPoint(mb.startTime, ts, val, tcpcheckEndpointAttributeValue) +} + +// 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.apply(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..6167017ec319 --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_metrics_test.go @@ -0,0 +1,150 @@ +// 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 _, tt := range tests { + t.Run(tt.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, tt.name), settings, WithStartTime(start)) + + expectedWarnings := 0 + + assert.Equal(t, expectedWarnings, observedLogs.Len()) + + defaultMetricsCount := 0 + allMetricsCount := 0 + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordTcpcheckDurationDataPoint(ts, 1, "tcpcheck.endpoint-val") + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordTcpcheckErrorDataPoint(ts, 1, "tcpcheck.endpoint-val", "error.code-val") + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordTcpcheckStatusDataPoint(ts, 1, "tcpcheck.endpoint-val") + + res := pcommon.NewResource() + metrics := mb.Emit(WithResource(res)) + + if tt.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 tt.metricsSet == testDataSetDefault { + assert.Equal(t, defaultMetricsCount, ms.Len()) + } + if tt.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("tcpcheck.endpoint") + assert.True(t, ok) + assert.EqualValues(t, "tcpcheck.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.True(t, 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("tcpcheck.endpoint") + assert.True(t, ok) + assert.EqualValues(t, "tcpcheck.endpoint-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("error.code") + assert.True(t, ok) + assert.EqualValues(t, "error.code-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("tcpcheck.endpoint") + assert.True(t, ok) + assert.EqualValues(t, "tcpcheck.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..042a06071d7b --- /dev/null +++ b/receiver/tcpcheckreceiver/internal/metadata/generated_status.go @@ -0,0 +1,16 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/component" +) + +var ( + Type = component.MustNewType("tcpcheck") + ScopeName = "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" +) + +const ( + MetricsStability = component.StabilityLevelDevelopment +) 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..88e529554a32 --- /dev/null +++ b/receiver/tcpcheckreceiver/metadata.yaml @@ -0,0 +1,44 @@ +type: tcpcheck + +status: + class: receiver + stability: + development: [metrics] + distributions: [contrib] + codeowners: + active: [atoulme, michael-burt, chengchuanpeng] + +resource_attributes: + +attributes: + error.code: + description: Error code recorded during check + type: string + tcpcheck.endpoint: + description: TCP endpoint + type: string + +metrics: + tcpcheck.duration: + description: Measures the duration of TCP connection. + enabled: true + gauge: + value_type: int + unit: ms + attributes: [tcpcheck.endpoint] + tcpcheck.status: + description: 1 if the TCP client successfully connected, otherwise 0. + enabled: true + gauge: + value_type: int + unit: "1" + attributes: [tcpcheck.endpoint] + tcpcheck.error: + description: Records errors occurring during TCP check. + enabled: true + sum: + value_type: int + aggregation_temporality: cumulative + monotonic: true + unit: "{error}" + attributes: [tcpcheck.endpoint, error.code] \ No newline at end of file diff --git a/receiver/tcpcheckreceiver/scraper.go b/receiver/tcpcheckreceiver/scraper.go new file mode 100644 index 000000000000..048f68b20fb7 --- /dev/null +++ b/receiver/tcpcheckreceiver/scraper.go @@ -0,0 +1,97 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +package tcpcheckreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver" + +import ( + "context" + "sync" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver/internal/metadata" +) + +type scraper struct { + cfg *Config + settings component.TelemetrySettings + mb *metadata.MetricsBuilder + getConnectionState func(tcpConfig *confignet.TCPAddrConfig) (TCPConnectionState, error) +} + +type TCPConnectionState struct { + LocalAddr string // Local address of the connection + RemoteAddr string // Remote address of the connection + Network string // Network type (e.g., "tcp") +} + +func getConnectionState(tcpConfig *confignet.TCPAddrConfig) (TCPConnectionState, error) { + conn, err := tcpConfig.Dial(context.Background()) + if err != nil { + return TCPConnectionState{}, err + } + defer conn.Close() + state := TCPConnectionState{ + LocalAddr: conn.LocalAddr().String(), + RemoteAddr: conn.RemoteAddr().String(), + Network: conn.LocalAddr().Network(), + } + return state, nil +} + +func (s *scraper) scrapeEndpoint(tcpConfig *confignet.TCPAddrConfig, wg *sync.WaitGroup, mux *sync.Mutex) { + defer wg.Done() + const pointValue int64 = 1 + + start := time.Now() + _, err := s.getConnectionState(tcpConfig) + now := pcommon.NewTimestampFromTime(time.Now()) + + duration := time.Since(start).Milliseconds() + + mux.Lock() + defer mux.Unlock() + + if err != nil { + // Record error data point and log the error + s.mb.RecordTcpcheckErrorDataPoint(now, pointValue, tcpConfig.Endpoint, err.Error()) + s.settings.Logger.Error("TCP connection error encountered", zap.String("endpoint", tcpConfig.Endpoint), zap.Error(err)) + return + } + + // Record success data points + s.mb.RecordTcpcheckDurationDataPoint(now, duration, tcpConfig.Endpoint) + s.mb.RecordTcpcheckStatusDataPoint(now, pointValue, tcpConfig.Endpoint) +} + +func (s *scraper) scrape(_ context.Context) (pmetric.Metrics, error) { + if s.cfg == nil || len(s.cfg.Targets) == 0 { + return pmetric.NewMetrics(), errMissingTargets + } + + var wg sync.WaitGroup + wg.Add(len(s.cfg.Targets)) + var mux sync.Mutex + + for _, tcpConfig := range s.cfg.Targets { + // endpoint and dialer + go s.scrapeEndpoint(tcpConfig, &wg, &mux) + } + + wg.Wait() + return s.mb.Emit(), nil +} + +func newScraper(cfg *Config, settings receiver.Settings) *scraper { + return &scraper{ + cfg: cfg, + settings: settings.TelemetrySettings, + mb: metadata.NewMetricsBuilder(metadata.DefaultMetricsBuilderConfig(), settings), + getConnectionState: getConnectionState, + } +} diff --git a/receiver/tcpcheckreceiver/scraper_test.go b/receiver/tcpcheckreceiver/scraper_test.go new file mode 100644 index 000000000000..bf8b285e7bca --- /dev/null +++ b/receiver/tcpcheckreceiver/scraper_test.go @@ -0,0 +1,244 @@ +// 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" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/pdata/pmetric" + "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() + assert.NoError(t, err) + go handleRequest(conn) + }() + return listener.Addr().String() +} + +func (server *Server) runTCPServerError() (string, error) { + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", server.host, server.port)) + if err != nil { + return "", err + } + server.listener = listener + go func() { + time.Sleep(time.Millisecond * 100) + err := listener.Close() + if err != nil { + fmt.Printf("Error closing listener: %v\n", err) + } + }() + return listener.Addr().String(), nil +} + +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", message) + _, err = conn.Write([]byte("Message received.\n")) + if err != nil { + conn.Close() + return + } + conn.Close() + } +} + +func timeout(deadline time.Time, timeout time.Duration) time.Duration { + timeToDeadline := time.Until(deadline) + if timeToDeadline < timeout { + return timeToDeadline + } + return timeout +} + +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: "expected.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) + cfg := &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "127.0.0.1:8080", + DialerConfig: confignet.DialerConfig{ + Timeout: 3 * time.Second, + }, + }, + }, + } + + cfg.ControllerConfig.CollectionInterval = 100 * time.Millisecond + settings := receivertest.NewNopSettings() + + scraper := newScraper(cfg, settings) + actualMetrics, err := scraper.scrape(context.Background()) + actualMetrics.ResourceMetrics() + require.NoError(t, err, "failed scrape") + require.NoError( + t, + pmetrictest.CompareMetrics( + expectedMetrics, + actualMetrics, + pmetrictest.IgnoreMetricValues("tcpcheck.duration"), + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + ), + ) + }) + } +} + +func TestScraper_TCPErrorMetrics(t *testing.T) { + s := newTCPServer("127.0.0.1", "8081") + endpoint, _ := s.runTCPServerError() + defer s.shutdown() + + testCases := []struct { + name string + filename string + endpoint string + }{ + { + name: "tcp_error_metrics", + filename: "expected_error.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) + + if runtime.GOOS == "windows" { + expectedError := "dial tcp 127.0.0.1:9999: connectex: No connection could be made because the target machine actively refused it." + expectedMetrics = updateErrorCodeInMetrics(expectedMetrics, expectedError) + } + + cfg := &Config{ + Targets: []*confignet.TCPAddrConfig{ + { + Endpoint: "127.0.0.1:9999", + DialerConfig: confignet.DialerConfig{ + Timeout: 3 * time.Second, + }, + }, + }, + } + + cfg.ControllerConfig.CollectionInterval = 100 * time.Millisecond + settings := receivertest.NewNopSettings() + + scraper := newScraper(cfg, settings) + actualMetrics, err := scraper.scrape(context.Background()) + require.NoError(t, err, "failed scrape") + + require.NoError( + t, + pmetrictest.CompareMetrics( + expectedMetrics, + actualMetrics, + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + ), + ) + }) + } +} + +func updateErrorCodeInMetrics(metrics pmetric.Metrics, newErrorCode string) pmetric.Metrics { + metrics.ResourceMetrics().At(0). + ScopeMetrics().At(0). + Metrics().At(0). + Sum().DataPoints().At(0). + Attributes().PutStr("error.code", newErrorCode) + return metrics +} 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/expected.yaml b/receiver/tcpcheckreceiver/testdata/expected_metrics/expected.yaml new file mode 100644 index 000000000000..d36c9621e338 --- /dev/null +++ b/receiver/tcpcheckreceiver/testdata/expected_metrics/expected.yaml @@ -0,0 +1,31 @@ +resourceMetrics: + - resource: {} + scopeMetrics: + - metrics: + - description: Measures the duration of TCP connection. + name: tcpcheck.duration + gauge: + dataPoints: + - asInt: "1" + attributes: + - key: tcpcheck.endpoint + value: + stringValue: 127.0.0.1:8080 + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + unit: ms + - description: 1 if the TCP client successfully connected, otherwise 0. + name: tcpcheck.status + gauge: + dataPoints: + - asInt: "1" + attributes: + - key: tcpcheck.endpoint + value: + stringValue: 127.0.0.1:8080 + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + unit: "1" + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver + version: latest diff --git a/receiver/tcpcheckreceiver/testdata/expected_metrics/expected_error.yaml b/receiver/tcpcheckreceiver/testdata/expected_metrics/expected_error.yaml new file mode 100644 index 000000000000..19e0b053caac --- /dev/null +++ b/receiver/tcpcheckreceiver/testdata/expected_metrics/expected_error.yaml @@ -0,0 +1,24 @@ +resourceMetrics: + - resource: {} + scopeMetrics: + - metrics: + - description: Records errors occurring during TCP check. + name: tcpcheck.error + sum: + dataPoints: + - asInt: 1 + attributes: + - key: tcpcheck.endpoint + value: + stringValue: 127.0.0.1:9999 + - key: error.code + value: + stringValue: "dial tcp 127.0.0.1:9999: connect: connection refused" + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + aggregationTemporality: AGGREGATION_TEMPORALITY_CUMULATIVE + isMonotonic: true + unit: "{error}" + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver + version: latest \ 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..bc70d45793d0 --- /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: tcpcheck.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 + gauge: + dataPoints: + - asInt: "1" + attributes: + - key: tcpcheck.endpoint + value: + stringValue: 127.0.0.1:8080 + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + unit: "1" + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver + version: latest diff --git a/reports/distributions/contrib.yaml b/reports/distributions/contrib.yaml index f52c2435cafc..e73d3ff55e6d 100644 --- a/reports/distributions/contrib.yaml +++ b/reports/distributions/contrib.yaml @@ -193,6 +193,7 @@ components: - sshcheck - statsd - syslog + - tcpcheck - tcplog - tlscheck - udplog diff --git a/versions.yaml b/versions.yaml index 444e416a179c..5dab027393dd 100644 --- a/versions.yaml +++ b/versions.yaml @@ -292,6 +292,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/systemdreceiver + - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcpcheckreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tcplogreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/tlscheckreceiver - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/udplogreceiver