From 7216fdb611a08312d369040f05b8b3b2c3561514 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 17:36:21 -0700 Subject: [PATCH 1/7] wip --- .../internal/resourcequota/doc.go | 6 + .../internal/resourcequota/documentation.md | 49 ++++ .../internal/metadata/generated_config.go | 80 ++++++ .../metadata/generated_config_test.go | 72 +++++ .../internal/metadata/generated_metrics.go | 267 ++++++++++++++++++ .../metadata/generated_metrics_test.go | 145 ++++++++++ .../internal/metadata/testdata/config.yaml | 27 ++ .../internal/resourcequota/metadata.yaml | 44 +++ .../internal/resourcequota/resourcequotas.go | 73 +---- 9 files changed, 705 insertions(+), 58 deletions(-) create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/doc.go create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/documentation.md create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/doc.go b/receiver/k8sclusterreceiver/internal/resourcequota/doc.go new file mode 100644 index 000000000000..a499c8f7f3f3 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/doc.go @@ -0,0 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:generate mdatagen metadata.yaml + +package resourcequota // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/resourcequota" diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md b/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md new file mode 100644 index 000000000000..fb46466e9606 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md @@ -0,0 +1,49 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# k8s/hpa + +## Default Metrics + +The following metrics are emitted by default. Each of them can be disabled by applying the following configuration: + +```yaml +metrics: + : + enabled: false +``` + +### k8s.resource_quota.hard_limit + +The upper limit for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| 1 | Gauge | Int | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| resource | the name of the resource on which the quota is applied | Any Str | + +### k8s.resource_quota.used + +The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| 1 | Gauge | Int | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| resource | the name of the resource on which the quota is applied | Any Str | + +## Resource Attributes + +| Name | Description | Values | Enabled | +| ---- | ----------- | ------ | ------- | +| k8s.namespace.name | The name of the namespace that the pod is running in. | Any Str | true | +| k8s.resourcequota.name | The k8s resourcequota name. | Any Str | true | +| k8s.resourcequota.uid | The k8s resourcequota uid. | Any Str | true | diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go new file mode 100644 index 000000000000..11a70e9cf2a6 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/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 k8s/hpa metrics. +type MetricsConfig struct { + K8sResourceQuotaHardLimit MetricConfig `mapstructure:"k8s.resource_quota.hard_limit"` + K8sResourceQuotaUsed MetricConfig `mapstructure:"k8s.resource_quota.used"` +} + +func DefaultMetricsConfig() MetricsConfig { + return MetricsConfig{ + K8sResourceQuotaHardLimit: MetricConfig{ + Enabled: true, + }, + K8sResourceQuotaUsed: MetricConfig{ + Enabled: true, + }, + } +} + +// ResourceAttributeConfig provides common config for a particular resource attribute. +type ResourceAttributeConfig struct { + Enabled bool `mapstructure:"enabled"` +} + +// ResourceAttributesConfig provides config for k8s/hpa resource attributes. +type ResourceAttributesConfig struct { + K8sNamespaceName ResourceAttributeConfig `mapstructure:"k8s.namespace.name"` + K8sResourcequotaName ResourceAttributeConfig `mapstructure:"k8s.resourcequota.name"` + K8sResourcequotaUID ResourceAttributeConfig `mapstructure:"k8s.resourcequota.uid"` +} + +func DefaultResourceAttributesConfig() ResourceAttributesConfig { + return ResourceAttributesConfig{ + K8sNamespaceName: ResourceAttributeConfig{ + Enabled: true, + }, + K8sResourcequotaName: ResourceAttributeConfig{ + Enabled: true, + }, + K8sResourcequotaUID: ResourceAttributeConfig{ + Enabled: true, + }, + } +} + +// MetricsBuilderConfig is a configuration for k8s/hpa 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/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go new file mode 100644 index 000000000000..773e7e9f8086 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go @@ -0,0 +1,72 @@ +// 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{ + K8sResourceQuotaHardLimit: MetricConfig{Enabled: true}, + K8sResourceQuotaUsed: MetricConfig{Enabled: true}, + }, + ResourceAttributes: ResourceAttributesConfig{ + K8sNamespaceName: ResourceAttributeConfig{Enabled: true}, + K8sResourcequotaName: ResourceAttributeConfig{Enabled: true}, + K8sResourcequotaUID: ResourceAttributeConfig{Enabled: true}, + }, + }, + }, + { + name: "none_set", + want: MetricsBuilderConfig{ + Metrics: MetricsConfig{ + K8sResourceQuotaHardLimit: MetricConfig{Enabled: false}, + K8sResourceQuotaUsed: MetricConfig{Enabled: false}, + }, + ResourceAttributes: ResourceAttributesConfig{ + K8sNamespaceName: ResourceAttributeConfig{Enabled: false}, + K8sResourcequotaName: ResourceAttributeConfig{Enabled: false}, + K8sResourcequotaUID: 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 +} diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go new file mode 100644 index 000000000000..7474e9fa55ff --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go @@ -0,0 +1,267 @@ +// 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" + conventions "go.opentelemetry.io/collector/semconv/v1.18.0" +) + +type metricK8sResourceQuotaHardLimit 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 k8s.resource_quota.hard_limit metric with initial data. +func (m *metricK8sResourceQuotaHardLimit) init() { + m.data.SetName("k8s.resource_quota.hard_limit") + m.data.SetDescription("The upper limit for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores") + m.data.SetUnit("1") + m.data.SetEmptyGauge() + m.data.Gauge().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricK8sResourceQuotaHardLimit) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, resourceAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("resource", resourceAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricK8sResourceQuotaHardLimit) 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 *metricK8sResourceQuotaHardLimit) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricK8sResourceQuotaHardLimit(cfg MetricConfig) metricK8sResourceQuotaHardLimit { + m := metricK8sResourceQuotaHardLimit{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + +type metricK8sResourceQuotaUsed 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 k8s.resource_quota.used metric with initial data. +func (m *metricK8sResourceQuotaUsed) init() { + m.data.SetName("k8s.resource_quota.used") + m.data.SetDescription("The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores") + m.data.SetUnit("1") + m.data.SetEmptyGauge() + m.data.Gauge().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricK8sResourceQuotaUsed) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, resourceAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("resource", resourceAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricK8sResourceQuotaUsed) 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 *metricK8sResourceQuotaUsed) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricK8sResourceQuotaUsed(cfg MetricConfig) metricK8sResourceQuotaUsed { + m := metricK8sResourceQuotaUsed{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 { + startTime pcommon.Timestamp // start time that will be applied to all recorded data points. + metricsCapacity int // maximum observed number of metrics per resource. + resourceCapacity int // maximum observed number of resource attributes. + metricsBuffer pmetric.Metrics // accumulates metrics data before emitting. + buildInfo component.BuildInfo // contains version information + resourceAttributesConfig ResourceAttributesConfig + metricK8sResourceQuotaHardLimit metricK8sResourceQuotaHardLimit + metricK8sResourceQuotaUsed metricK8sResourceQuotaUsed +} + +// 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{ + startTime: pcommon.NewTimestampFromTime(time.Now()), + metricsBuffer: pmetric.NewMetrics(), + buildInfo: settings.BuildInfo, + resourceAttributesConfig: mbc.ResourceAttributes, + metricK8sResourceQuotaHardLimit: newMetricK8sResourceQuotaHardLimit(mbc.Metrics.K8sResourceQuotaHardLimit), + metricK8sResourceQuotaUsed: newMetricK8sResourceQuotaUsed(mbc.Metrics.K8sResourceQuotaUsed), + } + for _, op := range options { + op(mb) + } + return mb +} + +// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity. +func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) { + if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() { + mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len() + } + if mb.resourceCapacity < rm.Resource().Attributes().Len() { + mb.resourceCapacity = rm.Resource().Attributes().Len() + } +} + +// ResourceMetricsOption applies changes to provided resource metrics. +type ResourceMetricsOption func(ResourceAttributesConfig, pmetric.ResourceMetrics) + +// WithK8sNamespaceName sets provided value as "k8s.namespace.name" attribute for current resource. +func WithK8sNamespaceName(val string) ResourceMetricsOption { + return func(rac ResourceAttributesConfig, rm pmetric.ResourceMetrics) { + if rac.K8sNamespaceName.Enabled { + rm.Resource().Attributes().PutStr("k8s.namespace.name", val) + } + } +} + +// WithK8sResourcequotaName sets provided value as "k8s.resourcequota.name" attribute for current resource. +func WithK8sResourcequotaName(val string) ResourceMetricsOption { + return func(rac ResourceAttributesConfig, rm pmetric.ResourceMetrics) { + if rac.K8sResourcequotaName.Enabled { + rm.Resource().Attributes().PutStr("k8s.resourcequota.name", val) + } + } +} + +// WithK8sResourcequotaUID sets provided value as "k8s.resourcequota.uid" attribute for current resource. +func WithK8sResourcequotaUID(val string) ResourceMetricsOption { + return func(rac ResourceAttributesConfig, rm pmetric.ResourceMetrics) { + if rac.K8sResourcequotaUID.Enabled { + rm.Resource().Attributes().PutStr("k8s.resourcequota.uid", val) + } + } +} + +// 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(_ ResourceAttributesConfig, 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() + rm.SetSchemaUrl(conventions.SchemaURL) + rm.Resource().Attributes().EnsureCapacity(mb.resourceCapacity) + ils := rm.ScopeMetrics().AppendEmpty() + ils.Scope().SetName("otelcol/k8sclusterreceiver") + ils.Scope().SetVersion(mb.buildInfo.Version) + ils.Metrics().EnsureCapacity(mb.metricsCapacity) + mb.metricK8sResourceQuotaHardLimit.emit(ils.Metrics()) + mb.metricK8sResourceQuotaUsed.emit(ils.Metrics()) + + for _, op := range rmo { + op(mb.resourceAttributesConfig, 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 +} + +// RecordK8sResourceQuotaHardLimitDataPoint adds a data point to k8s.resource_quota.hard_limit metric. +func (mb *MetricsBuilder) RecordK8sResourceQuotaHardLimitDataPoint(ts pcommon.Timestamp, val int64, resourceAttributeValue string) { + mb.metricK8sResourceQuotaHardLimit.recordDataPoint(mb.startTime, ts, val, resourceAttributeValue) +} + +// RecordK8sResourceQuotaUsedDataPoint adds a data point to k8s.resource_quota.used metric. +func (mb *MetricsBuilder) RecordK8sResourceQuotaUsedDataPoint(ts pcommon.Timestamp, val int64, resourceAttributeValue string) { + mb.metricK8sResourceQuotaUsed.recordDataPoint(mb.startTime, ts, val, resourceAttributeValue) +} + +// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted, +// and metrics builder should update its startTime and reset it's internal state accordingly. +func (mb *MetricsBuilder) Reset(options ...metricBuilderOption) { + mb.startTime = pcommon.NewTimestampFromTime(time.Now()) + for _, op := range options { + op(mb) + } +} diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go new file mode 100644 index 000000000000..4515bd6d67d9 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go @@ -0,0 +1,145 @@ +// 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.RecordK8sResourceQuotaHardLimitDataPoint(ts, 1, "attr-val") + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordK8sResourceQuotaUsedDataPoint(ts, 1, "attr-val") + + metrics := mb.Emit(WithK8sNamespaceName("attr-val"), WithK8sResourcequotaName("attr-val"), WithK8sResourcequotaUID("attr-val")) + + if test.configSet == testSetNone { + assert.Equal(t, 0, metrics.ResourceMetrics().Len()) + return + } + + assert.Equal(t, 1, metrics.ResourceMetrics().Len()) + rm := metrics.ResourceMetrics().At(0) + attrCount := 0 + enabledAttrCount := 0 + attrVal, ok := rm.Resource().Attributes().Get("k8s.namespace.name") + attrCount++ + assert.Equal(t, mb.resourceAttributesConfig.K8sNamespaceName.Enabled, ok) + if mb.resourceAttributesConfig.K8sNamespaceName.Enabled { + enabledAttrCount++ + assert.EqualValues(t, "attr-val", attrVal.Str()) + } + attrVal, ok = rm.Resource().Attributes().Get("k8s.resourcequota.name") + attrCount++ + assert.Equal(t, mb.resourceAttributesConfig.K8sResourcequotaName.Enabled, ok) + if mb.resourceAttributesConfig.K8sResourcequotaName.Enabled { + enabledAttrCount++ + assert.EqualValues(t, "attr-val", attrVal.Str()) + } + attrVal, ok = rm.Resource().Attributes().Get("k8s.resourcequota.uid") + attrCount++ + assert.Equal(t, mb.resourceAttributesConfig.K8sResourcequotaUID.Enabled, ok) + if mb.resourceAttributesConfig.K8sResourcequotaUID.Enabled { + enabledAttrCount++ + assert.EqualValues(t, "attr-val", attrVal.Str()) + } + assert.Equal(t, enabledAttrCount, rm.Resource().Attributes().Len()) + assert.Equal(t, attrCount, 3) + + 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 "k8s.resource_quota.hard_limit": + assert.False(t, validatedMetrics["k8s.resource_quota.hard_limit"], "Found a duplicate in the metrics slice: k8s.resource_quota.hard_limit") + validatedMetrics["k8s.resource_quota.hard_limit"] = true + assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len()) + assert.Equal(t, "The upper limit for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores", 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("resource") + assert.True(t, ok) + assert.EqualValues(t, "attr-val", attrVal.Str()) + case "k8s.resource_quota.used": + assert.False(t, validatedMetrics["k8s.resource_quota.used"], "Found a duplicate in the metrics slice: k8s.resource_quota.used") + validatedMetrics["k8s.resource_quota.used"] = true + assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len()) + assert.Equal(t, "The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores", 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("resource") + assert.True(t, ok) + assert.EqualValues(t, "attr-val", attrVal.Str()) + } + } + }) + } +} diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml new file mode 100644 index 000000000000..75dd26272f17 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml @@ -0,0 +1,27 @@ +default: +all_set: + metrics: + k8s.resource_quota.hard_limit: + enabled: true + k8s.resource_quota.used: + enabled: true + resource_attributes: + k8s.namespace.name: + enabled: true + k8s.resourcequota.name: + enabled: true + k8s.resourcequota.uid: + enabled: true +none_set: + metrics: + k8s.resource_quota.hard_limit: + enabled: false + k8s.resource_quota.used: + enabled: false + resource_attributes: + k8s.namespace.name: + enabled: false + k8s.resourcequota.name: + enabled: false + k8s.resourcequota.uid: + enabled: false diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml new file mode 100644 index 000000000000..000e4e9d6562 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml @@ -0,0 +1,44 @@ +type: k8s/hpa + +sem_conv_version: 1.18.0 + +resource_attributes: + k8s.resourcequota.uid: + description: The k8s resourcequota uid. + type: string + enabled: true + + k8s.resourcequota.name: + description: The k8s resourcequota name. + type: string + enabled: true + + k8s.namespace.name: + description: The name of the namespace that the pod is running in. + type: string + enabled: true + +attributes: + resource: + description: the name of the resource on which the quota is applied + type: string + enabled: true + +metrics: + k8s.resource_quota.hard_limit: + enabled: true + description: The upper limit for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores + unit: 1 + gauge: + value_type: int + attributes: + - resource + + k8s.resource_quota.used: + enabled: true + description: The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores + unit: 1 + gauge: + value_type: int + attributes: + - resource diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go index 6616c0ef0cc2..3f0e2aa3116c 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go @@ -5,50 +5,30 @@ package resourcequota // import "github.com/open-telemetry/opentelemetry-collect import ( "strings" + "time" - agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1" - metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" - resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" - conventions "go.opentelemetry.io/collector/semconv/v1.6.1" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" corev1 "k8s.io/api/core/v1" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/constants" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/utils" + imetadata "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata" ) -var resourceQuotaHardLimitMetric = &metricspb.MetricDescriptor{ - Name: "k8s.resource_quota.hard_limit", - Description: "The upper limit for a particular resource in a specific namespace." + - " Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores", - Type: metricspb.MetricDescriptor_GAUGE_INT64, - LabelKeys: []*metricspb.LabelKey{{ - Key: "resource", - }}, -} - -var resourceQuotaUsedMetric = &metricspb.MetricDescriptor{ - Name: "k8s.resource_quota.used", - Description: "The usage for a particular resource in a specific namespace." + - " Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores", - Type: metricspb.MetricDescriptor_GAUGE_INT64, - LabelKeys: []*metricspb.LabelKey{{ - Key: "resource", - }}, -} - -func GetMetrics(rq *corev1.ResourceQuota) []*agentmetricspb.ExportMetricsServiceRequest { - var metrics []*metricspb.Metric +func GetMetrics(set receiver.CreateSettings, rq *corev1.ResourceQuota) pmetric.Metrics { + mb := imetadata.NewMetricsBuilder(imetadata.DefaultMetricsBuilderConfig(), set) + ts := pcommon.NewTimestampFromTime(time.Now()) for _, t := range []struct { - metric *metricspb.MetricDescriptor - rl corev1.ResourceList + recordFn func(ts pcommon.Timestamp, val int64, resource string) + rl corev1.ResourceList }{ { - resourceQuotaHardLimitMetric, + mb.RecordK8sResourceQuotaHardLimitDataPoint, rq.Status.Hard, }, { - resourceQuotaUsedMetric, + mb.RecordK8sResourceQuotaUsedDataPoint, rq.Status.Used, }, } { @@ -58,33 +38,10 @@ func GetMetrics(rq *corev1.ResourceQuota) []*agentmetricspb.ExportMetricsService if strings.HasSuffix(string(k), ".cpu") { val = v.MilliValue() } - - metrics = append(metrics, - &metricspb.Metric{ - MetricDescriptor: t.metric, - Timeseries: []*metricspb.TimeSeries{ - utils.GetInt64TimeSeriesWithLabels(val, []*metricspb.LabelValue{{Value: string(k), HasValue: true}}), - }, - }, - ) + mb.RecordK8sResourceQuotaUsedDataPoint(ts, val, string(k)) + mb.RecordK8sResourceQuotaHardLimitDataPoint(ts, val, string(k)) } } - return []*agentmetricspb.ExportMetricsServiceRequest{ - { - Resource: getResource(rq), - Metrics: metrics, - }, - } -} - -func getResource(rq *corev1.ResourceQuota) *resourcepb.Resource { - return &resourcepb.Resource{ - Type: constants.K8sType, - Labels: map[string]string{ - constants.K8sKeyResourceQuotaUID: string(rq.UID), - constants.K8sKeyResourceQuotaName: rq.Name, - conventions.AttributeK8SNamespaceName: rq.Namespace, - }, - } + return mb.Emit(imetadata.WithK8sResourcequotaUID(string(rq.UID)), imetadata.WithK8sResourcequotaName(rq.Name), imetadata.WithK8sNamespaceName(rq.Namespace)) } From 04c0f87b29cf26fd5b729bde1aa29f538972d95c Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 17:55:49 -0700 Subject: [PATCH 2/7] wip --- .../internal/resourcequota/documentation.md | 1 + .../internal/metadata/generated_config.go | 10 +++-- .../metadata/generated_config_test.go | 14 ++++--- .../internal/metadata/generated_metrics.go | 9 ++++ .../metadata/generated_metrics_test.go | 11 ++++- .../internal/metadata/testdata/config.yaml | 4 ++ .../internal/resourcequota/metadata.yaml | 5 +++ .../internal/resourcequota/resourcequotas.go | 2 +- .../resourcequota/resourcequotas_test.go | 39 ++++++++---------- .../resourcequota/testdata/expected.yaml | 41 +++++++++++++++++++ 10 files changed, 102 insertions(+), 34 deletions(-) create mode 100644 receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md b/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md index fb46466e9606..f72ee1a41eab 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md +++ b/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md @@ -47,3 +47,4 @@ The usage for a particular resource in a specific namespace. Will only be sent i | k8s.namespace.name | The name of the namespace that the pod is running in. | Any Str | true | | k8s.resourcequota.name | The k8s resourcequota name. | Any Str | true | | k8s.resourcequota.uid | The k8s resourcequota uid. | Any Str | true | +| opencensus.resourcetype | The OpenCensus resource type. | Any Str | true | diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go index 11a70e9cf2a6..409354cfb2ad 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go @@ -47,9 +47,10 @@ type ResourceAttributeConfig struct { // ResourceAttributesConfig provides config for k8s/hpa resource attributes. type ResourceAttributesConfig struct { - K8sNamespaceName ResourceAttributeConfig `mapstructure:"k8s.namespace.name"` - K8sResourcequotaName ResourceAttributeConfig `mapstructure:"k8s.resourcequota.name"` - K8sResourcequotaUID ResourceAttributeConfig `mapstructure:"k8s.resourcequota.uid"` + K8sNamespaceName ResourceAttributeConfig `mapstructure:"k8s.namespace.name"` + K8sResourcequotaName ResourceAttributeConfig `mapstructure:"k8s.resourcequota.name"` + K8sResourcequotaUID ResourceAttributeConfig `mapstructure:"k8s.resourcequota.uid"` + OpencensusResourcetype ResourceAttributeConfig `mapstructure:"opencensus.resourcetype"` } func DefaultResourceAttributesConfig() ResourceAttributesConfig { @@ -63,6 +64,9 @@ func DefaultResourceAttributesConfig() ResourceAttributesConfig { K8sResourcequotaUID: ResourceAttributeConfig{ Enabled: true, }, + OpencensusResourcetype: ResourceAttributeConfig{ + Enabled: true, + }, } } diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go index 773e7e9f8086..0c8cac3a4744 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config_test.go @@ -30,9 +30,10 @@ func TestMetricsBuilderConfig(t *testing.T) { K8sResourceQuotaUsed: MetricConfig{Enabled: true}, }, ResourceAttributes: ResourceAttributesConfig{ - K8sNamespaceName: ResourceAttributeConfig{Enabled: true}, - K8sResourcequotaName: ResourceAttributeConfig{Enabled: true}, - K8sResourcequotaUID: ResourceAttributeConfig{Enabled: true}, + K8sNamespaceName: ResourceAttributeConfig{Enabled: true}, + K8sResourcequotaName: ResourceAttributeConfig{Enabled: true}, + K8sResourcequotaUID: ResourceAttributeConfig{Enabled: true}, + OpencensusResourcetype: ResourceAttributeConfig{Enabled: true}, }, }, }, @@ -44,9 +45,10 @@ func TestMetricsBuilderConfig(t *testing.T) { K8sResourceQuotaUsed: MetricConfig{Enabled: false}, }, ResourceAttributes: ResourceAttributesConfig{ - K8sNamespaceName: ResourceAttributeConfig{Enabled: false}, - K8sResourcequotaName: ResourceAttributeConfig{Enabled: false}, - K8sResourcequotaUID: ResourceAttributeConfig{Enabled: false}, + K8sNamespaceName: ResourceAttributeConfig{Enabled: false}, + K8sResourcequotaName: ResourceAttributeConfig{Enabled: false}, + K8sResourcequotaUID: ResourceAttributeConfig{Enabled: false}, + OpencensusResourcetype: ResourceAttributeConfig{Enabled: false}, }, }, }, diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go index 7474e9fa55ff..9906fb6f256a 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics.go @@ -192,6 +192,15 @@ func WithK8sResourcequotaUID(val string) ResourceMetricsOption { } } +// WithOpencensusResourcetype sets provided value as "opencensus.resourcetype" attribute for current resource. +func WithOpencensusResourcetype(val string) ResourceMetricsOption { + return func(rac ResourceAttributesConfig, rm pmetric.ResourceMetrics) { + if rac.OpencensusResourcetype.Enabled { + rm.Resource().Attributes().PutStr("opencensus.resourcetype", val) + } + } +} + // 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 { diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go index 4515bd6d67d9..d80473983699 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_metrics_test.go @@ -62,7 +62,7 @@ func TestMetricsBuilder(t *testing.T) { allMetricsCount++ mb.RecordK8sResourceQuotaUsedDataPoint(ts, 1, "attr-val") - metrics := mb.Emit(WithK8sNamespaceName("attr-val"), WithK8sResourcequotaName("attr-val"), WithK8sResourcequotaUID("attr-val")) + metrics := mb.Emit(WithK8sNamespaceName("attr-val"), WithK8sResourcequotaName("attr-val"), WithK8sResourcequotaUID("attr-val"), WithOpencensusResourcetype("attr-val")) if test.configSet == testSetNone { assert.Equal(t, 0, metrics.ResourceMetrics().Len()) @@ -94,8 +94,15 @@ func TestMetricsBuilder(t *testing.T) { enabledAttrCount++ assert.EqualValues(t, "attr-val", attrVal.Str()) } + attrVal, ok = rm.Resource().Attributes().Get("opencensus.resourcetype") + attrCount++ + assert.Equal(t, mb.resourceAttributesConfig.OpencensusResourcetype.Enabled, ok) + if mb.resourceAttributesConfig.OpencensusResourcetype.Enabled { + enabledAttrCount++ + assert.EqualValues(t, "attr-val", attrVal.Str()) + } assert.Equal(t, enabledAttrCount, rm.Resource().Attributes().Len()) - assert.Equal(t, attrCount, 3) + assert.Equal(t, attrCount, 4) assert.Equal(t, 1, rm.ScopeMetrics().Len()) ms := rm.ScopeMetrics().At(0).Metrics() diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml index 75dd26272f17..72c6ed1f46e9 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/testdata/config.yaml @@ -12,6 +12,8 @@ all_set: enabled: true k8s.resourcequota.uid: enabled: true + opencensus.resourcetype: + enabled: true none_set: metrics: k8s.resource_quota.hard_limit: @@ -25,3 +27,5 @@ none_set: enabled: false k8s.resourcequota.uid: enabled: false + opencensus.resourcetype: + enabled: false diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml index 000e4e9d6562..6744176998d3 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml +++ b/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml @@ -18,6 +18,11 @@ resource_attributes: type: string enabled: true + opencensus.resourcetype: + description: The OpenCensus resource type. + type: string + enabled: true + attributes: resource: description: the name of the resource on which the quota is applied diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go index 3f0e2aa3116c..e6478a625529 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go @@ -43,5 +43,5 @@ func GetMetrics(set receiver.CreateSettings, rq *corev1.ResourceQuota) pmetric.M } } - return mb.Emit(imetadata.WithK8sResourcequotaUID(string(rq.UID)), imetadata.WithK8sResourcequotaName(rq.Name), imetadata.WithK8sNamespaceName(rq.Namespace)) + return mb.Emit(imetadata.WithK8sResourcequotaUID(string(rq.UID)), imetadata.WithK8sResourcequotaName(rq.Name), imetadata.WithK8sNamespaceName(rq.Namespace), imetadata.WithOpencensusResourcetype("k8s")) } diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go index 5fe185d896a5..b49d031ac500 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go @@ -4,40 +4,35 @@ package resourcequota import ( + "path/filepath" "testing" - metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/golden" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/receiver/receivertest" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/constants" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/testutils" ) func TestRequestQuotaMetrics(t *testing.T) { rq := newResourceQuota("1") - - actualResourceMetrics := GetMetrics(rq) - - require.Equal(t, 1, len(actualResourceMetrics)) - - require.Equal(t, 2, len(actualResourceMetrics[0].Metrics)) - testutils.AssertResource(t, actualResourceMetrics[0].Resource, constants.K8sType, - map[string]string{ - "k8s.resourcequota.uid": "test-resourcequota-1-uid", - "k8s.resourcequota.name": "test-resourcequota-1", - "k8s.namespace.name": "test-namespace", - }, + m := GetMetrics(receivertest.NewNopCreateSettings(), rq) + + expected, err := golden.ReadMetrics(filepath.Join("testdata", "expected.yaml")) + require.NoError(t, err) + require.NoError(t, pmetrictest.CompareMetrics(expected, m, + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + pmetrictest.IgnoreResourceMetricsOrder(), + pmetrictest.IgnoreMetricsOrder(), + pmetrictest.IgnoreScopeMetricsOrder(), + pmetrictest.IgnoreMetricDataPointsOrder(), + ), ) - - testutils.AssertMetricsWithLabels(t, actualResourceMetrics[0].Metrics[0], "k8s.resource_quota.hard_limit", - metricspb.MetricDescriptor_GAUGE_INT64, map[string]string{"resource": "requests.cpu"}, 2000) - - testutils.AssertMetricsWithLabels(t, actualResourceMetrics[0].Metrics[1], "k8s.resource_quota.used", - metricspb.MetricDescriptor_GAUGE_INT64, map[string]string{"resource": "requests.cpu"}, 1000) } func newResourceQuota(id string) *corev1.ResourceQuota { diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml new file mode 100644 index 000000000000..c484f18a7ad8 --- /dev/null +++ b/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml @@ -0,0 +1,41 @@ +resourceMetrics: + - resource: + attributes: + - key: k8s.namespace.name + value: + stringValue: test-namespace + - key: k8s.resourcequota.name + value: + stringValue: test-resourcequota-1 + - key: k8s.resourcequota.uid + value: + stringValue: test-resourcequota-1-uid + - key: opencensus.resourcetype + value: + stringValue: k8s + schemaUrl: https://opentelemetry.io/schemas/1.18.0 + scopeMetrics: + - metrics: + - description: The upper limit for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores + unit: "1" + gauge: + dataPoints: + - asInt: "2000" + attributes: + - key: resource + value: + stringValue: requests.cpu + name: k8s.resource_quota.hard_limit + - description: The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores + unit: "1" + gauge: + dataPoints: + - asInt: "1000" + attributes: + - key: resource + value: + stringValue: requests.cpu + name: k8s.resource_quota.used + scope: + name: otelcol/k8sclusterreceiver + version: latest From 8ca8e5187b19530732e8434fbc7381d362ff3077 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 18:02:22 -0700 Subject: [PATCH 3/7] wip --- .../internal/resourcequota/resourcequotas_test.go | 1 - .../internal/resourcequota/testdata/expected.yaml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go index b49d031ac500..6b4786624552 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go @@ -30,7 +30,6 @@ func TestRequestQuotaMetrics(t *testing.T) { pmetrictest.IgnoreResourceMetricsOrder(), pmetrictest.IgnoreMetricsOrder(), pmetrictest.IgnoreScopeMetricsOrder(), - pmetrictest.IgnoreMetricDataPointsOrder(), ), ) } diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml index c484f18a7ad8..d821db9282a1 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml +++ b/receiver/k8sclusterreceiver/internal/resourcequota/testdata/expected.yaml @@ -17,7 +17,6 @@ resourceMetrics: scopeMetrics: - metrics: - description: The upper limit for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores - unit: "1" gauge: dataPoints: - asInt: "2000" @@ -26,8 +25,8 @@ resourceMetrics: value: stringValue: requests.cpu name: k8s.resource_quota.hard_limit - - description: The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores unit: "1" + - description: The usage for a particular resource in a specific namespace. Will only be sent if a quota is specified. CPU requests/limits will be sent as millicores gauge: dataPoints: - asInt: "1000" @@ -36,6 +35,7 @@ resourceMetrics: value: stringValue: requests.cpu name: k8s.resource_quota.used + unit: "1" scope: name: otelcol/k8sclusterreceiver version: latest From abafee3b5eaba35bd0c71e7f754ace327cc2d9a1 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 18:08:31 -0700 Subject: [PATCH 4/7] wip --- .../internal/resourcequota/resourcequotas.go | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go index e6478a625529..332cc9f6af8a 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas.go @@ -19,28 +19,20 @@ func GetMetrics(set receiver.CreateSettings, rq *corev1.ResourceQuota) pmetric.M mb := imetadata.NewMetricsBuilder(imetadata.DefaultMetricsBuilderConfig(), set) ts := pcommon.NewTimestampFromTime(time.Now()) - for _, t := range []struct { - recordFn func(ts pcommon.Timestamp, val int64, resource string) - rl corev1.ResourceList - }{ - { - mb.RecordK8sResourceQuotaHardLimitDataPoint, - rq.Status.Hard, - }, - { - mb.RecordK8sResourceQuotaUsedDataPoint, - rq.Status.Used, - }, - } { - for k, v := range t.rl { - - val := v.Value() - if strings.HasSuffix(string(k), ".cpu") { - val = v.MilliValue() - } - mb.RecordK8sResourceQuotaUsedDataPoint(ts, val, string(k)) - mb.RecordK8sResourceQuotaHardLimitDataPoint(ts, val, string(k)) + for k, v := range rq.Status.Hard { + val := v.Value() + if strings.HasSuffix(string(k), ".cpu") { + val = v.MilliValue() } + mb.RecordK8sResourceQuotaHardLimitDataPoint(ts, val, string(k)) + } + + for k, v := range rq.Status.Used { + val := v.Value() + if strings.HasSuffix(string(k), ".cpu") { + val = v.MilliValue() + } + mb.RecordK8sResourceQuotaUsedDataPoint(ts, val, string(k)) } return mb.Emit(imetadata.WithK8sResourcequotaUID(string(rq.UID)), imetadata.WithK8sResourcequotaName(rq.Name), imetadata.WithK8sNamespaceName(rq.Namespace), imetadata.WithOpencensusResourcetype("k8s")) From 37ca51bc7ef077a059defec1b13ca48443d414c3 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 18:09:07 -0700 Subject: [PATCH 5/7] apply resourcequota changes --- receiver/k8sclusterreceiver/internal/collection/collector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/receiver/k8sclusterreceiver/internal/collection/collector.go b/receiver/k8sclusterreceiver/internal/collection/collector.go index 73c603de3469..04428cebdb1e 100644 --- a/receiver/k8sclusterreceiver/internal/collection/collector.go +++ b/receiver/k8sclusterreceiver/internal/collection/collector.go @@ -112,7 +112,7 @@ func (dc *DataCollector) SyncMetrics(obj interface{}) { case *corev1.ReplicationController: md = ocsToMetrics(replicationcontroller.GetMetrics(o)) case *corev1.ResourceQuota: - md = ocsToMetrics(resourcequota.GetMetrics(o)) + md = resourcequota.GetMetrics(dc.settings, o) case *appsv1.Deployment: md = ocsToMetrics(deployment.GetMetrics(o)) case *appsv1.ReplicaSet: From 3b114f1a9f3c724ff09bdab9214b1dbbf6b96cc2 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 18:10:32 -0700 Subject: [PATCH 6/7] move resourcequota to use pmetrics --- .chloggen/switchk8srq.yaml | 11 +++++++++++ .../internal/resourcequota/documentation.md | 2 +- .../internal/metadata/generated_config.go | 6 +++--- .../internal/resourcequota/metadata.yaml | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100755 .chloggen/switchk8srq.yaml diff --git a/.chloggen/switchk8srq.yaml b/.chloggen/switchk8srq.yaml new file mode 100755 index 000000000000..0625af825831 --- /dev/null +++ b/.chloggen/switchk8srq.yaml @@ -0,0 +1,11 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: k8sclusterreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Switch k8s.rq metrics to use pdata. + +# One or more tracking issues related to the change +issues: [23419] diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md b/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md index f72ee1a41eab..8f167b3085f8 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md +++ b/receiver/k8sclusterreceiver/internal/resourcequota/documentation.md @@ -1,6 +1,6 @@ [comment]: <> (Code generated by mdatagen. DO NOT EDIT.) -# k8s/hpa +# k8s/resourcequota ## Default Metrics diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go index 409354cfb2ad..b6c02756cf5e 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/internal/metadata/generated_config.go @@ -23,7 +23,7 @@ func (ms *MetricConfig) Unmarshal(parser *confmap.Conf) error { return nil } -// MetricsConfig provides config for k8s/hpa metrics. +// MetricsConfig provides config for k8s/resourcequota metrics. type MetricsConfig struct { K8sResourceQuotaHardLimit MetricConfig `mapstructure:"k8s.resource_quota.hard_limit"` K8sResourceQuotaUsed MetricConfig `mapstructure:"k8s.resource_quota.used"` @@ -45,7 +45,7 @@ type ResourceAttributeConfig struct { Enabled bool `mapstructure:"enabled"` } -// ResourceAttributesConfig provides config for k8s/hpa resource attributes. +// ResourceAttributesConfig provides config for k8s/resourcequota resource attributes. type ResourceAttributesConfig struct { K8sNamespaceName ResourceAttributeConfig `mapstructure:"k8s.namespace.name"` K8sResourcequotaName ResourceAttributeConfig `mapstructure:"k8s.resourcequota.name"` @@ -70,7 +70,7 @@ func DefaultResourceAttributesConfig() ResourceAttributesConfig { } } -// MetricsBuilderConfig is a configuration for k8s/hpa metrics builder. +// MetricsBuilderConfig is a configuration for k8s/resourcequota metrics builder. type MetricsBuilderConfig struct { Metrics MetricsConfig `mapstructure:"metrics"` ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"` diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml b/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml index 6744176998d3..7cdbb1c915cc 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml +++ b/receiver/k8sclusterreceiver/internal/resourcequota/metadata.yaml @@ -1,4 +1,4 @@ -type: k8s/hpa +type: k8s/resourcequota sem_conv_version: 1.18.0 From 6761c5358010a8bf779f5a2fed5595f2659782d7 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 15 Jun 2023 18:12:26 -0700 Subject: [PATCH 7/7] lint --- .../internal/resourcequota/resourcequotas_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go index 6b4786624552..0190660dd98e 100644 --- a/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go +++ b/receiver/k8sclusterreceiver/internal/resourcequota/resourcequotas_test.go @@ -7,15 +7,15 @@ import ( "path/filepath" "testing" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/golden" - "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/receiver/receivertest" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/golden" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" ) func TestRequestQuotaMetrics(t *testing.T) {