diff --git a/README.md b/README.md index 8561ea0..a7ca9c0 100644 --- a/README.md +++ b/README.md @@ -15,20 +15,10 @@ receivers: **Requires privileged execution** -## Metrics +## Metrics && Attributes -``` -peer.usage.rx_bytes -peer.usage.tx_bytes -peer.last_handshake -``` - -## Attributes +See [metadata autogenerated file](./documentation.md). -``` -peer.device.name ## Interface name -peer.name ## Peer's public key -``` ### Todo diff --git a/config.go b/config.go index e656bbf..957cb5e 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package wireguardreceiver import ( "errors" + "github.com/rogercoll/wireguardreceiver/internal/metadata" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/receiver/scraperhelper" ) @@ -13,6 +14,9 @@ type Config struct { scraperhelper.ScraperControllerSettings `mapstructure:",squash"` // TODO: implement exclude option Exclude ExcludeConfig `mapstructure:"exclude"` + + // MetricsBuilderConfig config. Enable or disable stats by name. + metadata.MetricsBuilderConfig `mapstructure:",squash"` } type ExcludeConfig struct { diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000..00ab24b --- /dev/null +++ b/documentation.md @@ -0,0 +1,37 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# wireguard_stats + +## Default Metrics + +The following metrics are emitted by default. Each of them can be disabled by applying the following configuration: + +```yaml +metrics: + : + enabled: false +``` + +### wireguard.peer.network.io.usage.rx_bytes + +Bytes received by the peer. + +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | +| ---- | ----------- | ---------- | ----------------------- | --------- | +| By | Sum | Int | Cumulative | true | + +### wireguard.peer.network.io.usage.tx_bytes + +Bytes sent. + +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | +| ---- | ----------- | ---------- | ----------------------- | --------- | +| By | Sum | Int | Delta | false | + +## Resource Attributes + +| Name | Description | Values | Enabled | +| ---- | ----------- | ------ | ------- | +| wireguard.device.name | A Device is a WireGuard device. | Any Str | true | +| wireguard.device.type | A DeviceType specifies the underlying implementation of a WireGuard device. | Str: ``Linux kernel``, ``OpenBSD kernel``, ``FreeBSD kernel``, ``Windows kernel``, ``userspace``, ``unknown`` | true | +| wireguard.peer.name | A Device is a WireGuard device. | Any Str | true | diff --git a/factory.go b/factory.go index 6406e3d..e4a4f52 100644 --- a/factory.go +++ b/factory.go @@ -24,6 +24,7 @@ func createDefaultConfig() *Config { cfg.Timeout = 5 * time.Second return &Config{ ScraperControllerSettings: cfg, + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), } } diff --git a/go.mod b/go.mod index d66a6c3..447c08a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/stretchr/testify v1.8.4 go.opentelemetry.io/collector/component v0.86.0 + go.opentelemetry.io/collector/confmap v0.86.0 go.opentelemetry.io/collector/consumer v0.86.0 go.opentelemetry.io/collector/pdata v1.0.0-rcv0015 go.opentelemetry.io/collector/receiver v0.86.0 @@ -20,7 +21,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/knadh/koanf v1.4.3 // indirect + github.com/knadh/koanf v1.5.0 // indirect github.com/knadh/koanf/v2 v2.0.1 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect @@ -30,11 +31,11 @@ require ( 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/cmd/mdatagen v0.86.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/collector v0.86.0 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.86.0 // indirect - go.opentelemetry.io/collector/confmap v0.86.0 // indirect go.opentelemetry.io/collector/featuregate v1.0.0-rcv0015 // indirect go.opentelemetry.io/otel v1.18.0 // indirect go.opentelemetry.io/otel/metric v1.18.0 // indirect @@ -43,14 +44,14 @@ require ( go.opentelemetry.io/otel/trace v1.18.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.16.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/grpc v1.58.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/grpc v1.58.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e0e8a2f..27d4f20 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/knadh/koanf v1.4.3 h1:rSJcSH5LSFhvzBRsAYfT3k7eLP0I4UxeZqjtAatk+wc= github.com/knadh/koanf v1.4.3/go.mod h1:5FAkuykKXZvLqhAbP4peWgM5CTcZmn7L1d27k/a+kfg= +github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= +github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -216,6 +220,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/open-telemetry/opentelemetry-collector-contrib/cmd/mdatagen v0.86.0 h1:tyiUPgapucBnGN5A1Ly1zBh4TJRJCtI6awjPqEO9vG0= +github.com/open-telemetry/opentelemetry-collector-contrib/cmd/mdatagen v0.86.0/go.mod h1:baZ6CnCoqDTI42m3pVwZpLLp8cpcLFfdA82Zu7N1CtM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= @@ -324,6 +330,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -353,6 +361,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -399,6 +409,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -438,6 +450,8 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -449,6 +463,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/internal/metadata/generated_config.go b/internal/metadata/generated_config.go new file mode 100644 index 0000000..0a58fc5 --- /dev/null +++ b/internal/metadata/generated_config.go @@ -0,0 +1,80 @@ +// 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, confmap.WithErrorUnused()) + if err != nil { + return err + } + ms.enabledSetByUser = parser.IsSet("enabled") + return nil +} + +// MetricsConfig provides config for wireguard_stats metrics. +type MetricsConfig struct { + WireguardPeerNetworkIoUsageRxBytes MetricConfig `mapstructure:"wireguard.peer.network.io.usage.rx_bytes"` + WireguardPeerNetworkIoUsageTxBytes MetricConfig `mapstructure:"wireguard.peer.network.io.usage.tx_bytes"` +} + +func DefaultMetricsConfig() MetricsConfig { + return MetricsConfig{ + WireguardPeerNetworkIoUsageRxBytes: MetricConfig{ + Enabled: true, + }, + WireguardPeerNetworkIoUsageTxBytes: MetricConfig{ + Enabled: true, + }, + } +} + +// ResourceAttributeConfig provides common config for a particular resource attribute. +type ResourceAttributeConfig struct { + Enabled bool `mapstructure:"enabled"` +} + +// ResourceAttributesConfig provides config for wireguard_stats resource attributes. +type ResourceAttributesConfig struct { + WireguardDeviceName ResourceAttributeConfig `mapstructure:"wireguard.device.name"` + WireguardDeviceType ResourceAttributeConfig `mapstructure:"wireguard.device.type"` + WireguardPeerName ResourceAttributeConfig `mapstructure:"wireguard.peer.name"` +} + +func DefaultResourceAttributesConfig() ResourceAttributesConfig { + return ResourceAttributesConfig{ + WireguardDeviceName: ResourceAttributeConfig{ + Enabled: true, + }, + WireguardDeviceType: ResourceAttributeConfig{ + Enabled: true, + }, + WireguardPeerName: ResourceAttributeConfig{ + Enabled: true, + }, + } +} + +// MetricsBuilderConfig is a configuration for wireguard_stats metrics builder. +type MetricsBuilderConfig struct { + Metrics MetricsConfig `mapstructure:"metrics"` + ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"` +} + +func DefaultMetricsBuilderConfig() MetricsBuilderConfig { + return MetricsBuilderConfig{ + Metrics: DefaultMetricsConfig(), + ResourceAttributes: DefaultResourceAttributesConfig(), + } +} diff --git a/internal/metadata/generated_config_test.go b/internal/metadata/generated_config_test.go new file mode 100644 index 0000000..71f601c --- /dev/null +++ b/internal/metadata/generated_config_test.go @@ -0,0 +1,120 @@ +// 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/component" + "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{ + WireguardPeerNetworkIoUsageRxBytes: MetricConfig{Enabled: true}, + WireguardPeerNetworkIoUsageTxBytes: MetricConfig{Enabled: true}, + }, + ResourceAttributes: ResourceAttributesConfig{ + WireguardDeviceName: ResourceAttributeConfig{Enabled: true}, + WireguardDeviceType: ResourceAttributeConfig{Enabled: true}, + WireguardPeerName: ResourceAttributeConfig{Enabled: true}, + }, + }, + }, + { + name: "none_set", + want: MetricsBuilderConfig{ + Metrics: MetricsConfig{ + WireguardPeerNetworkIoUsageRxBytes: MetricConfig{Enabled: false}, + WireguardPeerNetworkIoUsageTxBytes: MetricConfig{Enabled: false}, + }, + ResourceAttributes: ResourceAttributesConfig{ + WireguardDeviceName: ResourceAttributeConfig{Enabled: false}, + WireguardDeviceType: ResourceAttributeConfig{Enabled: false}, + WireguardPeerName: ResourceAttributeConfig{Enabled: false}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := loadMetricsBuilderConfig(t, tt.name) + if diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{}, ResourceAttributeConfig{})); diff != "" { + t.Errorf("Config mismatch (-expected +actual):\n%s", diff) + } + }) + } +} + +func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + sub, err := cm.Sub(name) + require.NoError(t, err) + cfg := DefaultMetricsBuilderConfig() + require.NoError(t, component.UnmarshalConfig(sub, &cfg)) + return cfg +} + +func TestResourceAttributesConfig(t *testing.T) { + tests := []struct { + name string + want ResourceAttributesConfig + }{ + { + name: "default", + want: DefaultResourceAttributesConfig(), + }, + { + name: "all_set", + want: ResourceAttributesConfig{ + WireguardDeviceName: ResourceAttributeConfig{Enabled: true}, + WireguardDeviceType: ResourceAttributeConfig{Enabled: true}, + WireguardPeerName: ResourceAttributeConfig{Enabled: true}, + }, + }, + { + name: "none_set", + want: ResourceAttributesConfig{ + WireguardDeviceName: ResourceAttributeConfig{Enabled: false}, + WireguardDeviceType: ResourceAttributeConfig{Enabled: false}, + WireguardPeerName: ResourceAttributeConfig{Enabled: false}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := loadResourceAttributesConfig(t, tt.name) + if diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{})); diff != "" { + t.Errorf("Config mismatch (-expected +actual):\n%s", diff) + } + }) + } +} + +func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + sub, err := cm.Sub(name) + require.NoError(t, err) + sub, err = sub.Sub("resource_attributes") + require.NoError(t, err) + cfg := DefaultResourceAttributesConfig() + require.NoError(t, component.UnmarshalConfig(sub, &cfg)) + return cfg +} diff --git a/internal/metadata/generated_metrics.go b/internal/metadata/generated_metrics.go new file mode 100644 index 0000000..0f6d430 --- /dev/null +++ b/internal/metadata/generated_metrics.go @@ -0,0 +1,246 @@ +// 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 metricWireguardPeerNetworkIoUsageRxBytes 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 wireguard.peer.network.io.usage.rx_bytes metric with initial data. +func (m *metricWireguardPeerNetworkIoUsageRxBytes) init() { + m.data.SetName("wireguard.peer.network.io.usage.rx_bytes") + m.data.SetDescription("Bytes received by the peer.") + m.data.SetUnit("By") + m.data.SetEmptySum() + m.data.Sum().SetIsMonotonic(true) + m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) +} + +func (m *metricWireguardPeerNetworkIoUsageRxBytes) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) { + if !m.config.Enabled { + return + } + dp := m.data.Sum().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricWireguardPeerNetworkIoUsageRxBytes) 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 *metricWireguardPeerNetworkIoUsageRxBytes) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricWireguardPeerNetworkIoUsageRxBytes(cfg MetricConfig) metricWireguardPeerNetworkIoUsageRxBytes { + m := metricWireguardPeerNetworkIoUsageRxBytes{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + +type metricWireguardPeerNetworkIoUsageTxBytes 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 wireguard.peer.network.io.usage.tx_bytes metric with initial data. +func (m *metricWireguardPeerNetworkIoUsageTxBytes) init() { + m.data.SetName("wireguard.peer.network.io.usage.tx_bytes") + m.data.SetDescription("Bytes sent.") + m.data.SetUnit("By") + m.data.SetEmptySum() + m.data.Sum().SetIsMonotonic(false) + m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityDelta) +} + +func (m *metricWireguardPeerNetworkIoUsageTxBytes) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) { + if !m.config.Enabled { + return + } + dp := m.data.Sum().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricWireguardPeerNetworkIoUsageTxBytes) 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 *metricWireguardPeerNetworkIoUsageTxBytes) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricWireguardPeerNetworkIoUsageTxBytes(cfg MetricConfig) metricWireguardPeerNetworkIoUsageTxBytes { + m := metricWireguardPeerNetworkIoUsageTxBytes{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. + metricWireguardPeerNetworkIoUsageRxBytes metricWireguardPeerNetworkIoUsageRxBytes + metricWireguardPeerNetworkIoUsageTxBytes metricWireguardPeerNetworkIoUsageTxBytes +} + +// metricBuilderOption applies changes to default metrics builder. +type metricBuilderOption func(*MetricsBuilder) + +// WithStartTime sets startTime on the metrics builder. +func WithStartTime(startTime pcommon.Timestamp) metricBuilderOption { + return func(mb *MetricsBuilder) { + mb.startTime = startTime + } +} + +func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.CreateSettings, options ...metricBuilderOption) *MetricsBuilder { + mb := &MetricsBuilder{ + config: mbc, + startTime: pcommon.NewTimestampFromTime(time.Now()), + metricsBuffer: pmetric.NewMetrics(), + buildInfo: settings.BuildInfo, + metricWireguardPeerNetworkIoUsageRxBytes: newMetricWireguardPeerNetworkIoUsageRxBytes(mbc.Metrics.WireguardPeerNetworkIoUsageRxBytes), + metricWireguardPeerNetworkIoUsageTxBytes: newMetricWireguardPeerNetworkIoUsageTxBytes(mbc.Metrics.WireguardPeerNetworkIoUsageTxBytes), + } + for _, op := range options { + op(mb) + } + return mb +} + +// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics. +func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder { + return NewResourceBuilder(mb.config.ResourceAttributes) +} + +// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity. +func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) { + if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() { + mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len() + } +} + +// ResourceMetricsOption applies changes to provided resource metrics. +type ResourceMetricsOption func(pmetric.ResourceMetrics) + +// WithResource sets the provided resource on the emitted ResourceMetrics. +// It's recommended to use ResourceBuilder to create the resource. +func WithResource(res pcommon.Resource) ResourceMetricsOption { + return func(rm pmetric.ResourceMetrics) { + res.CopyTo(rm.Resource()) + } +} + +// WithStartTimeOverride overrides start time for all the resource metrics data points. +// This option should be only used if different start time has to be set on metrics coming from different resources. +func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption { + return func(rm pmetric.ResourceMetrics) { + var dps pmetric.NumberDataPointSlice + metrics := rm.ScopeMetrics().At(0).Metrics() + for i := 0; i < metrics.Len(); i++ { + switch metrics.At(i).Type() { + case pmetric.MetricTypeGauge: + dps = metrics.At(i).Gauge().DataPoints() + case pmetric.MetricTypeSum: + dps = metrics.At(i).Sum().DataPoints() + } + for j := 0; j < dps.Len(); j++ { + dps.At(j).SetStartTimestamp(start) + } + } + } +} + +// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for +// recording another set of data points as part of another resource. This function can be helpful when one scraper +// needs to emit metrics from several resources. Otherwise calling this function is not required, +// just `Emit` function can be called instead. +// Resource attributes should be provided as ResourceMetricsOption arguments. +func (mb *MetricsBuilder) EmitForResource(rmo ...ResourceMetricsOption) { + rm := pmetric.NewResourceMetrics() + ils := rm.ScopeMetrics().AppendEmpty() + ils.Scope().SetName("otelcol/wireguardreceiver") + ils.Scope().SetVersion(mb.buildInfo.Version) + ils.Metrics().EnsureCapacity(mb.metricsCapacity) + mb.metricWireguardPeerNetworkIoUsageRxBytes.emit(ils.Metrics()) + mb.metricWireguardPeerNetworkIoUsageTxBytes.emit(ils.Metrics()) + + for _, op := range rmo { + op(rm) + } + if ils.Metrics().Len() > 0 { + mb.updateCapacity(rm) + rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty()) + } +} + +// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for +// recording another set of metrics. This function will be responsible for applying all the transformations required to +// produce metric representation defined in metadata and user config, e.g. delta or cumulative. +func (mb *MetricsBuilder) Emit(rmo ...ResourceMetricsOption) pmetric.Metrics { + mb.EmitForResource(rmo...) + metrics := mb.metricsBuffer + mb.metricsBuffer = pmetric.NewMetrics() + return metrics +} + +// RecordWireguardPeerNetworkIoUsageRxBytesDataPoint adds a data point to wireguard.peer.network.io.usage.rx_bytes metric. +func (mb *MetricsBuilder) RecordWireguardPeerNetworkIoUsageRxBytesDataPoint(ts pcommon.Timestamp, val int64) { + mb.metricWireguardPeerNetworkIoUsageRxBytes.recordDataPoint(mb.startTime, ts, val) +} + +// RecordWireguardPeerNetworkIoUsageTxBytesDataPoint adds a data point to wireguard.peer.network.io.usage.tx_bytes metric. +func (mb *MetricsBuilder) RecordWireguardPeerNetworkIoUsageTxBytesDataPoint(ts pcommon.Timestamp, val int64) { + mb.metricWireguardPeerNetworkIoUsageTxBytes.recordDataPoint(mb.startTime, ts, val) +} + +// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted, +// and metrics builder should update its startTime and reset it's internal state accordingly. +func (mb *MetricsBuilder) Reset(options ...metricBuilderOption) { + mb.startTime = pcommon.NewTimestampFromTime(time.Now()) + for _, op := range options { + op(mb) + } +} diff --git a/internal/metadata/generated_metrics_test.go b/internal/metadata/generated_metrics_test.go new file mode 100644 index 0000000..0049ece --- /dev/null +++ b/internal/metadata/generated_metrics_test.go @@ -0,0 +1,123 @@ +// 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 testConfigCollection int + +const ( + testSetDefault testConfigCollection = iota + testSetAll + testSetNone +) + +func TestMetricsBuilder(t *testing.T) { + tests := []struct { + name string + configSet testConfigCollection + }{ + { + name: "default", + configSet: testSetDefault, + }, + { + name: "all_set", + configSet: testSetAll, + }, + { + name: "none_set", + configSet: testSetNone, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + start := pcommon.Timestamp(1_000_000_000) + ts := pcommon.Timestamp(1_000_001_000) + observedZapCore, observedLogs := observer.New(zap.WarnLevel) + settings := receivertest.NewNopCreateSettings() + settings.Logger = zap.New(observedZapCore) + mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, test.name), settings, WithStartTime(start)) + + expectedWarnings := 0 + assert.Equal(t, expectedWarnings, observedLogs.Len()) + + defaultMetricsCount := 0 + allMetricsCount := 0 + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordWireguardPeerNetworkIoUsageRxBytesDataPoint(ts, 1) + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordWireguardPeerNetworkIoUsageTxBytesDataPoint(ts, 1) + + rb := mb.NewResourceBuilder() + rb.SetWireguardDeviceName("wireguard.device.name-val") + rb.SetWireguardDeviceTypeLinuxKernel() + rb.SetWireguardPeerName("wireguard.peer.name-val") + res := rb.Emit() + metrics := mb.Emit(WithResource(res)) + + if test.configSet == testSetNone { + assert.Equal(t, 0, metrics.ResourceMetrics().Len()) + return + } + + assert.Equal(t, 1, metrics.ResourceMetrics().Len()) + rm := metrics.ResourceMetrics().At(0) + assert.Equal(t, res, rm.Resource()) + assert.Equal(t, 1, rm.ScopeMetrics().Len()) + ms := rm.ScopeMetrics().At(0).Metrics() + if test.configSet == testSetDefault { + assert.Equal(t, defaultMetricsCount, ms.Len()) + } + if test.configSet == testSetAll { + assert.Equal(t, allMetricsCount, ms.Len()) + } + validatedMetrics := make(map[string]bool) + for i := 0; i < ms.Len(); i++ { + switch ms.At(i).Name() { + case "wireguard.peer.network.io.usage.rx_bytes": + assert.False(t, validatedMetrics["wireguard.peer.network.io.usage.rx_bytes"], "Found a duplicate in the metrics slice: wireguard.peer.network.io.usage.rx_bytes") + validatedMetrics["wireguard.peer.network.io.usage.rx_bytes"] = true + assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len()) + assert.Equal(t, "Bytes received by the peer.", ms.At(i).Description()) + assert.Equal(t, "By", ms.At(i).Unit()) + assert.Equal(t, true, ms.At(i).Sum().IsMonotonic()) + assert.Equal(t, pmetric.AggregationTemporalityCumulative, ms.At(i).Sum().AggregationTemporality()) + dp := ms.At(i).Sum().DataPoints().At(0) + assert.Equal(t, start, dp.StartTimestamp()) + assert.Equal(t, ts, dp.Timestamp()) + assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) + assert.Equal(t, int64(1), dp.IntValue()) + case "wireguard.peer.network.io.usage.tx_bytes": + assert.False(t, validatedMetrics["wireguard.peer.network.io.usage.tx_bytes"], "Found a duplicate in the metrics slice: wireguard.peer.network.io.usage.tx_bytes") + validatedMetrics["wireguard.peer.network.io.usage.tx_bytes"] = true + assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len()) + assert.Equal(t, "Bytes sent.", ms.At(i).Description()) + assert.Equal(t, "By", ms.At(i).Unit()) + assert.Equal(t, false, ms.At(i).Sum().IsMonotonic()) + assert.Equal(t, pmetric.AggregationTemporalityDelta, 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()) + } + } + }) + } +} diff --git a/internal/metadata/generated_resource.go b/internal/metadata/generated_resource.go new file mode 100644 index 0000000..6786a95 --- /dev/null +++ b/internal/metadata/generated_resource.go @@ -0,0 +1,85 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/pdata/pcommon" +) + +// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml. +// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines. +type ResourceBuilder struct { + config ResourceAttributesConfig + res pcommon.Resource +} + +// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application. +func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder { + return &ResourceBuilder{ + config: rac, + res: pcommon.NewResource(), + } +} + +// SetWireguardDeviceName sets provided value as "wireguard.device.name" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceName(val string) { + if rb.config.WireguardDeviceName.Enabled { + rb.res.Attributes().PutStr("wireguard.device.name", val) + } +} + +// SetWireguardDeviceTypeLinuxKernel sets "wireguard.device.type=Linux kernel" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceTypeLinuxKernel() { + if rb.config.WireguardDeviceType.Enabled { + rb.res.Attributes().PutStr("wireguard.device.type", "Linux kernel") + } +} + +// SetWireguardDeviceTypeOpenBSDKernel sets "wireguard.device.type=OpenBSD kernel" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceTypeOpenBSDKernel() { + if rb.config.WireguardDeviceType.Enabled { + rb.res.Attributes().PutStr("wireguard.device.type", "OpenBSD kernel") + } +} + +// SetWireguardDeviceTypeFreeBSDKernel sets "wireguard.device.type=FreeBSD kernel" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceTypeFreeBSDKernel() { + if rb.config.WireguardDeviceType.Enabled { + rb.res.Attributes().PutStr("wireguard.device.type", "FreeBSD kernel") + } +} + +// SetWireguardDeviceTypeWindowsKernel sets "wireguard.device.type=Windows kernel" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceTypeWindowsKernel() { + if rb.config.WireguardDeviceType.Enabled { + rb.res.Attributes().PutStr("wireguard.device.type", "Windows kernel") + } +} + +// SetWireguardDeviceTypeUserspace sets "wireguard.device.type=userspace" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceTypeUserspace() { + if rb.config.WireguardDeviceType.Enabled { + rb.res.Attributes().PutStr("wireguard.device.type", "userspace") + } +} + +// SetWireguardDeviceTypeUnknown sets "wireguard.device.type=unknown" attribute. +func (rb *ResourceBuilder) SetWireguardDeviceTypeUnknown() { + if rb.config.WireguardDeviceType.Enabled { + rb.res.Attributes().PutStr("wireguard.device.type", "unknown") + } +} + +// SetWireguardPeerName sets provided value as "wireguard.peer.name" attribute. +func (rb *ResourceBuilder) SetWireguardPeerName(val string) { + if rb.config.WireguardPeerName.Enabled { + rb.res.Attributes().PutStr("wireguard.peer.name", val) + } +} + +// Emit returns the built resource and resets the internal builder state. +func (rb *ResourceBuilder) Emit() pcommon.Resource { + r := rb.res + rb.res = pcommon.NewResource() + return r +} diff --git a/internal/metadata/generated_resource_test.go b/internal/metadata/generated_resource_test.go new file mode 100644 index 0000000..85124f0 --- /dev/null +++ b/internal/metadata/generated_resource_test.go @@ -0,0 +1,52 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResourceBuilder(t *testing.T) { + for _, test := range []string{"default", "all_set", "none_set"} { + t.Run(test, func(t *testing.T) { + cfg := loadResourceAttributesConfig(t, test) + rb := NewResourceBuilder(cfg) + rb.SetWireguardDeviceName("wireguard.device.name-val") + rb.SetWireguardDeviceTypeLinuxKernel() + rb.SetWireguardPeerName("wireguard.peer.name-val") + + res := rb.Emit() + assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource + + switch test { + case "default": + assert.Equal(t, 3, res.Attributes().Len()) + case "all_set": + assert.Equal(t, 3, res.Attributes().Len()) + case "none_set": + assert.Equal(t, 0, res.Attributes().Len()) + return + default: + assert.Failf(t, "unexpected test case: %s", test) + } + + val, ok := res.Attributes().Get("wireguard.device.name") + assert.True(t, ok) + if ok { + assert.EqualValues(t, "wireguard.device.name-val", val.Str()) + } + val, ok = res.Attributes().Get("wireguard.device.type") + assert.True(t, ok) + if ok { + assert.EqualValues(t, "Linux kernel", val.Str()) + } + val, ok = res.Attributes().Get("wireguard.peer.name") + assert.True(t, ok) + if ok { + assert.EqualValues(t, "wireguard.peer.name-val", val.Str()) + } + }) + } +} diff --git a/internal/metadata/testdata/config.yaml b/internal/metadata/testdata/config.yaml new file mode 100644 index 0000000..008a8ac --- /dev/null +++ b/internal/metadata/testdata/config.yaml @@ -0,0 +1,27 @@ +default: +all_set: + metrics: + wireguard.peer.network.io.usage.rx_bytes: + enabled: true + wireguard.peer.network.io.usage.tx_bytes: + enabled: true + resource_attributes: + wireguard.device.name: + enabled: true + wireguard.device.type: + enabled: true + wireguard.peer.name: + enabled: true +none_set: + metrics: + wireguard.peer.network.io.usage.rx_bytes: + enabled: false + wireguard.peer.network.io.usage.tx_bytes: + enabled: false + resource_attributes: + wireguard.device.name: + enabled: false + wireguard.device.type: + enabled: false + wireguard.peer.name: + enabled: false diff --git a/metadata.yaml b/metadata.yaml index 4805591..2897faf 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -6,3 +6,44 @@ status: development: [metrics] codeowners: active: [rogercoll] + +resource_attributes: + wireguard.device.name: + description: "A Device is a WireGuard device." + type: string + enabled: true + wireguard.device.type: + description: "A DeviceType specifies the underlying implementation of a WireGuard device." + type: string + enum: + - "Linux kernel" + - "OpenBSD kernel" + - "FreeBSD kernel" + - "Windows kernel" + - "userspace" + - "unknown" + enabled: true + wireguard.peer.name: + description: "A Device is a WireGuard device." + type: string + enabled: true + + +metrics: + # Network + wireguard.peer.network.io.usage.rx_bytes: + enabled: true + description: "Bytes received by the peer." + unit: By + sum: + aggregation_temporality: cumulative + value_type: int + monotonic: true + wireguard.peer.network.io.usage.tx_bytes: + enabled: true + description: "Bytes sent." + unit: By + sum: + aggregation_temporality: cumulative + value_type: int + monotonic: true diff --git a/metrics.go b/metrics.go index fcea4b3..43475d5 100644 --- a/metrics.go +++ b/metrics.go @@ -1,52 +1,20 @@ package wireguardreceiver import ( - "fmt" - "time" - + "github.com/rogercoll/wireguardreceiver/internal/metadata" "go.opentelemetry.io/collector/pdata/pcommon" - "go.opentelemetry.io/collector/pdata/pmetric" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func peerToMetrics(ts time.Time, deviceName string, peer *wgtypes.Peer) pmetric.Metrics { - pbts := pcommon.NewTimestampFromTime(ts) - - md := pmetric.NewMetrics() - - rs := md.ResourceMetrics().AppendEmpty() - - resourceAttr := rs.Resource().Attributes() - resourceAttr.PutStr("peer.device.name", deviceName) - resourceAttr.PutStr("peer.name", peer.PublicKey.String()) - ms := rs.ScopeMetrics().AppendEmpty().Metrics() - appendPeerMetrics(ms, peer, pbts) - - return md -} +func recordPeerMetrics(mb *metadata.MetricsBuilder, now pcommon.Timestamp, deviceName string, peer *wgtypes.Peer) { + // set metrics + mb.RecordWireguardPeerNetworkIoUsageRxBytesDataPoint(now, peer.ReceiveBytes) + mb.RecordWireguardPeerNetworkIoUsageTxBytesDataPoint(now, peer.TransmitBytes) + // Always-present resource attrs + rb := mb.NewResourceBuilder() -func appendPeerMetrics(ms pmetric.MetricSlice, peer *wgtypes.Peer, ts pcommon.Timestamp) { - gaugeI(ms, "usage.rx_bytes", "By", peer.ReceiveBytes, ts) - gaugeI(ms, "usage.tx_bytes", "By", peer.TransmitBytes, ts) - gaugeI(ms, "last_handshake", "s", int64(peer.LastHandshakeTime.Second()), ts) -} - -func initMetric(ms pmetric.MetricSlice, name, unit string) pmetric.Metric { - m := ms.AppendEmpty() - m.SetName(fmt.Sprintf("peer.%s", name)) - m.SetUnit(unit) - return m -} - -func gauge(ms pmetric.MetricSlice, metricName string, unit string) pmetric.NumberDataPointSlice { - metric := initMetric(ms, metricName, unit) - gauge := metric.SetEmptyGauge() - return gauge.DataPoints() -} + rb.SetWireguardDeviceName(deviceName) + rb.SetWireguardPeerName(peer.PublicKey.String()) -func gaugeI(ms pmetric.MetricSlice, metricName string, unit string, value int64, ts pcommon.Timestamp) { - dataPoints := gauge(ms, metricName, unit) - dataPoint := dataPoints.AppendEmpty() - dataPoint.SetTimestamp(ts) - dataPoint.SetIntValue(value) + mb.Emit(metadata.WithResource(rb.Emit())) } diff --git a/metrics_test.go b/metrics_test.go index 42af0df..f956d2c 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -4,19 +4,21 @@ import ( "testing" "time" + "github.com/rogercoll/wireguardreceiver/internal/metadata" "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver/receivertest" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) func TestConvertPeerToMetrics(t *testing.T) { - ts := time.Now() - peer, err := getPeer() assert.Nil(t, err) - md := peerToMetrics(ts, "wg0", peer) - assertPeerToMetrics(t, peer, md) + mb := metadata.NewMetricsBuilder(metadata.DefaultMetricsBuilderConfig(), receivertest.NewNopCreateSettings()) + recordPeerMetrics(mb, pcommon.NewTimestampFromTime(time.Now()), "wg0", peer) + assertPeerToMetrics(t, peer, mb.Emit()) } func assertPeerToMetrics(t *testing.T, peer *wgtypes.Peer, md pmetric.Metrics) { @@ -24,9 +26,10 @@ func assertPeerToMetrics(t *testing.T, peer *wgtypes.Peer, md pmetric.Metrics) { rsm := md.ResourceMetrics().At(0) resourceAttrs := map[string]string{ - "peer.name": "aPxGwq8zERHQ3Q1cOZFdJ+cvJX5Ka4mLN38AyYKYF10=", - "peer.device.name": "wg0", + "wireguard.peer.name": "aPxGwq8zERHQ3Q1cOZFdJ+cvJX5Ka4mLN38AyYKYF10=", + "wireguard.device.name": "wg0", } + for k, v := range resourceAttrs { attr, exists := rsm.Resource().Attributes().Get(k) assert.True(t, exists) @@ -36,7 +39,7 @@ func assertPeerToMetrics(t *testing.T, peer *wgtypes.Peer, md pmetric.Metrics) { assert.Equal(t, rsm.ScopeMetrics().Len(), 1) metrics := rsm.ScopeMetrics().At(0).Metrics() - assert.Equal(t, metrics.Len(), 3) + assert.Equal(t, metrics.Len(), 2) } func getPeer() (*wgtypes.Peer, error) { diff --git a/receiver.go b/receiver.go index ed76a2a..0eec1b6 100644 --- a/receiver.go +++ b/receiver.go @@ -7,6 +7,7 @@ import ( "github.com/rogercoll/wireguardreceiver/internal/metadata" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.opentelemetry.io/collector/receiver" @@ -17,6 +18,7 @@ type wgreceiver struct { config *Config wgClient wireguardClient clientFactory clientFactory + mb *metadata.MetricsBuilder } func newReceiver(config *Config, set receiver.CreateSettings, nextConsumer consumer.Metrics, clientFactory clientFactory) (receiver.Metrics, error) { @@ -32,6 +34,7 @@ func newReceiver(config *Config, set receiver.CreateSettings, nextConsumer consu recv := &wgreceiver{ config: config, clientFactory: clientFactory, + mb: metadata.NewMetricsBuilder(config.MetricsBuilderConfig, set), } scrp, err := scraperhelper.NewScraper(metadata.Type, recv.scrape, scraperhelper.WithStart(recv.start)) @@ -52,18 +55,18 @@ func (r *wgreceiver) start(_ context.Context, _ component.Host) error { } func (r *wgreceiver) scrape(ctx context.Context) (pmetric.Metrics, error) { - md := pmetric.NewMetrics() devices, err := r.wgClient.Devices() if err != nil { - return md, err + return r.mb.Emit(), err } + now := pcommon.NewTimestampFromTime(time.Now()) for _, d := range devices { for _, peer := range d.Peers { - peerToMetrics(time.Now(), d.Name, &peer).ResourceMetrics().CopyTo(md.ResourceMetrics()) + recordPeerMetrics(r.mb, now, d.Name, &peer) } } - return md, nil + return r.mb.Emit(), nil } diff --git a/receiver_test.go b/receiver_test.go index dee43cf..8e2b599 100644 --- a/receiver_test.go +++ b/receiver_test.go @@ -49,7 +49,7 @@ func TestScrape(t *testing.T) { assert.NoError(t, r.Start(context.Background(), componenttest.NewNopHost())) md := <-consumer - assert.Equal(t, md.ResourceMetrics().Len(), 1) + assert.Equal(t, 1, md.ResourceMetrics().Len()) assert.NoError(t, r.Shutdown(context.Background())) }