From 33a942d6c93c16fed64c9cf6ee7731887745ae26 Mon Sep 17 00:00:00 2001 From: Erik Wu Date: Tue, 21 Oct 2025 09:09:27 -0500 Subject: [PATCH 1/5] initial poc for starttimeattribute strategy --- processor/metricstarttimeprocessor/config.go | 10 +- processor/metricstarttimeprocessor/factory.go | 13 +- processor/metricstarttimeprocessor/go.mod | 52 +++- processor/metricstarttimeprocessor/go.sum | 101 ++++++- .../internal/filter/filter.go | 73 +++++ .../internal/filter/filter_test.go | 261 +++++++++++++++++ .../internal/starttimeattribute/adjuster.go | 249 ++++++++++++++++ .../starttimeattribute/adjuster_test.go | 273 ++++++++++++++++++ .../internal/starttimeattribute/config.go | 15 + .../internal/starttimeattribute/k8s.go | 65 +++++ .../internal/starttimeattribute/pod_client.go | 160 ++++++++++ 11 files changed, 1257 insertions(+), 15 deletions(-) create mode 100644 processor/metricstarttimeprocessor/internal/filter/filter.go create mode 100644 processor/metricstarttimeprocessor/internal/filter/filter_test.go create mode 100644 processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go create mode 100644 processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go create mode 100644 processor/metricstarttimeprocessor/internal/starttimeattribute/config.go create mode 100644 processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go create mode 100644 processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go diff --git a/processor/metricstarttimeprocessor/config.go b/processor/metricstarttimeprocessor/config.go index 4aa2f358857ba..2024ee097bff8 100644 --- a/processor/metricstarttimeprocessor/config.go +++ b/processor/metricstarttimeprocessor/config.go @@ -9,6 +9,8 @@ import ( "regexp" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "go.opentelemetry.io/collector/component" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimemetric" @@ -21,7 +23,12 @@ type Config struct { Strategy string `mapstructure:"strategy"` GCInterval time.Duration `mapstructure:"gc_interval"` // StartTimeMetricRegex only applies then the start_time_metric strategy is used - StartTimeMetricRegex string `mapstructure:"start_time_metric_regex"` + StartTimeMetricRegex string `mapstructure:"start_time_metric_regex"` + IncludeMetrics filter.FilterConfig `mapstructure:"include_metrics"` + ExcludeMetrics filter.FilterConfig `mapstructure:"exclude_metrics"` + // AttributesFilters only applies to the start_time_attribute strategy to construct specific k8s api informer filters + AttributesFilters starttimeattribute.AttributesFilterConfig `mapstructure:"attributes_filters"` + SkipIfCTExists bool `mapstructure:"skip_if_ct_exists"` } var _ component.Config = (*Config)(nil) @@ -39,6 +46,7 @@ func (cfg *Config) Validate() error { case truereset.Type: case subtractinitial.Type: case starttimemetric.Type: + case starttimeattribute.Type: default: return fmt.Errorf("%q is not a valid strategy", cfg.Strategy) } diff --git a/processor/metricstarttimeprocessor/factory.go b/processor/metricstarttimeprocessor/factory.go index df7f5e201a01b..438af9ab45d1f 100644 --- a/processor/metricstarttimeprocessor/factory.go +++ b/processor/metricstarttimeprocessor/factory.go @@ -7,6 +7,8 @@ import ( "context" "regexp" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/processor" @@ -34,7 +36,10 @@ func createMetricsProcessor( nextConsumer consumer.Metrics, ) (processor.Metrics, error) { rCfg := cfg.(*Config) - + filter, err := filter.NewFilter(rCfg.IncludeMetrics, rCfg.ExcludeMetrics) + if err != nil { + return nil, err + } var adjustMetrics processorhelper.ProcessMetricsFunc switch rCfg.Strategy { @@ -55,6 +60,12 @@ func createMetricsProcessor( } adjuster := starttimemetric.NewAdjuster(set.TelemetrySettings, startTimeMetricRegex, rCfg.GCInterval) adjustMetrics = adjuster.AdjustMetrics + case starttimeattribute.Type: + adjuster, err := starttimeattribute.NewAdjuster(set.TelemetrySettings, filter, rCfg.AttributesFilters, rCfg.SkipIfCTExists, rCfg.GCInterval) + if err != nil { + return nil, err + } + adjustMetrics = adjuster.AdjustMetrics } return processorhelper.NewMetrics( diff --git a/processor/metricstarttimeprocessor/go.mod b/processor/metricstarttimeprocessor/go.mod index 297b8c01af239..df5d0b2e9f306 100644 --- a/processor/metricstarttimeprocessor/go.mod +++ b/processor/metricstarttimeprocessor/go.mod @@ -3,6 +3,8 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/processor/metri go 1.24.0 require ( + github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter v0.138.0 + github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig v0.138.0 github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.138.0 github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.138.0 github.com/stretchr/testify v1.11.1 @@ -19,27 +21,48 @@ require ( go.opentelemetry.io/otel v1.38.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 + k8s.io/api v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/client-go v0.32.3 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/providers/confmap v1.0.0 // indirect github.com/knadh/koanf/v2 v2.3.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v3.9.0+incompatible // indirect + github.com/openshift/client-go v0.0.0-20241203091221-452dfb8fa071 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/collector/component/componentstatus v0.138.1-0.20251021231522-c657d5d4e920 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.138.1-0.20251021231522-c657d5d4e920 // indirect @@ -57,13 +80,24 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.7.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil => ../../pkg/pdatautil @@ -71,3 +105,11 @@ replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest => ../../pkg/pdatatest replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden => ../../pkg/golden + +replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig => ./../../internal/k8sconfig + +replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter => ../../internal/filter + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl => ../../pkg/ottl + +replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal => ../../internal/coreinternal diff --git a/processor/metricstarttimeprocessor/go.sum b/processor/metricstarttimeprocessor/go.sum index 39506ab00b3d3..80ff9c60b691a 100644 --- a/processor/metricstarttimeprocessor/go.sum +++ b/processor/metricstarttimeprocessor/go.sum @@ -1,13 +1,29 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -16,13 +32,24 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -33,10 +60,15 @@ github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5z github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A= github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -47,14 +79,36 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= +github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/openshift/client-go v0.0.0-20241203091221-452dfb8fa071 h1:l0++HnGVKBcs8kXFL/1yeozxioxPGNpp0PYe3Y+0sq4= +github.com/openshift/client-go v0.0.0-20241203091221-452dfb8fa071/go.mod h1:gL0laCCiIaNTNw1ZsMQZXBVu2NeQFpNWm9bLtYO9+ZU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -134,24 +188,32 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -167,5 +229,28 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/processor/metricstarttimeprocessor/internal/filter/filter.go b/processor/metricstarttimeprocessor/internal/filter/filter.go new file mode 100644 index 0000000000000..dffba91a5f6b8 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/filter/filter.go @@ -0,0 +1,73 @@ +package filter + +import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset/regexp" +) + +type Filter struct { + include filterset.FilterSet + exclude filterset.FilterSet +} + +func NewFilter(include, exclude FilterConfig) (*Filter, error) { + var includeFilter filterset.FilterSet + var excludeFilter filterset.FilterSet + var err error + if len(include.Metrics) > 0 { + include.Config = regexConfigWithDefaults(include.Config) + includeFilter, err = filterset.CreateFilterSet(include.Metrics, &include.Config) + if err != nil { + return nil, err + } + } + if len(exclude.Metrics) > 0 { + exclude.Config = regexConfigWithDefaults(exclude.Config) + excludeFilter, err = filterset.CreateFilterSet(exclude.Metrics, &exclude.Config) + if err != nil { + return nil, err + } + } + return &Filter{ + include: includeFilter, + exclude: excludeFilter, + }, nil +} + +func regexConfigWithDefaults(c filterset.Config) filterset.Config { + if c.MatchType != "regex" { + return c + } + if c.RegexpConfig == nil { + c.RegexpConfig = ®exp.Config{} + } + c.RegexpConfig.CacheEnabled = true + if c.RegexpConfig.CacheMaxNumEntries == 0 { + c.RegexpConfig.CacheMaxNumEntries = 1000 // hold 1k metric names for each cache + } + + return c +} + +func (filter *Filter) Matches(name string) bool { + if filter.exclude != nil && filter.exclude.Matches(name) { + return false + } + if filter.include != nil && !filter.include.Matches(name) { + return false + } + return true +} + +type FilterConfig struct { + filterset.Config `mapstructure:",squash"` + Metrics []string `mapstructure:"metrics"` +} + +type NoOpFilter struct { + NoMatch bool +} + +func (f NoOpFilter) Matches(_ string) bool { + return !f.NoMatch +} diff --git a/processor/metricstarttimeprocessor/internal/filter/filter_test.go b/processor/metricstarttimeprocessor/internal/filter/filter_test.go new file mode 100644 index 0000000000000..9b4c387c3f029 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/filter/filter_test.go @@ -0,0 +1,261 @@ +package filter + +import ( + "testing" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewFilter(t *testing.T) { + tests := []struct { + name string + include FilterConfig + exclude FilterConfig + expectError bool + errorMsg string + }{ + { + name: "empty_filter", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + expectError: false, + }, + { + name: "include_only_with_strict_matching", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric1", "metric2"}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + expectError: false, + }, + { + name: "exclude_only_with_regexp_matching", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"metric.*", "test_.*"}, + }, + expectError: false, + }, + { + name: "both_include_and_exclude", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric1", "metric2", "metric3"}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric3"}, + }, + expectError: false, + }, + { + name: "invalid_regexp_in_include", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"[invalid"}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + expectError: true, + errorMsg: "error parsing regexp", + }, + { + name: "invalid_regexp_in_exclude", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"[invalid"}, + }, + expectError: true, + errorMsg: "error parsing regexp", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filter, err := NewFilter(tt.include, tt.exclude) + + if tt.expectError { + require.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + assert.Nil(t, filter) + } else { + require.NoError(t, err) + assert.NotNil(t, filter) + } + }) + } +} + +func TestFilter_Matches(t *testing.T) { + tests := []struct { + name string + include FilterConfig + exclude FilterConfig + metricName string + shouldMatch bool + }{ + { + name: "no_filters_matches_all", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + metricName: "any_metric", + shouldMatch: true, + }, + { + name: "include_strict_match", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric1", "metric2"}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + metricName: "metric1", + shouldMatch: true, + }, + { + name: "include_strict_no_match", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric1", "metric2"}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + metricName: "metric3", + shouldMatch: false, + }, + { + name: "exclude_strict_match", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"bad_metric"}, + }, + metricName: "bad_metric", + shouldMatch: false, + }, + { + name: "exclude_strict_no_match", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"bad_metric"}, + }, + metricName: "good_metric", + shouldMatch: true, + }, + { + name: "include_regexp_match", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"metric.*", "test_.*"}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + metricName: "metric_cpu", + shouldMatch: true, + }, + { + name: "include_regexp_no_match", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"metric.*", "test_.*"}, + }, + exclude: FilterConfig{ + Metrics: []string{}, + }, + metricName: "system_cpu", + shouldMatch: false, + }, + { + name: "exclude_regexp_match", + include: FilterConfig{ + Metrics: []string{}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"temp_.*", "debug_.*"}, + }, + metricName: "temp_metric", + shouldMatch: false, + }, + { + name: "exclude_takes_precedence", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric1", "metric2", "metric3"}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Strict}, + Metrics: []string{"metric2"}, + }, + metricName: "metric2", + shouldMatch: false, + }, + { + name: "include_and_exclude_both_active", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"prod_.*"}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{".*_temp"}, + }, + metricName: "prod_cpu", + shouldMatch: true, + }, + { + name: "include_and_exclude_conflict", + include: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{"prod_.*"}, + }, + exclude: FilterConfig{ + Config: filterset.Config{MatchType: filterset.Regexp}, + Metrics: []string{".*_temp"}, + }, + metricName: "prod_temp", + shouldMatch: false, // exclude takes precedence + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filter, err := NewFilter(tt.include, tt.exclude) + require.NoError(t, err) + + matches := filter.Matches(tt.metricName) + assert.Equal(t, tt.shouldMatch, matches, + "Expected Matches(%q) to return %v", tt.metricName, tt.shouldMatch) + }) + } +} diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go new file mode 100644 index 0000000000000..f7eddb7ed7dee --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go @@ -0,0 +1,249 @@ +package starttimeattribute + +import ( + "context" + "fmt" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/zap" +) + +const ( + // Type is the value users can use to configure the start time metric adjuster. + Type = "start_time_attribute" +) + +type Adjuster struct { + apiConfig k8sconfig.APIConfig + podClient podClient + logger *zap.Logger + + filter filterset.FilterSet + referenceCache *datapointstorage.Cache + + skipIfCTExists bool +} + +type podClientFactory func(context.Context, k8sconfig.APIConfig, informerFilter) (podClient, error) + +// NewAdjuster returns a new Adjuster which adjust metrics' start times based on the initial received points. +func NewAdjuster(set component.TelemetrySettings, filter filterset.FilterSet, attributeFilterConfig AttributesFilterConfig, skipIfCTExists bool, gcInterval time.Duration) (*Adjuster, error) { + return NewAdjusterWithFactory(set, newK8sPodClient, filter, attributeFilterConfig, skipIfCTExists, gcInterval) +} + +// NewAdjusterWithFactory returns a new Adjuster with a custom pod client factory +func NewAdjusterWithFactory(set component.TelemetrySettings, factory podClientFactory, filter filterset.FilterSet, attributeFilterConfig AttributesFilterConfig, skipIfCTExists bool, gcInterval time.Duration) (*Adjuster, error) { + apiConfig := k8sconfig.APIConfig{ + AuthType: k8sconfig.AuthTypeServiceAccount, + } + + ctx := context.Background() + k8sInformerFilter := toInformerFilter(attributeFilterConfig) + client, err := factory(ctx, apiConfig, k8sInformerFilter) + if err != nil { + return nil, fmt.Errorf("failed to create pod client: %w", err) + } + + return &Adjuster{ + apiConfig: apiConfig, + podClient: client, + logger: set.Logger, + filter: filter, + skipIfCTExists: skipIfCTExists, + referenceCache: datapointstorage.NewCache(gcInterval), + }, nil +} + +func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) (pmetric.Metrics, error) { + resourceMetrics := metrics.ResourceMetrics() + for i := 0; i < resourceMetrics.Len(); i++ { + rm := resourceMetrics.At(i) + resource := rm.Resource() + // Try to extract pod identifier from resource attributes + podID := a.extractPodIdentifier(resource.Attributes()) + if podID == nil { + continue + } + attrHash := pdatautil.MapHash(rm.Resource().Attributes()) + tsm, _ := a.referenceCache.Get(attrHash) + tsm.Lock() + scopeMetrics := rm.ScopeMetrics() + for j := 0; j < scopeMetrics.Len(); j++ { + sm := scopeMetrics.At(j) + metrics := sm.Metrics() + + for k := 0; k < metrics.Len(); k++ { + metric := metrics.At(k) + + metricName := metric.Name() + // Only process cumulative metrics + if !a.isCumulativeMetric(metric) { + a.logger.Debug("metric is not cumulative, skipping", + zap.String("metricName", metricName)) + continue + } + if !a.filter.Matches(metricName) { + a.logger.Debug("metric not included by filter, skipper", + zap.String("metricName", metricName)) + continue + } + if a.skipIfCTExists && a.hasStartTimeSet(metric) { + continue + } + // Get pod start time + startTime := a.podClient.GetPodStartTime(ctx, *podID) + if startTime.IsZero() { + a.logger.Debug("no known start time for pod", + zap.Stringer("podIDTypee", podID.Type), + zap.String("podID", podID.Value), + zap.String("metricName", metricName), + ) + continue + } + // Set start time for all data points + a.setStartTimeForMetric(tsm, metric, startTime) + } + } + tsm.Unlock() + } + + return metrics, nil +} + +// only look at the first datapoint +func (a *Adjuster) hasStartTimeSet(metric pmetric.Metric) bool { + switch metric.Type() { + case pmetric.MetricTypeSum: + dataPoints := metric.Sum().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + case pmetric.MetricTypeHistogram: + dataPoints := metric.Histogram().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + case pmetric.MetricTypeExponentialHistogram: + dataPoints := metric.ExponentialHistogram().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + } + return false +} + +func (a *Adjuster) extractPodIdentifier(attrs pcommon.Map) *podIdentifier { + // Check for pod name with namespace + podNameVal, nameOk := attrs.Get("k8s.pod.name") + namespaceVal, nsOk := attrs.Get("k8s.namespace.name") + if nameOk && nsOk { + return &podIdentifier{ + Value: fmt.Sprintf("%s/%s", namespaceVal.AsString(), podNameVal.AsString()), + Type: podName, + } + } + + // Check for pod UID + if uidVal, ok := attrs.Get("k8s.pod.uid"); ok { + return &podIdentifier{ + Value: uidVal.AsString(), + Type: podUID, + } + } + + // Check for pod IP + // Note: pod IP is not a unique identifier i.e for host-networked pods or ds pods + // so this is a fallback option + if ipVal, ok := attrs.Get("k8s.pod.ip"); ok { + return &podIdentifier{ + Value: ipVal.AsString(), + Type: podIP, + } + } + + return nil +} + +func (a *Adjuster) isCumulativeMetric(metric pmetric.Metric) bool { + switch metric.Type() { + case pmetric.MetricTypeSummary: + return true + case pmetric.MetricTypeSum: + return metric.Sum().AggregationTemporality() == pmetric.AggregationTemporalityCumulative + case pmetric.MetricTypeHistogram: + return metric.Histogram().AggregationTemporality() == pmetric.AggregationTemporalityCumulative + case pmetric.MetricTypeExponentialHistogram: + return metric.ExponentialHistogram().AggregationTemporality() == pmetric.AggregationTemporalityCumulative + default: + return false + } +} + +func (a *Adjuster) setStartTimeForMetric(tsm *datapointstorage.TimeseriesMap, metric pmetric.Metric, startTime time.Time) { + startTimeNanos := pcommon.NewTimestampFromTime(startTime) + switch metric.Type() { + case pmetric.MetricTypeSummary: + dataPoints := metric.Summary().DataPoints() + for i := 0; i < dataPoints.Len(); i++ { + dp := dataPoints.At(i) + refTsi, found := tsm.Get(metric, dp.Attributes()) + if !found { + refTsi.Summary = datapointstorage.SummaryInfo{StartTime: startTimeNanos} + } else if refTsi.IsResetSummary(dp) { + refTsi.Summary.StartTime = pcommon.NewTimestampFromTime(dp.Timestamp().AsTime().Add(-1 * time.Millisecond)) + } + refTsi.Summary.PreviousCount, refTsi.Summary.PreviousSum = dp.Count(), dp.Sum() + dp.SetStartTimestamp(refTsi.Summary.StartTime) + } + case pmetric.MetricTypeSum: + dataPoints := metric.Sum().DataPoints() + for i := 0; i < dataPoints.Len(); i++ { + dp := dataPoints.At(i) + refTsi, found := tsm.Get(metric, dp.Attributes()) + if !found { + refTsi.Number = datapointstorage.NumberInfo{StartTime: startTimeNanos} + } else if refTsi.IsResetSum(dp) { + refTsi.Number.StartTime = pcommon.NewTimestampFromTime(dp.Timestamp().AsTime().Add(-1 * time.Millisecond)) + } + refTsi.Number.PreviousDoubleValue = dp.DoubleValue() + refTsi.Number.PreviousIntValue = dp.IntValue() + dp.SetStartTimestamp(startTimeNanos) + } + case pmetric.MetricTypeHistogram: + dataPoints := metric.Histogram().DataPoints() + for i := 0; i < dataPoints.Len(); i++ { + dp := dataPoints.At(i) + refTsi, found := tsm.Get(metric, dp.Attributes()) + if !found { + refTsi.Histogram = datapointstorage.HistogramInfo{StartTime: startTimeNanos, ExplicitBounds: dp.ExplicitBounds().AsRaw()} + } else if refTsi.IsResetHistogram(dp) { + refTsi.Histogram.StartTime = pcommon.NewTimestampFromTime(dp.Timestamp().AsTime().Add(-1 * time.Millisecond)) + } + refTsi.Histogram.PreviousCount, refTsi.Histogram.PreviousSum = dp.Count(), dp.Sum() + refTsi.Histogram.PreviousBucketCounts = dp.BucketCounts().AsRaw() + dp.SetStartTimestamp(refTsi.Histogram.StartTime) + } + case pmetric.MetricTypeExponentialHistogram: + dataPoints := metric.ExponentialHistogram().DataPoints() + for i := 0; i < dataPoints.Len(); i++ { + dp := dataPoints.At(i) + refTsi, found := tsm.Get(metric, dp.Attributes()) + if !found { + refTsi.ExponentialHistogram = datapointstorage.ExponentialHistogramInfo{StartTime: startTimeNanos, Scale: dp.Scale()} + } else if refTsi.IsResetExponentialHistogram(dp) { + refTsi.ExponentialHistogram.StartTime = pcommon.NewTimestampFromTime(dp.Timestamp().AsTime().Add(-1 * time.Millisecond)) + } + refTsi.ExponentialHistogram.PreviousPositive = datapointstorage.NewExponentialHistogramBucketInfo(dp.Positive()) + refTsi.ExponentialHistogram.PreviousNegative = datapointstorage.NewExponentialHistogramBucketInfo(dp.Negative()) + refTsi.ExponentialHistogram.PreviousCount, refTsi.ExponentialHistogram.PreviousSum, refTsi.ExponentialHistogram.PreviousZeroCount = dp.Count(), dp.Sum(), dp.ZeroCount() + dp.SetStartTimestamp(refTsi.ExponentialHistogram.StartTime) + } + } +} diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go new file mode 100644 index 0000000000000..f8287b26511eb --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go @@ -0,0 +1,273 @@ +package starttimeattribute + +import ( + "context" + "testing" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" +) + +type mockPodClient struct { + startTimes map[string]time.Time +} + +func (m *mockPodClient) GetPodStartTime(ctx context.Context, podID podIdentifier) time.Time { + key := podID.Value + if t, ok := m.startTimes[key]; ok { + return t + } + return time.Time{} +} + +const testMetricName = "test_metric" + +func TestAdjustMetrics(t *testing.T) { + testStartTime := time.Now().Add(-1 * time.Hour) + + mockClient := &mockPodClient{ + startTimes: map[string]time.Time{ + "10.0.0.1": testStartTime, + "default/my-pod": testStartTime, + "uid-12345": testStartTime, + }, + } + + tests := []struct { + name string + attrs map[string]string + expectAdjusted bool + isCumulative bool + startTimeUnixSec int64 + filter filterset.FilterSet + }{ + { + name: "cumulative metric with pod IP", + attrs: map[string]string{ + "k8s.pod.ip": "10.0.0.1", + }, + expectAdjusted: true, + isCumulative: true, + }, + { + name: "cumulative metric with pod name", + attrs: map[string]string{ + "k8s.pod.name": "my-pod", + "k8s.namespace.name": "default", + }, + expectAdjusted: true, + isCumulative: true, + }, + { + name: "cumulative metric with pod UID", + attrs: map[string]string{ + "k8s.pod.uid": "uid-12345", + }, + expectAdjusted: true, + isCumulative: true, + }, + { + name: "delta metric should not be adjusted", + attrs: map[string]string{"k8s.pod.ip": "10.0.0.1"}, + expectAdjusted: false, + isCumulative: false, + }, + { + name: "metric without pod identifier", + attrs: map[string]string{"other": "value"}, + expectAdjusted: false, + isCumulative: true, + }, + { + name: "metric excluded by filter", + attrs: map[string]string{ + "k8s.pod.ip": "10.0.0.1", + }, + expectAdjusted: false, + isCumulative: true, + filter: filter.NoOpFilter{NoMatch: true}, + }, + { + name: "metric excluded because it has a non-zero start time", + attrs: map[string]string{ + "k8s.pod.ip": "10.0.0.1", + }, + expectAdjusted: false, + isCumulative: true, + startTimeUnixSec: time.Now().Unix(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + metrics := createTestMetrics(testMetricName, tt.attrs, tt.isCumulative, tt.startTimeUnixSec) + originalStartTime := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).StartTimestamp() + metricNameFilter := tt.filter + if metricNameFilter == nil { + metricNameFilter = filter.NoOpFilter{} + } + adjuster, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter) (podClient, error) { + return mockClient, nil + }, metricNameFilter, AttributesFilterConfig{}, true, 5*time.Minute) + require.NoError(t, err) + adjustedMetrics, err := adjuster.AdjustMetrics(context.Background(), metrics) + require.NoError(t, err) + + adjustedStartTime := adjustedMetrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).StartTimestamp() + + if tt.expectAdjusted { + expectedTime := pcommon.NewTimestampFromTime(testStartTime) + assert.Equal(t, expectedTime, adjustedStartTime) + assert.NotEqual(t, originalStartTime, adjustedStartTime) + } else { + assert.Equal(t, originalStartTime, adjustedStartTime) + } + }) + } +} + +func createTestMetrics(name string, attrs map[string]string, cumulative bool, startTime int64) pmetric.Metrics { + metrics := pmetric.NewMetrics() + rm := metrics.ResourceMetrics().AppendEmpty() + + resource := rm.Resource() + for k, v := range attrs { + resource.Attributes().PutStr(k, v) + } + + sm := rm.ScopeMetrics().AppendEmpty() + m := sm.Metrics().AppendEmpty() + m.SetName(name) + + sum := m.SetEmptySum() + if cumulative { + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + } else { + sum.SetAggregationTemporality(pmetric.AggregationTemporalityDelta) + } + + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(startTime, 0))) + dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) + dp.SetDoubleValue(100.0) + + return metrics +} + +func TestExtractPodIdentifier(t *testing.T) { + adjuster := &Adjuster{} + + tests := []struct { + name string + attrs map[string]string + expected *podIdentifier + }{ + { + name: "pod IP", + attrs: map[string]string{"k8s.pod.ip": "10.0.0.1"}, + expected: &podIdentifier{Value: "10.0.0.1", Type: podIP}, + }, + { + name: "pod name with namespace", + attrs: map[string]string{ + "k8s.pod.name": "my-pod", + "k8s.namespace.name": "default", + }, + expected: &podIdentifier{Value: "default/my-pod", Type: podName}, + }, + { + name: "pod UID", + attrs: map[string]string{"k8s.pod.uid": "uid-12345"}, + expected: &podIdentifier{Value: "uid-12345", Type: podUID}, + }, + { + name: "no pod identifier", + attrs: map[string]string{"other": "value"}, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + attrs := pcommon.NewMap() + for k, v := range tt.attrs { + attrs.PutStr(k, v) + } + + result := adjuster.extractPodIdentifier(attrs) + + if tt.expected == nil { + assert.Nil(t, result) + } else { + require.NotNil(t, result) + assert.Equal(t, tt.expected.Value, result.Value) + assert.Equal(t, tt.expected.Type, result.Type) + } + }) + } +} + +func TestIsCumulativeMetric(t *testing.T) { + adjuster := &Adjuster{} + + tests := []struct { + name string + metricType pmetric.MetricType + temporality pmetric.AggregationTemporality + isCumulative bool + }{ + { + name: "cumulative sum", + metricType: pmetric.MetricTypeSum, + temporality: pmetric.AggregationTemporalityCumulative, + isCumulative: true, + }, + { + name: "delta sum", + metricType: pmetric.MetricTypeSum, + temporality: pmetric.AggregationTemporalityDelta, + isCumulative: false, + }, + { + name: "cumulative histogram", + metricType: pmetric.MetricTypeHistogram, + temporality: pmetric.AggregationTemporalityCumulative, + isCumulative: true, + }, + { + name: "gauge is not cumulative", + metricType: pmetric.MetricTypeGauge, + temporality: pmetric.AggregationTemporalityUnspecified, + isCumulative: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + metric := pmetric.NewMetric() + + switch tt.metricType { + case pmetric.MetricTypeSum: + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(tt.temporality) + case pmetric.MetricTypeHistogram: + hist := metric.SetEmptyHistogram() + hist.SetAggregationTemporality(tt.temporality) + case pmetric.MetricTypeGauge: + metric.SetEmptyGauge() + } + + result := adjuster.isCumulativeMetric(metric) + assert.Equal(t, tt.isCumulative, result) + }) + } +} diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go new file mode 100644 index 0000000000000..df4f15a59d135 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go @@ -0,0 +1,15 @@ +package starttimeattribute + +// AttributesFilterConfig holds the user configuration for filtering the k8s informer that is used by the Adjuster +// currently, this only supports a subset of all filter types +type AttributesFilterConfig struct { + Node string `mapstructure:"node"` + Namespace string `mapstructure:"namespace"` + Labels []LabelFilter `mapstructure:"labels"` +} + +type LabelFilter struct { + Key string `mapstructure:"key"` + Value string `mapstructure:"value"` + Op string `mapstructure:"op"` +} diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go new file mode 100644 index 0000000000000..cc52e31ea4853 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go @@ -0,0 +1,65 @@ +package starttimeattribute + +import ( + "context" + "time" + + "k8s.io/apimachinery/pkg/selection" +) + +type podIdentifier struct { + Value string + Type idType +} + +type idType byte + +const ( + podIP idType = iota + podName + podUID +) + +func (i idType) String() string { + switch i { + case podIP: + return "podIP" + case podName: + return "podName" + case podUID: + return "podUID" + default: + return "" + } +} + +type podClient interface { + GetPodStartTime(ctx context.Context, podID podIdentifier) time.Time +} + +type informerFilter struct { + node string + namespace string + LabelFilters []labelFilter +} + +type labelFilter struct { + Key string + Value string + Op selection.Operator +} + +func toInformerFilter(cfg AttributesFilterConfig) informerFilter { + f := informerFilter{ + node: cfg.Node, + namespace: cfg.Namespace, + } + for _, label := range cfg.Labels { + f.LabelFilters = append(f.LabelFilters, labelFilter{ + Key: label.Key, + Value: label.Value, + Op: selection.Operator(label.Op), + }) + } + return f +} diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go new file mode 100644 index 0000000000000..053e1af587dd3 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go @@ -0,0 +1,160 @@ +package starttimeattribute + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" + k8s "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +type k8sPodClient struct { + clientset k8s.Interface + informerStop chan struct{} + informer cache.SharedIndexInformer + mu sync.RWMutex + startTimeCache map[string]time.Time +} + +func newK8sPodClient(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter) (podClient, error) { + clientset, err := k8sconfig.MakeClient(apiConfig) + if err != nil { + return nil, fmt.Errorf("failed to create k8s client: %w", err) + } + + labelSelector := labels.Everything() + if len(filter.LabelFilters) > 0 { + for _, lf := range filter.LabelFilters { + requirement, err := labels.NewRequirement(lf.Key, lf.Op, []string{lf.Value}) + if err != nil { + return nil, fmt.Errorf("failed to create label requirement: %w", err) + } + labelSelector = labelSelector.Add(*requirement) + } + } + + fieldSelectors := []fields.Selector{fields.Everything()} + if filter.node != "" { + fieldSelectors = append(fieldSelectors, fields.OneTermEqualSelector("spec.nodeName", filter.node)) + } + fieldSelector := fields.AndSelectors(fieldSelectors...) + options := informers.WithTweakListOptions(func(opts *metav1.ListOptions) { + opts.LabelSelector = labelSelector.String() + opts.FieldSelector = fieldSelector.String() + }) + + var factory informers.SharedInformerFactory + if filter.namespace != "" { + factory = informers.NewSharedInformerFactoryWithOptions( + clientset, + time.Hour, + informers.WithNamespace(filter.namespace), + options, + ) + } else { + factory = informers.NewSharedInformerFactoryWithOptions( + clientset, + time.Hour, + options, + ) + } + + podInformer := factory.Core().V1().Pods().Informer() + + client := &k8sPodClient{ + clientset: clientset, + informerStop: make(chan struct{}), + informer: podInformer, + startTimeCache: make(map[string]time.Time), + } + + podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + if pod, ok := obj.(*corev1.Pod); ok { + client.addPod(pod) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + if oldPod, ok := oldObj.(*corev1.Pod); ok { + client.deletePod(oldPod) + } + if newPod, ok := newObj.(*corev1.Pod); ok { + client.addPod(newPod) + } + }, + DeleteFunc: func(obj interface{}) { + if pod, ok := obj.(*corev1.Pod); ok { + client.deletePod(pod) + } + }, + }) + + factory.Start(client.informerStop) + factory.WaitForCacheSync(client.informerStop) + + return client, nil +} + +func (c *k8sPodClient) addPod(pod *corev1.Pod) { + c.mu.Lock() + defer c.mu.Unlock() + + podName := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name) + if pod.Status.StartTime != nil { + startTime := pod.Status.StartTime.Time + if pod.Status.PodIP != "" { + c.startTimeCache[fmt.Sprintf("ip:%s", pod.Status.PodIP)] = startTime + } + c.startTimeCache[fmt.Sprintf("name:%s", podName)] = startTime + c.startTimeCache[fmt.Sprintf("uid:%s", pod.UID)] = startTime + } +} + +func (c *k8sPodClient) deletePod(pod *corev1.Pod) { + c.mu.Lock() + defer c.mu.Unlock() + + if pod.Status.PodIP != "" { + delete(c.startTimeCache, fmt.Sprintf("ip:%s", pod.Status.PodIP)) + } + + podName := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name) + delete(c.startTimeCache, fmt.Sprintf("name:%s", podName)) + delete(c.startTimeCache, fmt.Sprintf("uid:%s", pod.UID)) +} + +func (c *k8sPodClient) GetPodStartTime(_ context.Context, podID podIdentifier) time.Time { + c.mu.RLock() + defer c.mu.RUnlock() + + var cacheKey string + + switch podID.Type { + case podIP: + cacheKey = fmt.Sprintf("ip:%s", podID.Value) + case podName: + cacheKey = fmt.Sprintf("name:%s", podID.Value) + case podUID: + cacheKey = fmt.Sprintf("uid:%s", podID.Value) + default: + return time.Time{} + } + + if startTime, ok := c.startTimeCache[cacheKey]; ok { + return startTime + } + + return time.Time{} +} + +func (c *k8sPodClient) Stop() { + close(c.informerStop) +} From db3e8523e33778c8fa26b95d1da41519cf55665f Mon Sep 17 00:00:00 2001 From: Erik Wu Date: Wed, 22 Oct 2025 09:29:16 -0500 Subject: [PATCH 2/5] [processor/metricstarttime]: - add new `starttimeattribute` strategy to use pod start time (or pod containersready time) as ST - allow all strategies to only apply to filtered metrics - allow all strategies to skip metrics with CTs already set --- processor/metricstarttimeprocessor/config.go | 2 + processor/metricstarttimeprocessor/factory.go | 14 +- .../internal/common/options.go | 13 + .../internal/common/utils.go | 29 + .../internal/starttimeattribute/adjuster.go | 65 +-- .../starttimeattribute/adjuster_test.go | 497 +++++++++++++++--- .../internal/starttimeattribute/k8s.go | 4 +- .../internal/starttimeattribute/pod_client.go | 145 +++-- .../internal/starttimemetric/adjuster.go | 16 +- .../internal/starttimemetric/adjuster_test.go | 7 +- .../internal/subtractinitial/adjuster.go | 17 +- .../internal/subtractinitial/adjuster_test.go | 55 +- .../internal/truereset/adjuster.go | 16 +- .../internal/truereset/adjuster_test.go | 43 +- 14 files changed, 686 insertions(+), 237 deletions(-) create mode 100644 processor/metricstarttimeprocessor/internal/common/options.go create mode 100644 processor/metricstarttimeprocessor/internal/common/utils.go diff --git a/processor/metricstarttimeprocessor/config.go b/processor/metricstarttimeprocessor/config.go index 2024ee097bff8..a325fdd61cc50 100644 --- a/processor/metricstarttimeprocessor/config.go +++ b/processor/metricstarttimeprocessor/config.go @@ -29,6 +29,8 @@ type Config struct { // AttributesFilters only applies to the start_time_attribute strategy to construct specific k8s api informer filters AttributesFilters starttimeattribute.AttributesFilterConfig `mapstructure:"attributes_filters"` SkipIfCTExists bool `mapstructure:"skip_if_ct_exists"` + + UseContainerReadinessTime bool `mapstructure:"use_container_readiness_time"` } var _ component.Config = (*Config)(nil) diff --git a/processor/metricstarttimeprocessor/factory.go b/processor/metricstarttimeprocessor/factory.go index 438af9ab45d1f..5fc849b7827bd 100644 --- a/processor/metricstarttimeprocessor/factory.go +++ b/processor/metricstarttimeprocessor/factory.go @@ -7,6 +7,7 @@ import ( "context" "regexp" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "go.opentelemetry.io/collector/component" @@ -40,14 +41,19 @@ func createMetricsProcessor( if err != nil { return nil, err } + opts := common.AdjustmentOptions{ + GCInterval: rCfg.GCInterval, + SkipIfCTExists: rCfg.SkipIfCTExists, + Filter: filter, + } var adjustMetrics processorhelper.ProcessMetricsFunc switch rCfg.Strategy { case truereset.Type: - adjuster := truereset.NewAdjuster(set.TelemetrySettings, rCfg.GCInterval) + adjuster := truereset.NewAdjuster(set.TelemetrySettings, opts) adjustMetrics = adjuster.AdjustMetrics case subtractinitial.Type: - adjuster := subtractinitial.NewAdjuster(set.TelemetrySettings, rCfg.GCInterval) + adjuster := subtractinitial.NewAdjuster(set.TelemetrySettings, opts) adjustMetrics = adjuster.AdjustMetrics case starttimemetric.Type: var startTimeMetricRegex *regexp.Regexp @@ -58,10 +64,10 @@ func createMetricsProcessor( return nil, err } } - adjuster := starttimemetric.NewAdjuster(set.TelemetrySettings, startTimeMetricRegex, rCfg.GCInterval) + adjuster := starttimemetric.NewAdjuster(set.TelemetrySettings, startTimeMetricRegex, opts) adjustMetrics = adjuster.AdjustMetrics case starttimeattribute.Type: - adjuster, err := starttimeattribute.NewAdjuster(set.TelemetrySettings, filter, rCfg.AttributesFilters, rCfg.SkipIfCTExists, rCfg.GCInterval) + adjuster, err := starttimeattribute.NewAdjuster(set.TelemetrySettings, rCfg.AttributesFilters, rCfg.UseContainerReadinessTime, opts) if err != nil { return nil, err } diff --git a/processor/metricstarttimeprocessor/internal/common/options.go b/processor/metricstarttimeprocessor/internal/common/options.go new file mode 100644 index 0000000000000..191c05f254f17 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/common/options.go @@ -0,0 +1,13 @@ +package common + +import ( + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" +) + +type AdjustmentOptions struct { + Filter filterset.FilterSet + SkipIfCTExists bool + GCInterval time.Duration +} diff --git a/processor/metricstarttimeprocessor/internal/common/utils.go b/processor/metricstarttimeprocessor/internal/common/utils.go new file mode 100644 index 0000000000000..b72cb797e6188 --- /dev/null +++ b/processor/metricstarttimeprocessor/internal/common/utils.go @@ -0,0 +1,29 @@ +package common + +import "go.opentelemetry.io/collector/pdata/pmetric" + +func HasStartTimeSet(metric pmetric.Metric) bool { + switch metric.Type() { + case pmetric.MetricTypeSum: + dataPoints := metric.Sum().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + case pmetric.MetricTypeHistogram: + dataPoints := metric.Histogram().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + case pmetric.MetricTypeExponentialHistogram: + dataPoints := metric.ExponentialHistogram().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + case pmetric.MetricTypeSummary: + dataPoints := metric.Summary().DataPoints() + if dataPoints.Len() > 0 { + return dataPoints.At(0).StartTimestamp() != 0 + } + } + return false +} diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go index f7eddb7ed7dee..24380c727f460 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" @@ -21,32 +21,31 @@ const ( ) type Adjuster struct { + set component.TelemetrySettings apiConfig k8sconfig.APIConfig podClient podClient - logger *zap.Logger - filter filterset.FilterSet referenceCache *datapointstorage.Cache - skipIfCTExists bool + opts common.AdjustmentOptions } -type podClientFactory func(context.Context, k8sconfig.APIConfig, informerFilter) (podClient, error) +type podClientFactory func(context.Context, k8sconfig.APIConfig, informerFilter, bool) (podClient, error) // NewAdjuster returns a new Adjuster which adjust metrics' start times based on the initial received points. -func NewAdjuster(set component.TelemetrySettings, filter filterset.FilterSet, attributeFilterConfig AttributesFilterConfig, skipIfCTExists bool, gcInterval time.Duration) (*Adjuster, error) { - return NewAdjusterWithFactory(set, newK8sPodClient, filter, attributeFilterConfig, skipIfCTExists, gcInterval) +func NewAdjuster(set component.TelemetrySettings, attributeFilterConfig AttributesFilterConfig, useContainerReadiness bool, opts common.AdjustmentOptions) (*Adjuster, error) { + return NewAdjusterWithFactory(set, newK8sPodClient, attributeFilterConfig, useContainerReadiness, opts) } // NewAdjusterWithFactory returns a new Adjuster with a custom pod client factory -func NewAdjusterWithFactory(set component.TelemetrySettings, factory podClientFactory, filter filterset.FilterSet, attributeFilterConfig AttributesFilterConfig, skipIfCTExists bool, gcInterval time.Duration) (*Adjuster, error) { +func NewAdjusterWithFactory(set component.TelemetrySettings, factory podClientFactory, attributeFilterConfig AttributesFilterConfig, useContainerReadiness bool, opts common.AdjustmentOptions) (*Adjuster, error) { apiConfig := k8sconfig.APIConfig{ AuthType: k8sconfig.AuthTypeServiceAccount, } ctx := context.Background() k8sInformerFilter := toInformerFilter(attributeFilterConfig) - client, err := factory(ctx, apiConfig, k8sInformerFilter) + client, err := factory(ctx, apiConfig, k8sInformerFilter, useContainerReadiness) if err != nil { return nil, fmt.Errorf("failed to create pod client: %w", err) } @@ -54,10 +53,9 @@ func NewAdjusterWithFactory(set component.TelemetrySettings, factory podClientFa return &Adjuster{ apiConfig: apiConfig, podClient: client, - logger: set.Logger, - filter: filter, - skipIfCTExists: skipIfCTExists, - referenceCache: datapointstorage.NewCache(gcInterval), + set: set, + opts: opts, + referenceCache: datapointstorage.NewCache(opts.GCInterval), }, nil } @@ -85,29 +83,27 @@ func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) ( metricName := metric.Name() // Only process cumulative metrics if !a.isCumulativeMetric(metric) { - a.logger.Debug("metric is not cumulative, skipping", + a.set.Logger.Debug("metric is not cumulative, skipping", zap.String("metricName", metricName)) continue } - if !a.filter.Matches(metricName) { - a.logger.Debug("metric not included by filter, skipper", + if a.opts.Filter != nil && !a.opts.Filter.Matches(metricName) { + a.set.Logger.Debug("metric not included by filter, skipping", zap.String("metricName", metricName)) continue } - if a.skipIfCTExists && a.hasStartTimeSet(metric) { + if a.opts.SkipIfCTExists && common.HasStartTimeSet(metric) { continue } - // Get pod start time startTime := a.podClient.GetPodStartTime(ctx, *podID) if startTime.IsZero() { - a.logger.Debug("no known start time for pod", + a.set.Logger.Debug("no known start time for pod", zap.Stringer("podIDTypee", podID.Type), zap.String("podID", podID.Value), zap.String("metricName", metricName), ) continue } - // Set start time for all data points a.setStartTimeForMetric(tsm, metric, startTime) } } @@ -117,30 +113,7 @@ func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) ( return metrics, nil } -// only look at the first datapoint -func (a *Adjuster) hasStartTimeSet(metric pmetric.Metric) bool { - switch metric.Type() { - case pmetric.MetricTypeSum: - dataPoints := metric.Sum().DataPoints() - if dataPoints.Len() > 0 { - return dataPoints.At(0).StartTimestamp() != 0 - } - case pmetric.MetricTypeHistogram: - dataPoints := metric.Histogram().DataPoints() - if dataPoints.Len() > 0 { - return dataPoints.At(0).StartTimestamp() != 0 - } - case pmetric.MetricTypeExponentialHistogram: - dataPoints := metric.ExponentialHistogram().DataPoints() - if dataPoints.Len() > 0 { - return dataPoints.At(0).StartTimestamp() != 0 - } - } - return false -} - func (a *Adjuster) extractPodIdentifier(attrs pcommon.Map) *podIdentifier { - // Check for pod name with namespace podNameVal, nameOk := attrs.Get("k8s.pod.name") namespaceVal, nsOk := attrs.Get("k8s.namespace.name") if nameOk && nsOk { @@ -149,18 +122,12 @@ func (a *Adjuster) extractPodIdentifier(attrs pcommon.Map) *podIdentifier { Type: podName, } } - - // Check for pod UID if uidVal, ok := attrs.Get("k8s.pod.uid"); ok { return &podIdentifier{ Value: uidVal.AsString(), Type: podUID, } } - - // Check for pod IP - // Note: pod IP is not a unique identifier i.e for host-networked pods or ds pods - // so this is a fallback option if ipVal, ok := attrs.Get("k8s.pod.ip"); ok { return &podIdentifier{ Value: ipVal.AsString(), diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go index f8287b26511eb..34496225d0110 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go @@ -7,7 +7,9 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/testhelper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" @@ -32,6 +34,11 @@ const testMetricName = "test_metric" func TestAdjustMetrics(t *testing.T) { testStartTime := time.Now().Add(-1 * time.Hour) + testStartTimestamp := pcommon.NewTimestampFromTime(testStartTime) + currentTime := time.Now() + currentTimestamp := pcommon.NewTimestampFromTime(currentTime) + nonZeroStartTime := time.Now().Add(-30 * time.Minute) + nonZeroStartTimestamp := pcommon.NewTimestampFromTime(nonZeroStartTime) mockClient := &mockPodClient{ startTimes: map[string]time.Time{ @@ -41,98 +48,440 @@ func TestAdjustMetrics(t *testing.T) { }, } - tests := []struct { - name string - attrs map[string]string - expectAdjusted bool - isCumulative bool - startTimeUnixSec int64 - filter filterset.FilterSet - }{ + // Test case 1: cumulative metric with pod IP + + script1 := []*testhelper.MetricsAdjusterTest{ { - name: "cumulative metric with pod IP", - attrs: map[string]string{ - "k8s.pod.ip": "10.0.0.1", - }, - expectAdjusted: true, - isCumulative: true, + Description: "Cumulative metric with pod IP - start time should be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(testStartTimestamp) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), }, + } + + adjuster1, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{}, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster1, script1) + + // Test case 2: cumulative metric with pod name and namespace + script2 := []*testhelper.MetricsAdjusterTest{ { - name: "cumulative metric with pod name", - attrs: map[string]string{ - "k8s.pod.name": "my-pod", - "k8s.namespace.name": "default", - }, - expectAdjusted: true, - isCumulative: true, + Description: "Cumulative metric with pod name and namespace - start time should be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.name", "my-pod") + rm.Resource().Attributes().PutStr("k8s.namespace.name", "default") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.name", "my-pod") + rm.Resource().Attributes().PutStr("k8s.namespace.name", "default") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(testStartTimestamp) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), }, + } + + adjuster2, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{}, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster2, script2) + + // Test case 3: cumulative metric with pod UID + script3 := []*testhelper.MetricsAdjusterTest{ { - name: "cumulative metric with pod UID", - attrs: map[string]string{ - "k8s.pod.uid": "uid-12345", - }, - expectAdjusted: true, - isCumulative: true, + Description: "Cumulative metric with pod UID - start time should be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.uid", "uid-12345") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.uid", "uid-12345") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(testStartTimestamp) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), }, + } + + adjuster3, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{}, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster3, script3) + + // Test case 4: delta metric should not be adjusted + script4 := []*testhelper.MetricsAdjusterTest{ { - name: "delta metric should not be adjusted", - attrs: map[string]string{"k8s.pod.ip": "10.0.0.1"}, - expectAdjusted: false, - isCumulative: false, + Description: "Delta metric with pod IP - start time should NOT be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityDelta) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityDelta) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + }, + } + + adjuster4, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{}, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster4, script4) + + // Test case 5: metric without pod identifier + script5 := []*testhelper.MetricsAdjusterTest{ + { + Description: "Metric without pod identifier - start time should NOT be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("other", "value") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("other", "value") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), }, + } + + adjuster5, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{}, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster5, script5) + + // Test case 6: metric excluded by NoOp filter + script6 := []*testhelper.MetricsAdjusterTest{ { - name: "metric without pod identifier", - attrs: map[string]string{"other": "value"}, - expectAdjusted: false, - isCumulative: true, + Description: "Metric excluded by NoOp filter - start time should NOT be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), }, + } + + adjuster6, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{NoMatch: true}, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster6, script6) + + // Test case 7: metric included by filter + script7 := []*testhelper.MetricsAdjusterTest{ { - name: "metric excluded by filter", - attrs: map[string]string{ - "k8s.pod.ip": "10.0.0.1", - }, - expectAdjusted: false, - isCumulative: true, - filter: filter.NoOpFilter{NoMatch: true}, + Description: "Metric included by filter - start time should be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(testStartTimestamp) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + }, + } + + filterSet7, err := filter.NewFilter(filter.FilterConfig{ + Metrics: []string{testMetricName}, + Config: filterset.Config{ + MatchType: filterset.Strict, }, + }, filter.FilterConfig{}) + require.NoError(t, err) + + adjuster7, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filterSet7, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster7, script7) + + // Test case 8: metric excluded by filter + script8 := []*testhelper.MetricsAdjusterTest{ { - name: "metric excluded because it has a non-zero start time", - attrs: map[string]string{ - "k8s.pod.ip": "10.0.0.1", - }, - expectAdjusted: false, - isCumulative: true, - startTimeUnixSec: time.Now().Unix(), + Description: "Metric excluded by filter - start time should NOT be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(0) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - metrics := createTestMetrics(testMetricName, tt.attrs, tt.isCumulative, tt.startTimeUnixSec) - originalStartTime := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).StartTimestamp() - metricNameFilter := tt.filter - if metricNameFilter == nil { - metricNameFilter = filter.NoOpFilter{} - } - adjuster, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter) (podClient, error) { - return mockClient, nil - }, metricNameFilter, AttributesFilterConfig{}, true, 5*time.Minute) - require.NoError(t, err) - adjustedMetrics, err := adjuster.AdjustMetrics(context.Background(), metrics) - require.NoError(t, err) - - adjustedStartTime := adjustedMetrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).StartTimestamp() - - if tt.expectAdjusted { - expectedTime := pcommon.NewTimestampFromTime(testStartTime) - assert.Equal(t, expectedTime, adjustedStartTime) - assert.NotEqual(t, originalStartTime, adjustedStartTime) - } else { - assert.Equal(t, originalStartTime, adjustedStartTime) - } + filterSet8, err := filter.NewFilter(filter.FilterConfig{}, + filter.FilterConfig{ + Metrics: []string{testMetricName}, + Config: filterset.Config{ + MatchType: filterset.Strict, + }, }) + require.NoError(t, err) + + adjuster8, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filterSet8, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster8, script8) + + // Test case 9: metric with non-zero start time should not be adjusted + script9 := []*testhelper.MetricsAdjusterTest{ + { + Description: "Metric with non-zero start time - start time should NOT be adjusted", + Metrics: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(nonZeroStartTimestamp) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + Adjusted: func() pmetric.Metrics { + m := pmetric.NewMetrics() + rm := m.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") + sm := rm.ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName(testMetricName) + sum := metric.SetEmptySum() + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(nonZeroStartTimestamp) + dp.SetTimestamp(currentTimestamp) + dp.SetDoubleValue(100.0) + return m + }(), + }, } + + adjuster9, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: filter.NoOpFilter{}, + SkipIfCTExists: true, + }) + require.NoError(t, err) + testhelper.RunScript(t, adjuster9, script9) } func createTestMetrics(name string, attrs map[string]string, cumulative bool, startTime int64) pmetric.Metrics { diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go index cc52e31ea4853..8f895a3eb9def 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go @@ -40,7 +40,7 @@ type podClient interface { type informerFilter struct { node string namespace string - LabelFilters []labelFilter + labelFilters []labelFilter } type labelFilter struct { @@ -55,7 +55,7 @@ func toInformerFilter(cfg AttributesFilterConfig) informerFilter { namespace: cfg.Namespace, } for _, label := range cfg.Labels { - f.LabelFilters = append(f.LabelFilters, labelFilter{ + f.labelFilters = append(f.labelFilters, labelFilter{ Key: label.Key, Value: label.Value, Op: selection.Operator(label.Op), diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go index 053e1af587dd3..e94e5ae64220b 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go @@ -17,22 +17,25 @@ import ( ) type k8sPodClient struct { - clientset k8s.Interface - informerStop chan struct{} - informer cache.SharedIndexInformer - mu sync.RWMutex - startTimeCache map[string]time.Time + useContainerReadiness bool + clientset k8s.Interface + informerStop chan struct{} + informer cache.SharedIndexInformer + mu sync.RWMutex + startTimeCache map[string]time.Time } -func newK8sPodClient(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter) (podClient, error) { +const defaultCacheSyncDuration = 10 * time.Minute + +func newK8sPodClient(_ context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { clientset, err := k8sconfig.MakeClient(apiConfig) if err != nil { return nil, fmt.Errorf("failed to create k8s client: %w", err) } labelSelector := labels.Everything() - if len(filter.LabelFilters) > 0 { - for _, lf := range filter.LabelFilters { + if len(filter.labelFilters) > 0 { + for _, lf := range filter.labelFilters { requirement, err := labels.NewRequirement(lf.Key, lf.Op, []string{lf.Value}) if err != nil { return nil, fmt.Errorf("failed to create label requirement: %w", err) @@ -46,57 +49,68 @@ func newK8sPodClient(ctx context.Context, apiConfig k8sconfig.APIConfig, filter fieldSelectors = append(fieldSelectors, fields.OneTermEqualSelector("spec.nodeName", filter.node)) } fieldSelector := fields.AndSelectors(fieldSelectors...) - options := informers.WithTweakListOptions(func(opts *metav1.ListOptions) { + options := []informers.SharedInformerOption{informers.WithTweakListOptions(func(opts *metav1.ListOptions) { opts.LabelSelector = labelSelector.String() opts.FieldSelector = fieldSelector.String() - }) - - var factory informers.SharedInformerFactory + })} if filter.namespace != "" { - factory = informers.NewSharedInformerFactoryWithOptions( - clientset, - time.Hour, - informers.WithNamespace(filter.namespace), - options, - ) - } else { - factory = informers.NewSharedInformerFactoryWithOptions( - clientset, - time.Hour, - options, - ) + options = append(options, informers.WithNamespace(filter.namespace)) } - podInformer := factory.Core().V1().Pods().Informer() + factory := informers.NewSharedInformerFactoryWithOptions( + clientset, + defaultCacheSyncDuration, + options..., + ) + podInformer := factory.Core().V1().Pods().Informer() client := &k8sPodClient{ - clientset: clientset, - informerStop: make(chan struct{}), - informer: podInformer, - startTimeCache: make(map[string]time.Time), + clientset: clientset, + informerStop: make(chan struct{}), + informer: podInformer, + startTimeCache: make(map[string]time.Time), + useContainerReadiness: useContainerReadiness, } - podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + _, err = podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { - if pod, ok := obj.(*corev1.Pod); ok { - client.addPod(pod) + pod, ok := obj.(*corev1.Pod) + if !ok { + return } + client.mu.Lock() + defer client.mu.Unlock() + client.addPod(pod) + }, UpdateFunc: func(oldObj, newObj interface{}) { - if oldPod, ok := oldObj.(*corev1.Pod); ok { - client.deletePod(oldPod) + oldPod, ok := oldObj.(*corev1.Pod) + if !ok { + return } - if newPod, ok := newObj.(*corev1.Pod); ok { - client.addPod(newPod) + newPod, ok := newObj.(*corev1.Pod) + if !ok { + return } + client.mu.Lock() + defer client.mu.Unlock() + + client.deletePod(oldPod) + client.addPod(newPod) }, DeleteFunc: func(obj interface{}) { - if pod, ok := obj.(*corev1.Pod); ok { - client.deletePod(pod) + pod, ok := obj.(*corev1.Pod) + if !ok { + return } + client.mu.Lock() + defer client.mu.Unlock() + client.deletePod(pod) }, }) - + if err != nil { + return nil, err + } factory.Start(client.informerStop) factory.WaitForCacheSync(client.informerStop) @@ -104,24 +118,53 @@ func newK8sPodClient(ctx context.Context, apiConfig k8sconfig.APIConfig, filter } func (c *k8sPodClient) addPod(pod *corev1.Pod) { - c.mu.Lock() - defer c.mu.Unlock() - podName := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name) - if pod.Status.StartTime != nil { - startTime := pod.Status.StartTime.Time - if pod.Status.PodIP != "" { - c.startTimeCache[fmt.Sprintf("ip:%s", pod.Status.PodIP)] = startTime + podStatus := pod.Status + var startTime time.Time + if c.useContainerReadiness { + ready, readyTime := c.containerReadinessTime(pod) + if ready { + startTime = readyTime } - c.startTimeCache[fmt.Sprintf("name:%s", podName)] = startTime - c.startTimeCache[fmt.Sprintf("uid:%s", pod.UID)] = startTime + } else { + if podStatus.StartTime != nil { + startTime = podStatus.StartTime.Time + } + } + if startTime.IsZero() { + return } + if podStatus.PodIP != "" { + c.startTimeCache[fmt.Sprintf("ip:%s", podStatus.PodIP)] = startTime + } + c.startTimeCache[fmt.Sprintf("name:%s", podName)] = startTime + c.startTimeCache[fmt.Sprintf("uid:%s", pod.UID)] = startTime } -func (c *k8sPodClient) deletePod(pod *corev1.Pod) { - c.mu.Lock() - defer c.mu.Unlock() +func (c *k8sPodClient) containerReadinessTime(pod *corev1.Pod) (bool, time.Time) { + var containerReadyTime time.Time + var podReady, containersReady bool + for _, condition := range pod.Status.Conditions { + if condition.Type == corev1.PodReady { + if condition.Status == corev1.ConditionTrue { + podReady = true + } else { + return false, containerReadyTime + } + } + if condition.Type == corev1.ContainersReady { + if condition.Status == corev1.ConditionTrue { + containersReady = true + containerReadyTime = condition.LastTransitionTime.Time + } else { + return false, containerReadyTime + } + } + } + return podReady && containersReady, containerReadyTime +} +func (c *k8sPodClient) deletePod(pod *corev1.Pod) { if pod.Status.PodIP != "" { delete(c.startTimeCache, fmt.Sprintf("ip:%s", pod.Status.PodIP)) } diff --git a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go index 5079c9e2d0d2a..d8d87a35cb9c4 100644 --- a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go @@ -9,6 +9,7 @@ import ( "regexp" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" @@ -42,14 +43,16 @@ type Adjuster struct { referenceValueCache *datapointstorage.Cache startTimeMetricRegex *regexp.Regexp set component.TelemetrySettings + opts common.AdjustmentOptions } // NewAdjuster returns a new Adjuster which adjust metrics' start times based on the initial received points. -func NewAdjuster(set component.TelemetrySettings, startTimeMetricRegex *regexp.Regexp, gcInterval time.Duration) *Adjuster { +func NewAdjuster(set component.TelemetrySettings, startTimeMetricRegex *regexp.Regexp, opts common.AdjustmentOptions) *Adjuster { return &Adjuster{ - referenceValueCache: datapointstorage.NewCache(gcInterval), + referenceValueCache: datapointstorage.NewCache(opts.GCInterval), set: set, startTimeMetricRegex: startTimeMetricRegex, + opts: opts, } } @@ -74,6 +77,15 @@ func (a *Adjuster) AdjustMetrics(_ context.Context, metrics pmetric.Metrics) (pm ilm := rm.ScopeMetrics().At(j) for k := 0; k < ilm.Metrics().Len(); k++ { metric := ilm.Metrics().At(k) + metricName := metric.Name() + if a.opts.Filter != nil && !a.opts.Filter.Matches(metricName) { + a.set.Logger.Debug("metric not included by filter, skipping", + zap.String("metricName", metricName)) + continue + } + if a.opts.SkipIfCTExists && common.HasStartTimeSet(metric) { + continue + } switch metric.Type() { case pmetric.MetricTypeGauge: continue diff --git a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go index e0270cb084154..1241950243ade 100644 --- a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" @@ -144,7 +145,7 @@ func TestStartTimeMetricMatch(t *testing.T) { // directly. approximateCollectorStartTime = collectorStartTime.AsTime() - stma := NewAdjuster(componenttest.NewNopTelemetrySettings(), tt.startTimeMetricRegex, time.Minute) + stma := NewAdjuster(componenttest.NewNopTelemetrySettings(), tt.startTimeMetricRegex, common.AdjustmentOptions{GCInterval: time.Minute}) // We need to make sure the job and instance labels are set before the adjuster is used. pmetrics := tt.inputs @@ -238,7 +239,7 @@ func TestStartTimeMetricFallback(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - stma := NewAdjuster(componenttest.NewNopTelemetrySettings(), tt.startTimeMetricRegex, time.Minute) + stma := NewAdjuster(componenttest.NewNopTelemetrySettings(), tt.startTimeMetricRegex, common.AdjustmentOptions{GCInterval: time.Minute}) // To test that the adjuster is using the fallback correctly, override the fallback time to use // directly. @@ -388,5 +389,5 @@ func TestMultiMetrics(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), regexp.MustCompile("^.*_process_start_time_seconds$"), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), regexp.MustCompile("^.*_process_start_time_seconds$"), common.AdjustmentOptions{GCInterval: time.Minute}), script) } diff --git a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go index 8003dd8143fc5..3cd59acb55f72 100644 --- a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go @@ -7,9 +7,11 @@ import ( "context" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" @@ -27,13 +29,15 @@ const Type = "subtract_initial_point" type Adjuster struct { referenceCache *datapointstorage.Cache set component.TelemetrySettings + opts common.AdjustmentOptions } // NewAdjuster returns a new Adjuster which adjust metrics' start times based on the initial received points. -func NewAdjuster(set component.TelemetrySettings, gcInterval time.Duration) *Adjuster { +func NewAdjuster(set component.TelemetrySettings, opts common.AdjustmentOptions) *Adjuster { return &Adjuster{ - referenceCache: datapointstorage.NewCache(gcInterval), + referenceCache: datapointstorage.NewCache(opts.GCInterval), set: set, + opts: opts, } } @@ -63,6 +67,15 @@ func (a *Adjuster) AdjustMetrics(_ context.Context, metrics pmetric.Metrics) (pm ilm := rm.ScopeMetrics().At(j) for k := range ilm.Metrics().Len() { metric := ilm.Metrics().At(k) + metricName := metric.Name() + if a.opts.Filter != nil && !a.opts.Filter.Matches(metricName) { + a.set.Logger.Debug("metric not included by filter, skipping", + zap.String("metricName", metricName)) + continue + } + if a.opts.SkipIfCTExists && common.HasStartTimeSet(metric) { + continue + } switch dataType := metric.Type(); dataType { case pmetric.MetricTypeHistogram: adjustMetricHistogram(previousValueTsm, metric) diff --git a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go index a442168074c40..ba4c1b46cd6f5 100644 --- a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" @@ -67,7 +68,7 @@ func TestGauge(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.GaugeMetric(gauge1, testhelper.DoublePoint(k1v1k2v2, t3, t3, 55))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSum(t *testing.T) { @@ -98,7 +99,7 @@ func TestSum(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.SumMetric(sum1, testhelper.DoublePoint(k1v1k2v2, t2, t5, 72))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumInt(t *testing.T) { @@ -129,7 +130,7 @@ func TestSumInt(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.SumMetric(sum1, testhelper.IntPoint(k1v1k2v2, t2, t5, 72))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumNoStartTimestamp(t *testing.T) { @@ -160,7 +161,7 @@ func TestSumNoStartTimestamp(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.SumMetric(sum1, testhelper.DoublePoint(k1v1k2v2, t2, t5, 72))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumWithDifferentResources(t *testing.T) { @@ -191,7 +192,7 @@ func TestSumWithDifferentResources(t *testing.T) { Adjusted: testhelper.MetricsFromResourceMetrics(testhelper.ResourceMetrics("job1", "instance1", testhelper.SumMetric(sum1, testhelper.DoublePoint(k1v1k2v2, t2, t5, 72))), testhelper.ResourceMetrics("job2", "instance2", testhelper.SumMetric(sum2, testhelper.DoublePoint(k1v1k2v2, t4, t5, 10)))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryNoCount(t *testing.T) { @@ -218,7 +219,7 @@ func TestSummaryNoCount(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryFlagNoRecordedValue(t *testing.T) { @@ -235,7 +236,7 @@ func TestSummaryFlagNoRecordedValue(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummary(t *testing.T) { @@ -278,7 +279,7 @@ func TestSummary(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryNoStartTimestamps(t *testing.T) { @@ -321,7 +322,7 @@ func TestSummaryNoStartTimestamps(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogram(t *testing.T) { @@ -348,7 +349,7 @@ func TestHistogram(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.HistogramMetric(histogram1, testhelper.HistogramPoint(k1v1k2v2, t4, t5, bounds0, []uint64{7, 4, 20, 11}))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogramNoStartTimestamps(t *testing.T) { @@ -371,7 +372,7 @@ func TestHistogramNoStartTimestamps(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.HistogramMetric(histogram1, testhelper.HistogramPoint(k1v1k2v2, t2, t4, bounds0, []uint64{7, 4, 2, 12}))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogramFlagNoRecordedValue(t *testing.T) { @@ -388,7 +389,7 @@ func TestHistogramFlagNoRecordedValue(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -405,7 +406,7 @@ func TestHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } // In TestExponentHistogram we exclude negative buckets on purpose as they are @@ -436,7 +437,7 @@ func TestExponentialHistogram(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.ExponentialHistogramMetric(exponentialHistogram1, testhelper.ExponentialHistogramPoint(k1v1k2v2, t4, t5, 3, 1, 0, []uint64{}, -2, []uint64{6, 3, 1, 11}))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestExponentialHistogramNoStartTimestamps(t *testing.T) { @@ -459,7 +460,7 @@ func TestExponentialHistogramNoStartTimestamps(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.ExponentialHistogramMetric(exponentialHistogram1, testhelper.ExponentialHistogramPoint(k1v1k2v2, t2, t4, 3, 1, 0, []uint64{}, -2, []uint64{7, 4, 2, 12}))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestExponentialHistogramFlagNoRecordedValue(t *testing.T) { @@ -476,7 +477,7 @@ func TestExponentialHistogramFlagNoRecordedValue(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestExponentialHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -493,7 +494,7 @@ func TestExponentialHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -510,7 +511,7 @@ func TestSummaryFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestGaugeFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -527,7 +528,7 @@ func TestGaugeFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -544,7 +545,7 @@ func TestSumFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestMultiMetrics(t *testing.T) { @@ -608,7 +609,7 @@ func TestMultiMetrics(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestNewDataPointsAdded(t *testing.T) { @@ -661,7 +662,7 @@ func TestNewDataPointsAdded(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestMultiTimeseries(t *testing.T) { @@ -720,7 +721,7 @@ func TestMultiTimeseries(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestDifferentStartTimes(t *testing.T) { @@ -750,7 +751,7 @@ func TestDifferentStartTimes(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestResetAfterInitialStart(t *testing.T) { @@ -804,7 +805,7 @@ func TestResetAfterInitialStart(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestTsGC(t *testing.T) { @@ -858,7 +859,7 @@ func TestTsGC(t *testing.T) { }, } - ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute) + ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}) resourceAttr := "0" resourceAttrs := pcommon.NewMap() @@ -929,7 +930,7 @@ func TestJobGC(t *testing.T) { } gcInterval := 10 * time.Millisecond - ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), gcInterval) + ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: gcInterval}) // run job 1, round 1 - all entries marked testhelper.RunScript(t, ma, job1Script1, "0") diff --git a/processor/metricstarttimeprocessor/internal/truereset/adjuster.go b/processor/metricstarttimeprocessor/internal/truereset/adjuster.go index a6ebaed349bc1..1677d4baa2374 100644 --- a/processor/metricstarttimeprocessor/internal/truereset/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/truereset/adjuster.go @@ -7,6 +7,7 @@ import ( "context" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" @@ -33,13 +34,15 @@ const Type = "true_reset_point" type Adjuster struct { startTimeCache *datapointstorage.Cache set component.TelemetrySettings + opts common.AdjustmentOptions } // NewAdjuster returns a new Adjuster which adjust metrics' start times based on the initial received points. -func NewAdjuster(set component.TelemetrySettings, gcInterval time.Duration) *Adjuster { +func NewAdjuster(set component.TelemetrySettings, opts common.AdjustmentOptions) *Adjuster { return &Adjuster{ - startTimeCache: datapointstorage.NewCache(gcInterval), + startTimeCache: datapointstorage.NewCache(opts.GCInterval), set: set, + opts: opts, } } @@ -58,6 +61,15 @@ func (a *Adjuster) AdjustMetrics(_ context.Context, metrics pmetric.Metrics) (pm ilm := rm.ScopeMetrics().At(j) for k := 0; k < ilm.Metrics().Len(); k++ { metric := ilm.Metrics().At(k) + metricName := metric.Name() + if a.opts.Filter != nil && !a.opts.Filter.Matches(metricName) { + a.set.Logger.Debug("metric not included by filter, skipping", + zap.String("metricName", metricName)) + continue + } + if a.opts.SkipIfCTExists && common.HasStartTimeSet(metric) { + continue + } switch dataType := metric.Type(); dataType { case pmetric.MetricTypeGauge: // gauges don't need to be adjusted so no additional processing is necessary diff --git a/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go b/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go index a84faf39b4f42..6741b62352b07 100644 --- a/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" @@ -67,7 +68,7 @@ func TestGauge(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.GaugeMetric(gauge1, testhelper.DoublePoint(k1v1k2v2, t3, t3, 55))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSum(t *testing.T) { @@ -98,7 +99,7 @@ func TestSum(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.SumMetric(sum1, testhelper.DoublePoint(k1v1k2v2, t2, t5, 72))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumInt(t *testing.T) { @@ -129,7 +130,7 @@ func TestSumInt(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.SumMetric(sum1, testhelper.IntPoint(k1v1k2v2, t2, t5, 72))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumWithDifferentResources(t *testing.T) { @@ -160,7 +161,7 @@ func TestSumWithDifferentResources(t *testing.T) { Adjusted: testhelper.MetricsFromResourceMetrics(testhelper.ResourceMetrics("job1", "instance1", testhelper.SumMetric(sum1, testhelper.DoublePoint(k1v1k2v2, t2, t5, 72))), testhelper.ResourceMetrics("job2", "instance2", testhelper.SumMetric(sum2, testhelper.DoublePoint(k1v1k2v2, t4, t5, 10)))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryNoCount(t *testing.T) { @@ -187,7 +188,7 @@ func TestSummaryNoCount(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryFlagNoRecordedValue(t *testing.T) { @@ -204,7 +205,7 @@ func TestSummaryFlagNoRecordedValue(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummary(t *testing.T) { @@ -247,7 +248,7 @@ func TestSummary(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogram(t *testing.T) { @@ -270,7 +271,7 @@ func TestHistogram(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.HistogramMetric(histogram1, testhelper.HistogramPoint(k1v1k2v2, t2, t4, bounds0, []uint64{7, 4, 2, 12}))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogramFlagNoRecordedValue(t *testing.T) { @@ -287,7 +288,7 @@ func TestHistogramFlagNoRecordedValue(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -304,7 +305,7 @@ func TestHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } // In TestExponentHistogram we exclude negative buckets on purpose as they are @@ -331,7 +332,7 @@ func TestExponentialHistogram(t *testing.T) { Adjusted: testhelper.Metrics(testhelper.ExponentialHistogramMetric(exponentialHistogram1, testhelper.ExponentialHistogramPoint(k1v1k2v2, t2, t4, 3, 1, 0, []uint64{}, -2, []uint64{7, 4, 2, 12}))), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestExponentialHistogramFlagNoRecordedValue(t *testing.T) { @@ -348,7 +349,7 @@ func TestExponentialHistogramFlagNoRecordedValue(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestExponentialHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -365,7 +366,7 @@ func TestExponentialHistogramFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSummaryFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -382,7 +383,7 @@ func TestSummaryFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestGaugeFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -399,7 +400,7 @@ func TestGaugeFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestSumFlagNoRecordedValueFirstObservation(t *testing.T) { @@ -416,7 +417,7 @@ func TestSumFlagNoRecordedValueFirstObservation(t *testing.T) { }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestMultiMetrics(t *testing.T) { @@ -480,7 +481,7 @@ func TestMultiMetrics(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestNewDataPointsAdded(t *testing.T) { @@ -542,7 +543,7 @@ func TestNewDataPointsAdded(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestMultiTimeseries(t *testing.T) { @@ -601,7 +602,7 @@ func TestMultiTimeseries(t *testing.T) { ), }, } - testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute), script) + testhelper.RunScript(t, NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}), script) } func TestTsGC(t *testing.T) { @@ -655,7 +656,7 @@ func TestTsGC(t *testing.T) { }, } - ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), time.Minute) + ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: time.Minute}) resourceAttr := "0" resourceAttrs := pcommon.NewMap() @@ -725,7 +726,7 @@ func TestJobGC(t *testing.T) { } gcInterval := 1 * time.Millisecond - ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), gcInterval) + ma := NewAdjuster(componenttest.NewNopTelemetrySettings(), common.AdjustmentOptions{GCInterval: gcInterval}) // run job 1, round 1 - all entries marked testhelper.RunScript(t, ma, job1Script1, "0") From dc63c2b54662b81d7fff30105eca1518aaaa459e Mon Sep 17 00:00:00 2001 From: Erik Wu Date: Wed, 22 Oct 2025 13:39:30 -0500 Subject: [PATCH 3/5] linters --- processor/metricstarttimeprocessor/config.go | 4 +- processor/metricstarttimeprocessor/factory.go | 6 +- .../internal/common/options.go | 3 + .../internal/common/utils.go | 3 + .../internal/filter/filter.go | 3 + .../internal/filter/filter_test.go | 6 +- .../internal/starttimeattribute/adjuster.go | 24 +- .../starttimeattribute/adjuster_test.go | 520 +++--------------- .../internal/starttimeattribute/config.go | 3 + .../internal/starttimeattribute/k8s.go | 3 + .../internal/starttimeattribute/pod_client.go | 38 +- .../internal/starttimemetric/adjuster.go | 2 +- .../internal/starttimemetric/adjuster_test.go | 2 +- .../internal/subtractinitial/adjuster.go | 2 +- .../internal/subtractinitial/adjuster_test.go | 2 +- .../internal/truereset/adjuster.go | 2 +- .../internal/truereset/adjuster_test.go | 2 +- 17 files changed, 151 insertions(+), 474 deletions(-) diff --git a/processor/metricstarttimeprocessor/config.go b/processor/metricstarttimeprocessor/config.go index a325fdd61cc50..2edff565900f6 100644 --- a/processor/metricstarttimeprocessor/config.go +++ b/processor/metricstarttimeprocessor/config.go @@ -9,10 +9,10 @@ import ( "regexp" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "go.opentelemetry.io/collector/component" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimemetric" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/subtractinitial" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/truereset" diff --git a/processor/metricstarttimeprocessor/factory.go b/processor/metricstarttimeprocessor/factory.go index 5fc849b7827bd..927f33d3d32c5 100644 --- a/processor/metricstarttimeprocessor/factory.go +++ b/processor/metricstarttimeprocessor/factory.go @@ -7,15 +7,15 @@ import ( "context" "regexp" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/processor" "go.opentelemetry.io/collector/processor/processorhelper" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/metadata" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimeattribute" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/starttimemetric" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/subtractinitial" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/truereset" diff --git a/processor/metricstarttimeprocessor/internal/common/options.go b/processor/metricstarttimeprocessor/internal/common/options.go index 191c05f254f17..2bc9264461e57 100644 --- a/processor/metricstarttimeprocessor/internal/common/options.go +++ b/processor/metricstarttimeprocessor/internal/common/options.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package common import ( diff --git a/processor/metricstarttimeprocessor/internal/common/utils.go b/processor/metricstarttimeprocessor/internal/common/utils.go index b72cb797e6188..2e8e0c004623b 100644 --- a/processor/metricstarttimeprocessor/internal/common/utils.go +++ b/processor/metricstarttimeprocessor/internal/common/utils.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package common import "go.opentelemetry.io/collector/pdata/pmetric" diff --git a/processor/metricstarttimeprocessor/internal/filter/filter.go b/processor/metricstarttimeprocessor/internal/filter/filter.go index dffba91a5f6b8..191a6557860af 100644 --- a/processor/metricstarttimeprocessor/internal/filter/filter.go +++ b/processor/metricstarttimeprocessor/internal/filter/filter.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package filter import ( diff --git a/processor/metricstarttimeprocessor/internal/filter/filter_test.go b/processor/metricstarttimeprocessor/internal/filter/filter_test.go index 9b4c387c3f029..1d15150bc4fd9 100644 --- a/processor/metricstarttimeprocessor/internal/filter/filter_test.go +++ b/processor/metricstarttimeprocessor/internal/filter/filter_test.go @@ -1,11 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package filter import ( "testing" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" ) func TestNewFilter(t *testing.T) { diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go index 24380c727f460..53c606fde6388 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package starttimeattribute import ( @@ -5,14 +8,15 @@ import ( "fmt" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" - "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" ) const ( @@ -65,7 +69,7 @@ func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) ( rm := resourceMetrics.At(i) resource := rm.Resource() // Try to extract pod identifier from resource attributes - podID := a.extractPodIdentifier(resource.Attributes()) + podID := extractPodIdentifier(resource.Attributes()) if podID == nil { continue } @@ -82,7 +86,7 @@ func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) ( metricName := metric.Name() // Only process cumulative metrics - if !a.isCumulativeMetric(metric) { + if !isCumulativeMetric(metric) { a.set.Logger.Debug("metric is not cumulative, skipping", zap.String("metricName", metricName)) continue @@ -104,7 +108,7 @@ func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) ( ) continue } - a.setStartTimeForMetric(tsm, metric, startTime) + setStartTimeForMetric(tsm, metric, startTime) } } tsm.Unlock() @@ -113,7 +117,7 @@ func (a *Adjuster) AdjustMetrics(ctx context.Context, metrics pmetric.Metrics) ( return metrics, nil } -func (a *Adjuster) extractPodIdentifier(attrs pcommon.Map) *podIdentifier { +func extractPodIdentifier(attrs pcommon.Map) *podIdentifier { podNameVal, nameOk := attrs.Get("k8s.pod.name") namespaceVal, nsOk := attrs.Get("k8s.namespace.name") if nameOk && nsOk { @@ -138,7 +142,7 @@ func (a *Adjuster) extractPodIdentifier(attrs pcommon.Map) *podIdentifier { return nil } -func (a *Adjuster) isCumulativeMetric(metric pmetric.Metric) bool { +func isCumulativeMetric(metric pmetric.Metric) bool { switch metric.Type() { case pmetric.MetricTypeSummary: return true @@ -153,7 +157,7 @@ func (a *Adjuster) isCumulativeMetric(metric pmetric.Metric) bool { } } -func (a *Adjuster) setStartTimeForMetric(tsm *datapointstorage.TimeseriesMap, metric pmetric.Metric, startTime time.Time) { +func setStartTimeForMetric(tsm *datapointstorage.TimeseriesMap, metric pmetric.Metric, startTime time.Time) { startTimeNanos := pcommon.NewTimestampFromTime(startTime) switch metric.Type() { case pmetric.MetricTypeSummary: diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go index 34496225d0110..bbaf788e08271 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/adjuster_test.go @@ -1,3 +1,5 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 package starttimeattribute import ( @@ -5,24 +7,24 @@ import ( "testing" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/testhelper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/filter" ) type mockPodClient struct { startTimes map[string]time.Time } -func (m *mockPodClient) GetPodStartTime(ctx context.Context, podID podIdentifier) time.Time { +func (m *mockPodClient) GetPodStartTime(_ context.Context, podID podIdentifier) time.Time { key := podID.Value if t, ok := m.startTimes[key]; ok { return t @@ -34,11 +36,6 @@ const testMetricName = "test_metric" func TestAdjustMetrics(t *testing.T) { testStartTime := time.Now().Add(-1 * time.Hour) - testStartTimestamp := pcommon.NewTimestampFromTime(testStartTime) - currentTime := time.Now() - currentTimestamp := pcommon.NewTimestampFromTime(currentTime) - nonZeroStartTime := time.Now().Add(-30 * time.Minute) - nonZeroStartTimestamp := pcommon.NewTimestampFromTime(nonZeroStartTime) mockClient := &mockPodClient{ startTimes: map[string]time.Time{ @@ -48,440 +45,101 @@ func TestAdjustMetrics(t *testing.T) { }, } - // Test case 1: cumulative metric with pod IP - - script1 := []*testhelper.MetricsAdjusterTest{ + tests := []struct { + name string + attrs map[string]string + expectAdjusted bool + isCumulative bool + startTimeUnixSec int64 + filter filterset.FilterSet + }{ { - Description: "Cumulative metric with pod IP - start time should be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(testStartTimestamp) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), + name: "cumulative metric with pod IP", + attrs: map[string]string{ + "k8s.pod.ip": "10.0.0.1", + }, + expectAdjusted: true, + isCumulative: true, }, - } - - adjuster1, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{}, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster1, script1) - - // Test case 2: cumulative metric with pod name and namespace - script2 := []*testhelper.MetricsAdjusterTest{ { - Description: "Cumulative metric with pod name and namespace - start time should be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.name", "my-pod") - rm.Resource().Attributes().PutStr("k8s.namespace.name", "default") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.name", "my-pod") - rm.Resource().Attributes().PutStr("k8s.namespace.name", "default") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(testStartTimestamp) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), + name: "cumulative metric with pod name", + attrs: map[string]string{ + "k8s.pod.name": "my-pod", + "k8s.namespace.name": "default", + }, + expectAdjusted: true, + isCumulative: true, }, - } - - adjuster2, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{}, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster2, script2) - - // Test case 3: cumulative metric with pod UID - script3 := []*testhelper.MetricsAdjusterTest{ { - Description: "Cumulative metric with pod UID - start time should be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.uid", "uid-12345") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.uid", "uid-12345") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(testStartTimestamp) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), + name: "cumulative metric with pod UID", + attrs: map[string]string{ + "k8s.pod.uid": "uid-12345", + }, + expectAdjusted: true, + isCumulative: true, }, - } - - adjuster3, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{}, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster3, script3) - - // Test case 4: delta metric should not be adjusted - script4 := []*testhelper.MetricsAdjusterTest{ { - Description: "Delta metric with pod IP - start time should NOT be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityDelta) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityDelta) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), + name: "delta metric should not be adjusted", + attrs: map[string]string{"k8s.pod.ip": "10.0.0.1"}, + expectAdjusted: false, + isCumulative: false, }, - } - - adjuster4, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{}, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster4, script4) - - // Test case 5: metric without pod identifier - script5 := []*testhelper.MetricsAdjusterTest{ { - Description: "Metric without pod identifier - start time should NOT be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("other", "value") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("other", "value") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), + name: "metric without pod identifier", + attrs: map[string]string{"other": "value"}, + expectAdjusted: false, + isCumulative: true, }, - } - - adjuster5, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{}, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster5, script5) - - // Test case 6: metric excluded by NoOp filter - script6 := []*testhelper.MetricsAdjusterTest{ - { - Description: "Metric excluded by NoOp filter - start time should NOT be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - }, - } - - adjuster6, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{NoMatch: true}, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster6, script6) - - // Test case 7: metric included by filter - script7 := []*testhelper.MetricsAdjusterTest{ { - Description: "Metric included by filter - start time should be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(testStartTimestamp) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - }, - } - - filterSet7, err := filter.NewFilter(filter.FilterConfig{ - Metrics: []string{testMetricName}, - Config: filterset.Config{ - MatchType: filterset.Strict, + name: "metric excluded by filter", + attrs: map[string]string{ + "k8s.pod.ip": "10.0.0.1", + }, + expectAdjusted: false, + isCumulative: true, + filter: filter.NoOpFilter{NoMatch: true}, }, - }, filter.FilterConfig{}) - require.NoError(t, err) - - adjuster7, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filterSet7, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster7, script7) - - // Test case 8: metric excluded by filter - script8 := []*testhelper.MetricsAdjusterTest{ { - Description: "Metric excluded by filter - start time should NOT be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(0) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - }, - } - - filterSet8, err := filter.NewFilter(filter.FilterConfig{}, - filter.FilterConfig{ - Metrics: []string{testMetricName}, - Config: filterset.Config{ - MatchType: filterset.Strict, + name: "metric excluded because it has a non-zero start time", + attrs: map[string]string{ + "k8s.pod.ip": "10.0.0.1", }, - }) - require.NoError(t, err) - - adjuster8, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filterSet8, - }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster8, script8) - - // Test case 9: metric with non-zero start time should not be adjusted - script9 := []*testhelper.MetricsAdjusterTest{ - { - Description: "Metric with non-zero start time - start time should NOT be adjusted", - Metrics: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(nonZeroStartTimestamp) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), - Adjusted: func() pmetric.Metrics { - m := pmetric.NewMetrics() - rm := m.ResourceMetrics().AppendEmpty() - rm.Resource().Attributes().PutStr("k8s.pod.ip", "10.0.0.1") - sm := rm.ScopeMetrics().AppendEmpty() - metric := sm.Metrics().AppendEmpty() - metric.SetName(testMetricName) - sum := metric.SetEmptySum() - sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) - dp := sum.DataPoints().AppendEmpty() - dp.SetStartTimestamp(nonZeroStartTimestamp) - dp.SetTimestamp(currentTimestamp) - dp.SetDoubleValue(100.0) - return m - }(), + expectAdjusted: false, + isCumulative: true, + startTimeUnixSec: time.Now().Unix(), }, } - adjuster9, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, - func(ctx context.Context, apiConfig k8sconfig.APIConfig, filter informerFilter, useContainerReadiness bool) (podClient, error) { - return mockClient, nil - }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ - Filter: filter.NoOpFilter{}, - SkipIfCTExists: true, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + metrics := createTestMetrics(testMetricName, tt.attrs, tt.isCumulative, tt.startTimeUnixSec) + originalStartTime := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).StartTimestamp() + metricNameFilter := tt.filter + if metricNameFilter == nil { + metricNameFilter = filter.NoOpFilter{} + } + adjuster, err := NewAdjusterWithFactory(component.TelemetrySettings{Logger: componenttest.NewNopTelemetrySettings().Logger}, + func(context.Context, k8sconfig.APIConfig, informerFilter, bool) (podClient, error) { + return mockClient, nil + }, AttributesFilterConfig{}, false, common.AdjustmentOptions{ + Filter: metricNameFilter, + SkipIfCTExists: !tt.expectAdjusted, + }) + require.NoError(t, err) + adjustedMetrics, err := adjuster.AdjustMetrics(t.Context(), metrics) + require.NoError(t, err) + + adjustedStartTime := adjustedMetrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).StartTimestamp() + + if tt.expectAdjusted { + expectedTime := pcommon.NewTimestampFromTime(testStartTime) + assert.Equal(t, expectedTime, adjustedStartTime) + assert.NotEqual(t, originalStartTime, adjustedStartTime) + } else { + assert.Equal(t, originalStartTime, adjustedStartTime) + } }) - require.NoError(t, err) - testhelper.RunScript(t, adjuster9, script9) + } } func createTestMetrics(name string, attrs map[string]string, cumulative bool, startTime int64) pmetric.Metrics { @@ -513,8 +171,6 @@ func createTestMetrics(name string, attrs map[string]string, cumulative bool, st } func TestExtractPodIdentifier(t *testing.T) { - adjuster := &Adjuster{} - tests := []struct { name string attrs map[string]string @@ -552,7 +208,7 @@ func TestExtractPodIdentifier(t *testing.T) { attrs.PutStr(k, v) } - result := adjuster.extractPodIdentifier(attrs) + result := extractPodIdentifier(attrs) if tt.expected == nil { assert.Nil(t, result) @@ -566,8 +222,6 @@ func TestExtractPodIdentifier(t *testing.T) { } func TestIsCumulativeMetric(t *testing.T) { - adjuster := &Adjuster{} - tests := []struct { name string metricType pmetric.MetricType @@ -615,7 +269,7 @@ func TestIsCumulativeMetric(t *testing.T) { metric.SetEmptyGauge() } - result := adjuster.isCumulativeMetric(metric) + result := isCumulativeMetric(metric) assert.Equal(t, tt.isCumulative, result) }) } diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go index df4f15a59d135..1deb5e1f6af22 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/config.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package starttimeattribute // AttributesFilterConfig holds the user configuration for filtering the k8s informer that is used by the Adjuster diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go index 8f895a3eb9def..8d3ad63491ce0 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/k8s.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package starttimeattribute import ( diff --git a/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go b/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go index e94e5ae64220b..346a164289641 100644 --- a/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go +++ b/processor/metricstarttimeprocessor/internal/starttimeattribute/pod_client.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package starttimeattribute import ( @@ -6,7 +9,6 @@ import ( "sync" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -14,6 +16,8 @@ import ( "k8s.io/client-go/informers" k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" ) type k8sPodClient struct { @@ -36,7 +40,7 @@ func newK8sPodClient(_ context.Context, apiConfig k8sconfig.APIConfig, filter in labelSelector := labels.Everything() if len(filter.labelFilters) > 0 { for _, lf := range filter.labelFilters { - requirement, err := labels.NewRequirement(lf.Key, lf.Op, []string{lf.Value}) + requirement, err := labels.NewRequirement(lf.Key, lf.Op, []string{lf.Value}) //nolint: govet if err != nil { return nil, fmt.Errorf("failed to create label requirement: %w", err) } @@ -73,7 +77,7 @@ func newK8sPodClient(_ context.Context, apiConfig k8sconfig.APIConfig, filter in } _, err = podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj interface{}) { //nolint: revive pod, ok := obj.(*corev1.Pod) if !ok { return @@ -81,9 +85,8 @@ func newK8sPodClient(_ context.Context, apiConfig k8sconfig.APIConfig, filter in client.mu.Lock() defer client.mu.Unlock() client.addPod(pod) - }, - UpdateFunc: func(oldObj, newObj interface{}) { + UpdateFunc: func(oldObj, newObj interface{}) { //nolint: revive oldPod, ok := oldObj.(*corev1.Pod) if !ok { return @@ -98,7 +101,7 @@ func newK8sPodClient(_ context.Context, apiConfig k8sconfig.APIConfig, filter in client.deletePod(oldPod) client.addPod(newPod) }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj interface{}) { //nolint: revive pod, ok := obj.(*corev1.Pod) if !ok { return @@ -122,15 +125,14 @@ func (c *k8sPodClient) addPod(pod *corev1.Pod) { podStatus := pod.Status var startTime time.Time if c.useContainerReadiness { - ready, readyTime := c.containerReadinessTime(pod) + ready, readyTime := containerReadinessTime(pod) if ready { startTime = readyTime } - } else { - if podStatus.StartTime != nil { - startTime = podStatus.StartTime.Time - } + } else if podStatus.StartTime != nil { + startTime = podStatus.StartTime.Time } + if startTime.IsZero() { return } @@ -141,24 +143,22 @@ func (c *k8sPodClient) addPod(pod *corev1.Pod) { c.startTimeCache[fmt.Sprintf("uid:%s", pod.UID)] = startTime } -func (c *k8sPodClient) containerReadinessTime(pod *corev1.Pod) (bool, time.Time) { +func containerReadinessTime(pod *corev1.Pod) (bool, time.Time) { var containerReadyTime time.Time var podReady, containersReady bool for _, condition := range pod.Status.Conditions { if condition.Type == corev1.PodReady { - if condition.Status == corev1.ConditionTrue { - podReady = true - } else { + if condition.Status != corev1.ConditionTrue { return false, containerReadyTime } + podReady = true } if condition.Type == corev1.ContainersReady { - if condition.Status == corev1.ConditionTrue { - containersReady = true - containerReadyTime = condition.LastTransitionTime.Time - } else { + if condition.Status != corev1.ConditionTrue { return false, containerReadyTime } + containersReady = true + containerReadyTime = condition.LastTransitionTime.Time } } return podReady && containersReady, containerReadyTime diff --git a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go index d8d87a35cb9c4..db1d615500f72 100644 --- a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster.go @@ -9,13 +9,13 @@ import ( "regexp" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" ) diff --git a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go index 1241950243ade..6e22172ea1919 100644 --- a/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/starttimemetric/adjuster_test.go @@ -8,13 +8,13 @@ import ( "testing" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/testhelper" ) diff --git a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go index 3cd59acb55f72..c970360f3a0f6 100644 --- a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster.go @@ -7,13 +7,13 @@ import ( "context" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" ) diff --git a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go index ba4c1b46cd6f5..e3e2b7bd96bbb 100644 --- a/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/subtractinitial/adjuster_test.go @@ -7,12 +7,12 @@ import ( "testing" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/testhelper" ) diff --git a/processor/metricstarttimeprocessor/internal/truereset/adjuster.go b/processor/metricstarttimeprocessor/internal/truereset/adjuster.go index 1677d4baa2374..3660dd948f749 100644 --- a/processor/metricstarttimeprocessor/internal/truereset/adjuster.go +++ b/processor/metricstarttimeprocessor/internal/truereset/adjuster.go @@ -7,13 +7,13 @@ import ( "context" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/datapointstorage" ) diff --git a/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go b/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go index 6741b62352b07..d47fbd6f8fad5 100644 --- a/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go +++ b/processor/metricstarttimeprocessor/internal/truereset/adjuster_test.go @@ -7,12 +7,12 @@ import ( "testing" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstarttimeprocessor/internal/testhelper" ) From 239ee74ed2510d88d97dc7e5335d88c70661a67a Mon Sep 17 00:00:00 2001 From: Erik Wu Date: Wed, 22 Oct 2025 13:45:20 -0500 Subject: [PATCH 4/5] changelog --- .chloggen/erw_metricstarttime_k8s_attrs.yaml | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .chloggen/erw_metricstarttime_k8s_attrs.yaml diff --git a/.chloggen/erw_metricstarttime_k8s_attrs.yaml b/.chloggen/erw_metricstarttime_k8s_attrs.yaml new file mode 100644 index 0000000000000..500d1e20c356b --- /dev/null +++ b/.chloggen/erw_metricstarttime_k8s_attrs.yaml @@ -0,0 +1,30 @@ +# Use this changelog template to create an entry for release notes. + +# 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. receiver/filelog) +component: processor/metricstarttime + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: add a new `starttimeattribute` strategy to the metricstarttime processor + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [43694] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + add new `starttimeattribute` strategy to use pod start time (or pod containersready time) as ST \ + allow all strategies to only apply to filtered metrics \ + allow all strategies to skip metrics with CTs already set + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] From 3827988eb78445f3fbd3f1ebea38f785cedf814b Mon Sep 17 00:00:00 2001 From: Erik Wu Date: Wed, 22 Oct 2025 15:32:45 -0500 Subject: [PATCH 5/5] update changelog issue --- .chloggen/erw_metricstarttime_k8s_attrs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/erw_metricstarttime_k8s_attrs.yaml b/.chloggen/erw_metricstarttime_k8s_attrs.yaml index 500d1e20c356b..9ea87f3176667 100644 --- a/.chloggen/erw_metricstarttime_k8s_attrs.yaml +++ b/.chloggen/erw_metricstarttime_k8s_attrs.yaml @@ -10,7 +10,7 @@ component: processor/metricstarttime note: add a new `starttimeattribute` strategy to the metricstarttime processor # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. -issues: [43694] +issues: [43739] # (Optional) One or more lines of additional information to render under the primary note. # These lines will be padded with 2 spaces and then inserted directly into the document.