From 5b842a9ee39d6dbcdbe832732bac247eb1ea9617 Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Thu, 9 May 2024 12:45:25 -0400 Subject: [PATCH] add otelcol.receiver.file_stats component Add an otelcol.receiver.file_stats component which wraps around the upstream filestatsreceiver component from the -contrib distribution. Closes #238. --- CHANGELOG.md | 5 +- .../components/otelcol.receiver.file_stats.md | 269 ++++++++++++++++++ go.mod | 12 +- go.sum | 4 + internal/component/all/all.go | 1 + .../component/otelcol/config_controller.go | 55 ++++ .../otelcol/receiver/file_stats/file_stats.go | 239 ++++++++++++++++ .../receiver/file_stats/file_stats_test.go | 69 +++++ 8 files changed, 647 insertions(+), 7 deletions(-) create mode 100644 docs/sources/reference/components/otelcol.receiver.file_stats.md create mode 100644 internal/component/otelcol/config_controller.go create mode 100644 internal/component/otelcol/receiver/file_stats/file_stats.go create mode 100644 internal/component/otelcol/receiver/file_stats/file_stats_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 369d6aafd7..4b4f51830f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ v1.1.0-rc.0 - (_Public preview_) Add support for setting GOMEMLIMIT based on cgroup setting. (@mattdurham) - (_Public preview_) Introduce BoringCrypto Docker images. - The BoringCrypto image is tagged with the `-boringcrypto` suffix and + The BoringCrypto image is tagged with the `-boringcrypto` suffix and is only available on AMD64 and ARM64 Linux containers. (@rfratto, @mattdurham) @@ -28,6 +28,9 @@ v1.1.0-rc.0 - `otelcol.exporter.loadbalancing`: Add a new `aws_cloud_map` resolver. (@ptodev) +- Introduce a `otelcol.receiver.file_stats` component from the upstream + OpenTelemetry `filestatsreceiver` component. (@rfratto) + ### Enhancements - Update `prometheus.exporter.kafka` with the following functionalities (@wildum): diff --git a/docs/sources/reference/components/otelcol.receiver.file_stats.md b/docs/sources/reference/components/otelcol.receiver.file_stats.md new file mode 100644 index 0000000000..cac45c225b --- /dev/null +++ b/docs/sources/reference/components/otelcol.receiver.file_stats.md @@ -0,0 +1,269 @@ +--- +canonical: https://grafana.com/docs/alloy/latest/reference/components/otelcol.receiver.file_stats/ +title: otelcol.receiver.file_stats +description: Learn about otelcol.receiver.file_stats +--- + +# otelcol.receiver.file_stats + +`otelcol.receiver.file_stats` collects metrics from files specified with a glob pattern. + +{{< admonition type="note" >}} +`otelcol.receiver.file_stats` is a wrapper over the upstream OpenTelemetry Collector `filestats` receiver from the `otelcol-contrib` distribution. +Bug reports or feature requests will be redirected to the upstream repository, if necessary. +{{< /admonition >}} + +Multiple `otelcol.receiver.file_stats` components can be specified by giving them different labels. + +## Usage + +```alloy +otelcol.receiver.file_stats "LABEL" { + include = "GLOB_PATTERN" + + output { + metrics = [...] + } +} +``` + +## Arguments + +`otelcol.receiver.file_stats` supports the following arguments: + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`include` | `string` | Glob path for files to collect stats from. | | yes +`collection_interval` | `duration` | How often to collect file information. | `"1m"` | no +`initial_delay` | `duration` | Initial time to wait before collecting file information. | `"1s"` | no +`timeout` | `duration` | Timeout for collecting file information; `0s` means no timeout. | `"0s"` | no + +`include` is a glob pattern that specifies which files and folders to collect +stats from. A `*` character matches files in a directory, while `**` includes +matches files. For example, `/var/log/**/*.log` matches all `.log` files in +`/var/log` and its subdirectories. + +## Blocks + +The following blocks are supported inside the definition of `otelcol.receiver.file_stats`: + +Hierarchy | Block | Description | Required +--------- | ----- | ----------- | -------- +metrics | [metrics][] | Configures which metrics will be sent to downstream components. | no +metrics > file.atime | [file.atime][] | Configures the `file.atime` metric. | no +metrics > file.count | [file.count][] | Configures the `file.count` metric. | no +metrics > file.ctime | [file.ctime][] | Configures the `file.ctime` metric. | no +metrics > file.mtime | [file.mtime][] | Configures the `file.mtime` metric. | no +metrics > file.size | [file.size][] | Configures the `file.size` metric. | no +resource_attributes | [resource_attributes][] | Configures resource attributes for metrics sent to downstream components. | no +resource_attributes > file.name | [file.name][] | Configures the `file.name` resource attribute. | no +resource_attributes > file.name > metrics_include | [metrics_include][] | Metrics to include the `file.name` resource attribute in. | no +resource_attributes > file.name > metrics_exclude | [metrics_exclude][] | Metrics to exclude the `file.name` resource attribute from. | no +resource_attributes > file.path | [file.path][] | Configures the `file.path` resource attribute. | no +resource_attributes > file.path > metrics_include | [metrics_include][] | Metrics to include the `file.path` resource attribute in. | no +resource_attributes > file.path > metrics_exclude | [metrics_exclude][] | Metrics to exclude the `file.path` resource attribute from. | no +debug_metrics | [debug_metrics][] | Configures the metrics that this component generates to monitor its state. | no +output | [output][] | Configures where to send received telemetry data. | yes + +[metrics]: #metrics-block +[file.atime]: #fileatime-block +[file.count]: #filecount-block +[file.ctime]: #filectime-block +[file.mtime]: #filemtime-block +[file.size]: #filesize-block +[resource_attributes]: #resource_attributes-block +[file.name]: #filename-block +[metrics_include]: #metrics_include-block +[metrics_exclude]: #metrics_exclude-block +[file.path]: #filepath-block +[debug_metrics]: #debug_metrics-block +[output]: #output-block + +### metrics block + +The `metrics` block configures the set of metrics that will be sent to downstream components. +It accepts no arguments, but contains other blocks for individual metrics: + +* The [file.atime][] block +* The [file.count][] block +* The [file.ctime][] block +* The [file.mtime][] block +* The [file.size][] block + +Refer to the documentation of individual metric blocks for whether that metric is enabled by default. + +[file.atime]: #fileatime-block +[file.count]: #filecount-block +[file.ctime]: #filectime-block +[file.mtime]: #filemtime-block +[file.size]: #filesize-block + +### file.atime block + +The `file.atime` block configures the `file.atime` metric. +`file.atime` tracks the elapsed time since the last access of the file or folder in Unix seconds since the epoch. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to collect the `file.atime` metric. | `false` | no + +### file.count block + +The `file.count` block configures the `file.count` metric. +`file.count` tracks the number of files and folders in the specified glob pattern. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to collect the `file.count` metric. | `false` | no + +### file.ctime block + +The `file.ctime` block configures the `file.ctime` metric. +`file.ctime` tracks the elapsed time since the last change of the file or folder in Unix seconds since the epoch. +Changes include permissions, ownership, and timestamps. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to collect the `file.ctime` metric. | `false` | no + +### file.mtime block + +The `file.mtime` block configures the `file.mtime` metric. +`file.mtime` tracks the elapsed time since the last modification of the file or folder in Unix seconds since the epoch. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to collect the `file.mtime` metric. | `true` | no + +### file.size block + +The `file.size` block configures the `file.size` metric. +`file.size` tracks the size of the file or folder in bytes. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to collect the `file.size` metric. | `true` | no + +### resource_attributes block + +The `resource_attributes` block configures resource attributes for metrics sent to downstream components. +It accepts no arguments, but contains other blocks for configuring individual resource attributes: + +* The [file.name][] block +* The [file.path][] block + +Refer to the documentation of individual resource attribute blocks for whether that resource attribute is enabled by default. + +[file.name]: #filename-block +[file.path]: #filepath-block + +### file.name block + +The `file.name` block configures the `file.name` resource attribute. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to include the `file.name` resource attribute. | `true` | no + +When `enabled` is true, the `file.name` attribute is included in all metrics. + +The children blocks `metrics_include` and `metrics_exclude` can be used to further filter which metrics are given the `file.name` attribute. +If a given metric matches all the `metrics_include` blocks and none of the `metrics_exclude` blocks, the `file.name` attribute is added. + +### metrics_include block + +The `metrics_include` block configures a filter for matching metrics. +The `metrics_include` block may be specified multiple times. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`strict` | `string` | The exact name of the metric to include. | | yes* +`regexp` | `string` | A regular expression for the metrics to include. | | yes* + +Exactly one of `strict` or `regexp` must be specified. + +### metrics_exclude block + +The `metrics_exclude` block configures a filter for excluding metrics. +The `metrics_exclude` block may be specified multiple times. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`strict` | `string` | The exact name of the metric to exclude. | | yes* +`regexp` | `string` | A regular expression for the metrics to exclude. | | yes* + +Exactly one of `strict` or `regexp` must be specified. + +### file.path block + +The `file.path` block configures the `file.path` resource attribute. + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`enabled` | `boolean` | Whether to include the `file.path` resource attribute. | `false` | no + +When `enabled` is true, the `file.path` attribute is included in all metrics. +The children blocks `metrics_include` and `metrics_exclude` can be used to further filter which metrics are given the `file.path` attribute. +If a given metric matches all the `metrics_include` blocks and none of the `metrics_exclude` blocks, the `file.path` attribute is added. + +### debug_metrics block + +{{< docs/shared lookup="reference/components/otelcol-debug-metrics-block.md" source="alloy" version="" >}} + +### output block + +{{< docs/shared lookup="reference/components/output-block.md" source="alloy" version="" >}} + +## Exported fields + +`otelcol.receiver.file_stats` does not export any fields. + +## Component health + +`otelcol.receiver.file_stats` is only reported as unhealthy if given an invalid configuration. + +## Debug information + +`otelcol.receiver.file_stats` does not expose any component-specific debug information. + +## Example + +This example forwards file stats of files with the `.log` extension in `/var/log` through a batch processor before finally sending it to an OTLP-capable endpoint: + +```alloy +otelcol.receiver.file_stats "default" { + include = "/var/log/**/*.log" + + output { + metrics = [otelcol.processor.batch.default.input] + } +} + +otelcol.processor.batch "default" { + output { + metrics = [otelcol.exporter.otlp.default.input] + } +} + +otelcol.exporter.otlp "default" { + client { + endpoint = env("OTLP_ENDPOINT") + } +} +``` + + + +## Compatible components + +`otelcol.receiver.file_stats` can accept arguments from the following components: + +- Components that export [OpenTelemetry `otelcol.Consumer`](../../compatibility/#opentelemetry-otelcolconsumer-exporters) + + +{{< admonition type="note" >}} +Connecting some components may not be sensible or components may require further configuration to make the connection work correctly. +Refer to the linked documentation for more details. +{{< /admonition >}} + + diff --git a/go.mod b/go.mod index 3f0f329c7e..8fe5d40b3d 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,11 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 github.com/Azure/go-autorest/autorest v0.11.29 github.com/IBM/sarama v1.43.1 + github.com/KimMachineGun/automemlimit v0.6.0 github.com/Lusitaniae/apache_exporter v0.11.1-0.20220518131644-f9522724dab4 github.com/Masterminds/sprig/v3 v3.2.3 github.com/PuerkitoBio/rehttp v1.3.0 + github.com/Shopify/sarama v1.38.1 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 github.com/aws/aws-sdk-go v1.51.22 // indirect @@ -19,6 +21,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.49.0 + github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.5 github.com/blang/semver/v4 v4.0.0 github.com/bmatcuk/doublestar v1.3.4 github.com/boynux/squid-exporter v1.10.5-0.20230618153315-c1fae094e18e @@ -60,6 +63,7 @@ require ( github.com/grafana/go-gelf/v2 v2.0.1 github.com/grafana/jfr-parser/pprof v0.0.0-20240126072739-986e71dc0361 github.com/grafana/jsonparser v0.0.0-20240209175146-098958973a2d + github.com/grafana/kafka_exporter v0.0.0-20240409084445-5e3488ad9f9a github.com/grafana/loki v1.6.2-0.20240221085104-f9d188620153 // k190 branch github.com/grafana/loki/pkg/push v0.0.0-20231212100434-384e5c2dc872 // k180 branch github.com/grafana/pyroscope-go/godeltaprof v0.1.7 @@ -254,12 +258,7 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -require ( - github.com/KimMachineGun/automemlimit v0.6.0 - github.com/Shopify/sarama v1.38.1 - github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.5 - github.com/grafana/kafka_exporter v0.0.0-20240409084445-5e3488ad9f9a -) +require github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filestatsreceiver v0.99.0 require ( cloud.google.com/go v0.112.0 // indirect @@ -340,6 +339,7 @@ require ( github.com/axiomhq/hyperloglog v0.0.0-20240124082744-24bca3a5b39b // indirect github.com/beevik/ntp v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b // indirect github.com/caarlos0/env/v9 v9.0.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index df5a073d7c..81107247e8 100644 --- a/go.sum +++ b/go.sum @@ -409,6 +409,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/boynux/squid-exporter v1.10.5-0.20230618153315-c1fae094e18e h1:C1vYe728vM2FpXaICJuDRt5zgGyRdMmUGYnVfM7WcLY= @@ -1785,6 +1787,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsampling github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.99.0/go.mod h1:VcylhFRTIFy5rFvHhRMg+DKwRW9kp7sbr+e2CiRC4+Y= github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.99.0 h1:GRUah2g+YM2hLr0hewf+z5Fk2VsaakuxTGFUqvFw7/Y= github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.99.0/go.mod h1:rGhH1L2ThE4CscUNS3nhZ8yUQCrnBqoa1Pz8ccj8lRY= +github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filestatsreceiver v0.99.0 h1:Qz9pQ+jDK+b3uPjze4AfNyzLNHGSskEosj2BV0Ao+xE= +github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filestatsreceiver v0.99.0/go.mod h1:yu04DIzkhgRo8diVu8iyE+mywIM3NjnlVbVoq5IdIC0= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.99.0 h1:ZFLADkTCZxJUSMl5CLRZoCU0LovlfDXTzOlPiCbnbeg= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.99.0/go.mod h1:5ZSvDckeb+JO3xKaoyv2nbdNXHyFsg8copo4x+giIf4= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.99.0 h1:632+Gox9DXLIrFK4sCICCA7nRZ0rTFeEou1a6egf1TQ= diff --git a/internal/component/all/all.go b/internal/component/all/all.go index 498aa106c4..cd29bebf6d 100644 --- a/internal/component/all/all.go +++ b/internal/component/all/all.go @@ -85,6 +85,7 @@ import ( _ "github.com/grafana/alloy/internal/component/otelcol/processor/span" // Import otelcol.processor.span _ "github.com/grafana/alloy/internal/component/otelcol/processor/tail_sampling" // Import otelcol.processor.tail_sampling _ "github.com/grafana/alloy/internal/component/otelcol/processor/transform" // Import otelcol.processor.transform + _ "github.com/grafana/alloy/internal/component/otelcol/receiver/file_stats" // Import otelcol.receiver.file_stats _ "github.com/grafana/alloy/internal/component/otelcol/receiver/jaeger" // Import otelcol.receiver.jaeger _ "github.com/grafana/alloy/internal/component/otelcol/receiver/kafka" // Import otelcol.receiver.kafka _ "github.com/grafana/alloy/internal/component/otelcol/receiver/loki" // Import otelcol.receiver.loki diff --git a/internal/component/otelcol/config_controller.go b/internal/component/otelcol/config_controller.go new file mode 100644 index 0000000000..4c702a6226 --- /dev/null +++ b/internal/component/otelcol/config_controller.go @@ -0,0 +1,55 @@ +package otelcol + +import ( + "time" + + "github.com/grafana/alloy/syntax" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// ControllerArguments defines common settings for a scraper controller +// configuration. Scraper controller receivers can squash this struct, instead +// of receiver.Settings, and extend it with more fields if needed. +type ControllerArguments struct { + // CollectionInterval sets how frequently the scraper should be called and + // used as the context timeout to ensure that scrapers don't exceed the + // interval. + CollectionInterval time.Duration `alloy:"collection_interval,attr,optional"` + // InitialDelay sets the initial start delay for the scraper, any non + // positive value is assumed to be immediately. + InitialDelay time.Duration `alloy:"initial_delay,attr,optional"` + // Timeout is an optional value used to set scraper's context deadline. + Timeout time.Duration `alloy:"timeout,attr,optional"` +} + +var ( + _ syntax.Defaulter = (*ControllerArguments)(nil) + _ syntax.Validator = (*ControllerArguments)(nil) +) + +// SetToDefault implements syntax.Defaulter. +func (args *ControllerArguments) SetToDefault() { + *args = ControllerArguments{ + CollectionInterval: time.Minute, + InitialDelay: time.Second, + Timeout: 0, + } +} + +// Validate implements syntax.Validator. +func (args *ControllerArguments) Validate() error { + return args.Convert().Validate() +} + +// Convert converts args to the upstream type. +func (args *ControllerArguments) Convert() *scraperhelper.ControllerConfig { + if args == nil { + return nil + } + + return &scraperhelper.ControllerConfig{ + CollectionInterval: args.CollectionInterval, + InitialDelay: args.InitialDelay, + Timeout: args.Timeout, + } +} diff --git a/internal/component/otelcol/receiver/file_stats/file_stats.go b/internal/component/otelcol/receiver/file_stats/file_stats.go new file mode 100644 index 0000000000..e83e7935d8 --- /dev/null +++ b/internal/component/otelcol/receiver/file_stats/file_stats.go @@ -0,0 +1,239 @@ +// Package file_stats provides an otelcol.receiver.file_stats component. +package file_stats + +import ( + "fmt" + "regexp" + + "github.com/grafana/alloy/internal/component" + "github.com/grafana/alloy/internal/component/otelcol" + "github.com/grafana/alloy/internal/component/otelcol/receiver" + "github.com/grafana/alloy/internal/featuregate" + "github.com/mitchellh/mapstructure" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filestatsreceiver" + otelcomponent "go.opentelemetry.io/collector/component" + otelextension "go.opentelemetry.io/collector/extension" +) + +func init() { + component.Register(component.Registration{ + Name: "otelcol.receiver.file_stats", + Stability: featuregate.StabilityGenerallyAvailable, + Args: Arguments{}, + + Build: func(opts component.Options, args component.Arguments) (component.Component, error) { + fact := filestatsreceiver.NewFactory() + return receiver.New(opts, fact, args.(Arguments)) + }, + }) +} + +// Arguments configures the otelcol.receiver.file_stats component. +type Arguments struct { + Include string `alloy:"include,attr"` + + Controller otelcol.ControllerArguments `alloy:",squash"` + MetricsBuilder MetricsBuilderArguments `alloy:",squash"` + + // DebugMetrics configures component internal metrics. Optional. + DebugMetrics otelcol.DebugMetricsArguments `alloy:"debug_metrics,block,optional"` + + // Output configures where to send received data. Required. + Output *otelcol.ConsumerArguments `alloy:"output,block"` +} + +var _ receiver.Arguments = Arguments{} + +// SetToDefault implements syntax.Defaulter. +func (args *Arguments) SetToDefault() { + *args = Arguments{} + args.Controller.SetToDefault() + args.DebugMetrics.SetToDefault() +} + +// Validate implemenets syntax.Validator. +func (args *Arguments) Validate() error { + if args.Include == "" { + return fmt.Errorf("include must not be empty") + } + return nil +} + +// Convert implements receiver.Arguments. +func (args Arguments) Convert() (otelcomponent.Config, error) { + var out filestatsreceiver.Config + + out.ControllerConfig = *args.Controller.Convert() + out.Include = args.Include + + // We have to use mapstructure.Decode for args.MetricsBuilder because the + // upstream type is in an internal package. + err := mapstructure.Decode( + args.MetricsBuilder.toMap(), + &out.MetricsBuilderConfig, + ) + + return &out, err +} + +// Extensions implements receiver.Arguments. +func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { + return nil +} + +// Exporters implements receiver.Arguments. +func (args Arguments) Exporters() map[otelcomponent.DataType]map[otelcomponent.ID]otelcomponent.Component { + return nil +} + +// NextConsumers implements receiver.Arguments. +func (args Arguments) NextConsumers() *otelcol.ConsumerArguments { + return args.Output +} + +// DebugMetricsConfig implements receiver.Arguments. +func (args Arguments) DebugMetricsConfig() otelcol.DebugMetricsArguments { + return args.DebugMetrics +} + +// MetricsBuilderArguments is a configuration for file_stats metrics builder. +type MetricsBuilderArguments struct { + Metrics MetricsArguments `alloy:"metrics,block,optional"` + ResourceAttributes ResourceAttributesArguments `alloy:"resource_attributes,block,optional"` +} + +// toMap encodes args to a map for use with mapstructure.Decode. +func (args *MetricsBuilderArguments) toMap() map[string]any { + return map[string]any{ + "metrics": args.Metrics.toMap(), + "resource_attributes": args.ResourceAttributes.toMap(), + } +} + +// SetToDefault implements syntax.Defaulter. +func (args *MetricsBuilderArguments) SetToDefault() { + *args = MetricsBuilderArguments{} + args.Metrics.SetToDefault() + args.ResourceAttributes.SetToDefault() +} + +// MetricsArguments provides config for file_stats metrics. +type MetricsArguments struct { + FileAtime MetricArguments `alloy:"file.atime,block,optional"` + FileCount MetricArguments `alloy:"file.count,block,optional"` + FileCtime MetricArguments `alloy:"file.ctime,block,optional"` + FileMtime MetricArguments `alloy:"file.mtime,block,optional"` + FileSize MetricArguments `alloy:"file.size,block,optional"` +} + +// toMap encodes args to a map for use with mapstructure.Decode. +func (args *MetricsArguments) toMap() map[string]any { + return map[string]any{ + "file.atime": args.FileAtime.toMap(), + "file.count": args.FileCount.toMap(), + "file.ctime": args.FileCtime.toMap(), + "file.mtime": args.FileMtime.toMap(), + "file.size": args.FileSize.toMap(), + } +} + +// SetToDefault implements syntax.Defaulter. +func (args *MetricsArguments) SetToDefault() { + args.FileAtime.Enabled = false + args.FileCount.Enabled = false + args.FileCtime.Enabled = false + args.FileMtime.Enabled = true + args.FileSize.Enabled = true +} + +// MetricArguments provides common config for a particular metric. +type MetricArguments struct { + Enabled bool `alloy:"enabled,attr,optional"` +} + +// toMap encodes args to a map for use with mapstructure.Decode. +func (args *MetricArguments) toMap() map[string]any { + return map[string]any{"enabled": args.Enabled} +} + +// ResourceATtributesArguments provides config for file_stats resource +// attributes. +type ResourceAttributesArguments struct { + FileName ResourceAttributeArguments `alloy:"file.name,block,optional"` + FilePath ResourceAttributeArguments `alloy:"file.path,block,optional"` +} + +// toMap encodes args to a map for use with mapstructure.Decode. +func (args *ResourceAttributesArguments) toMap() map[string]any { + return map[string]any{ + "file.name": args.FileName.toMap(), + "file.path": args.FilePath.toMap(), + } +} + +// SetToDefault implements syntax.Defaulter. +func (args *ResourceAttributesArguments) SetToDefault() { + *args = ResourceAttributesArguments{} + args.FileName.Enabled = true + args.FilePath.Enabled = false +} + +// ResourceAttributeArguments provides common config for a particular resource +// attribute. +type ResourceAttributeArguments struct { + Enabled bool `alloy:"enabled,attr,optional"` + MetricsInclude []FilterArguments `alloy:"metrics_include,block,optional"` + MetricsExclude []FilterArguments `alloy:"metrics_exclude,block,optional"` +} + +// toMap encodes args to a map for use with mapstructure.Decode. +func (args *ResourceAttributeArguments) toMap() map[string]any { + includes := make([]map[string]any, 0, len(args.MetricsInclude)) + excludes := make([]map[string]any, 0, len(args.MetricsExclude)) + + for _, include := range args.MetricsInclude { + includes = append(includes, include.toMap()) + } + for _, exclude := range args.MetricsExclude { + excludes = append(excludes, exclude.toMap()) + } + + return map[string]any{ + "enabled": args.Enabled, + "metrics_include": includes, + "metrics_exclude": excludes, + } +} + +// FilterArguments configures the matching behavior of a FilterSet. +type FilterArguments struct { + Strict string `alloy:"strict,attr,optional"` + Regex string `alloy:"regexp,attr,optional"` +} + +// toMap encodes args to a map for use with mapstructure.Decode. +func (args *FilterArguments) toMap() map[string]any { + return map[string]any{ + "strict": args.Strict, + "regexp": args.Regex, + } +} + +// Validate implements syntax.Validator. +func (args *FilterArguments) Validate() error { + if args.Strict == "" && args.Regex == "" { + return fmt.Errorf("must specify either strict or regexp") + } + if args.Strict != "" && args.Regex != "" { + return fmt.Errorf("strict and regexp are mutually exclusive") + } + + if args.Regex != "" { + _, err := regexp.Compile(args.Regex) + if err != nil { + return fmt.Errorf("parsing regexp: %w", err) + } + } + + return nil +} diff --git a/internal/component/otelcol/receiver/file_stats/file_stats_test.go b/internal/component/otelcol/receiver/file_stats/file_stats_test.go new file mode 100644 index 0000000000..6251becba1 --- /dev/null +++ b/internal/component/otelcol/receiver/file_stats/file_stats_test.go @@ -0,0 +1,69 @@ +package file_stats_test + +import ( + "testing" + + "github.com/grafana/alloy/internal/component/otelcol/receiver/file_stats" + "github.com/grafana/alloy/syntax" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filestatsreceiver" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestArguments(t *testing.T) { + in := ` + include = "/var/log/*" + + metrics { + file.atime { + enabled = true + } + file.count { + enabled = true + } + } + + resource_attributes { + file.name { + enabled = true + + metrics_include { + strict = "foobar" + } + metrics_include { + strict = "foobar2" + } + + metrics_exclude { + regexp = "fizz.*" + } + } + } + + output { + // no-op + } + ` + + var args file_stats.Arguments + err := syntax.Unmarshal([]byte(in), &args) + require.NoError(t, err, "arguments should unmarshal without error") + + outAny, err := args.Convert() + require.NoError(t, err, "Arguments should not fail to convert") + + out := outAny.(*filestatsreceiver.Config) + + // We can't compare the types at a high level because the upstream type has + // fields in an internal package, so we check some fields individually here. + // + // NOTE(rfratto): we don't check for defaults being applied; we're primarily + // only interested in ensuring the conversion works. + assert.Equal(t, "/var/log/*", out.Include) + assert.Equal(t, true, out.Metrics.FileAtime.Enabled) + assert.Equal(t, true, out.Metrics.FileCount.Enabled) + assert.Equal(t, true, out.ResourceAttributes.FileName.Enabled) + assert.Equal(t, "foobar", out.ResourceAttributes.FileName.MetricsInclude[0].Strict) + assert.Equal(t, "foobar2", out.ResourceAttributes.FileName.MetricsInclude[1].Strict) + assert.Equal(t, "fizz.*", out.ResourceAttributes.FileName.MetricsExclude[0].Regex) +}