From 0350a6e50b6cff7a4b3cb082510734b75c587b04 Mon Sep 17 00:00:00 2001 From: Pramod Bindal Date: Thu, 19 Sep 2024 11:48:41 +0530 Subject: [PATCH] Add Metrics for Running PipelinesRuns at Pipeline and Namespace level We have added the PipelineRun metric at pipeline and namespace level. Fix Lint Error Fix Lint Error Adding Tests for better coverage of different running-pipelinerun use cases. Fix Lint --- config/config-observability.yaml | 1 + docs/metrics.md | 31 ++- pkg/apis/config/metrics.go | 11 + pkg/apis/config/metrics_test.go | 5 + .../config-observability-namespacelevel.yaml | 1 + .../config-observability-throttle.yaml | 1 + pkg/pipelinerunmetrics/metrics.go | 64 ++++- pkg/pipelinerunmetrics/metrics_test.go | 254 ++++++++++++++++++ 8 files changed, 350 insertions(+), 18 deletions(-) diff --git a/config/config-observability.yaml b/config/config-observability.yaml index f1f800beb06..facda374d83 100644 --- a/config/config-observability.yaml +++ b/config/config-observability.yaml @@ -59,3 +59,4 @@ data: metrics.pipelinerun.level: "pipeline" metrics.pipelinerun.duration-type: "histogram" metrics.count.enable-reason: "false" + metrics.running-pipelinerun.level: "" diff --git a/docs/metrics.md b/docs/metrics.md index 68441559ead..2e0e826989e 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -41,26 +41,31 @@ A sample config-map has been provided as [config-observability](./../config/conf metrics.taskrun.level: "task" metrics.taskrun.duration-type: "histogram" metrics.pipelinerun.level: "pipeline" + metrics.running-pipelinerun.level: "" metrics.pipelinerun.duration-type: "histogram" metrics.count.enable-reason: "false" ``` Following values are available in the configmap: -| configmap data | value | description | -| -- | ----------- | ----------- | -| metrics.taskrun.level | `taskrun` | Level of metrics is taskrun | -| metrics.taskrun.level | `task` | Level of metrics is task and taskrun label isn't present in the metrics | -| metrics.taskrun.level | `namespace` | Level of metrics is namespace, and task and taskrun label isn't present in the metrics -| metrics.pipelinerun.level | `pipelinerun` | Level of metrics is pipelinerun | -| metrics.pipelinerun.level | `pipeline` | Level of metrics is pipeline and pipelinerun label isn't present in the metrics | -| metrics.pipelinerun.level | `namespace` | Level of metrics is namespace, pipeline and pipelinerun label isn't present in the metrics | -| metrics.taskrun.duration-type | `histogram` | `tekton_pipelines_controller_pipelinerun_taskrun_duration_seconds` and `tekton_pipelines_controller_taskrun_duration_seconds` is of type histogram | +| configmap data | value | description | +| -- | ----------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| metrics.taskrun.level | `taskrun` | Level of metrics is taskrun | +| metrics.taskrun.level | `task` | Level of metrics is task and taskrun label isn't present in the metrics | +| metrics.taskrun.level | `namespace` | Level of metrics is namespace, and task and taskrun label isn't present in the metrics +| metrics.pipelinerun.level | `pipelinerun` | Level of metrics is pipelinerun | +| metrics.pipelinerun.level | `pipeline` | Level of metrics is pipeline and pipelinerun label isn't present in the metrics | +| metrics.pipelinerun.level | `namespace` | Level of metrics is namespace, pipeline and pipelinerun label isn't present in the metrics | +| metrics.running-pipelinerun.level | `pipelinerun` | Level of running-pipelinerun metrics is pipelinerun | +| metrics.running-pipelinerun.level | `pipeline` | Level of running-pipelinerun metrics is pipeline and pipelinerun label isn't present in the metrics | +| metrics.running-pipelinerun.level | `namespace` | Level of running-pipelinerun metrics is namespace, pipeline and pipelinerun label isn't present in the metrics | +| metrics.running-pipelinerun.level | `` | Level of running-pipelinerun metrics is cluster, namespace, pipeline and pipelinerun label isn't present in the metrics. | +| metrics.taskrun.duration-type | `histogram` | `tekton_pipelines_controller_pipelinerun_taskrun_duration_seconds` and `tekton_pipelines_controller_taskrun_duration_seconds` is of type histogram | | metrics.taskrun.duration-type | `lastvalue` | `tekton_pipelines_controller_pipelinerun_taskrun_duration_seconds` and `tekton_pipelines_controller_taskrun_duration_seconds` is of type gauge or lastvalue | -| metrics.pipelinerun.duration-type | `histogram` | `tekton_pipelines_controller_pipelinerun_duration_seconds` is of type histogram | -| metrics.pipelinerun.duration-type | `lastvalue` | `tekton_pipelines_controller_pipelinerun_duration_seconds` is of type gauge or lastvalue | -| metrics.count.enable-reason | `false` | Sets if the `reason` label should be included on count metrics | -| metrics.taskrun.throttle.enable-namespace | `false` | Sets if the `namespace` label should be included on the `tekton_pipelines_controller_running_taskruns_throttled_by_quota` metric | +| metrics.pipelinerun.duration-type | `histogram` | `tekton_pipelines_controller_pipelinerun_duration_seconds` is of type histogram | +| metrics.pipelinerun.duration-type | `lastvalue` | `tekton_pipelines_controller_pipelinerun_duration_seconds` is of type gauge or lastvalue | +| metrics.count.enable-reason | `false` | Sets if the `reason` label should be included on count metrics | +| metrics.taskrun.throttle.enable-namespace | `false` | Sets if the `namespace` label should be included on the `tekton_pipelines_controller_running_taskruns_throttled_by_quota` metric | Histogram value isn't available when pipelinerun or taskrun labels are selected. The Lastvalue or Gauge will be provided. Histogram would serve no purpose because it would generate a single bar. TaskRun and PipelineRun level metrics aren't recommended because they lead to an unbounded cardinality which degrades the observability database. diff --git a/pkg/apis/config/metrics.go b/pkg/apis/config/metrics.go index 0df91805db0..f86d4a136a2 100644 --- a/pkg/apis/config/metrics.go +++ b/pkg/apis/config/metrics.go @@ -29,6 +29,9 @@ const ( // metricsPipelinerunLevel determines to what level to aggregate metrics // for pipelinerun metricsPipelinerunLevelKey = "metrics.pipelinerun.level" + // metricsRunningPipelinerunLevelKey determines to what level to aggregate metrics + // for running pipelineruns + metricsRunningPipelinerunLevelKey = "metrics.running-pipelinerun.level" // metricsDurationTaskrunType determines what type of // metrics to use for aggregating duration for taskrun metricsDurationTaskrunType = "metrics.taskrun.duration-type" @@ -55,6 +58,9 @@ const ( // DefaultPipelinerunLevel determines to what level to aggregate metrics // when it isn't specified in configmap DefaultPipelinerunLevel = PipelinerunLevelAtPipeline + // DefaultRunningPipelinerunLevel determines to what level to aggregate metrics + // when it isn't specified in configmap + DefaultRunningPipelinerunLevel = "" // PipelinerunLevelAtPipelinerun specify that aggregation will be done at // pipelinerun level PipelinerunLevelAtPipelinerun = "pipelinerun" @@ -96,6 +102,7 @@ var DefaultMetrics, _ = newMetricsFromMap(map[string]string{}) type Metrics struct { TaskrunLevel string PipelinerunLevel string + RunningPipelinerunLevel string DurationTaskrunType string DurationPipelinerunType string CountWithReason bool @@ -130,6 +137,7 @@ func newMetricsFromMap(cfgMap map[string]string) (*Metrics, error) { tc := Metrics{ TaskrunLevel: DefaultTaskrunLevel, PipelinerunLevel: DefaultPipelinerunLevel, + RunningPipelinerunLevel: DefaultRunningPipelinerunLevel, DurationTaskrunType: DefaultDurationTaskrunType, DurationPipelinerunType: DefaultDurationPipelinerunType, CountWithReason: false, @@ -143,6 +151,9 @@ func newMetricsFromMap(cfgMap map[string]string) (*Metrics, error) { if pipelinerunLevel, ok := cfgMap[metricsPipelinerunLevelKey]; ok { tc.PipelinerunLevel = pipelinerunLevel } + if runningPipelinerunLevel, ok := cfgMap[metricsRunningPipelinerunLevelKey]; ok { + tc.RunningPipelinerunLevel = runningPipelinerunLevel + } if durationTaskrun, ok := cfgMap[metricsDurationTaskrunType]; ok { tc.DurationTaskrunType = durationTaskrun } diff --git a/pkg/apis/config/metrics_test.go b/pkg/apis/config/metrics_test.go index c273ed6b76e..62872897af1 100644 --- a/pkg/apis/config/metrics_test.go +++ b/pkg/apis/config/metrics_test.go @@ -36,6 +36,7 @@ func TestNewMetricsFromConfigMap(t *testing.T) { expectedConfig: &config.Metrics{ TaskrunLevel: config.TaskrunLevelAtTaskrun, PipelinerunLevel: config.PipelinerunLevelAtPipelinerun, + RunningPipelinerunLevel: config.DefaultRunningPipelinerunLevel, DurationTaskrunType: config.DurationPipelinerunTypeHistogram, DurationPipelinerunType: config.DurationPipelinerunTypeHistogram, CountWithReason: false, @@ -47,6 +48,7 @@ func TestNewMetricsFromConfigMap(t *testing.T) { expectedConfig: &config.Metrics{ TaskrunLevel: config.TaskrunLevelAtNS, PipelinerunLevel: config.PipelinerunLevelAtNS, + RunningPipelinerunLevel: config.PipelinerunLevelAtNS, DurationTaskrunType: config.DurationTaskrunTypeHistogram, DurationPipelinerunType: config.DurationPipelinerunTypeLastValue, CountWithReason: false, @@ -58,6 +60,7 @@ func TestNewMetricsFromConfigMap(t *testing.T) { expectedConfig: &config.Metrics{ TaskrunLevel: config.TaskrunLevelAtNS, PipelinerunLevel: config.PipelinerunLevelAtNS, + RunningPipelinerunLevel: config.DefaultRunningPipelinerunLevel, DurationTaskrunType: config.DurationTaskrunTypeHistogram, DurationPipelinerunType: config.DurationPipelinerunTypeLastValue, CountWithReason: true, @@ -69,6 +72,7 @@ func TestNewMetricsFromConfigMap(t *testing.T) { expectedConfig: &config.Metrics{ TaskrunLevel: config.TaskrunLevelAtNS, PipelinerunLevel: config.PipelinerunLevelAtNS, + RunningPipelinerunLevel: config.PipelinerunLevelAtPipeline, DurationTaskrunType: config.DurationTaskrunTypeHistogram, DurationPipelinerunType: config.DurationPipelinerunTypeLastValue, CountWithReason: true, @@ -88,6 +92,7 @@ func TestNewMetricsFromEmptyConfigMap(t *testing.T) { expectedConfig := &config.Metrics{ TaskrunLevel: config.TaskrunLevelAtTask, PipelinerunLevel: config.PipelinerunLevelAtPipeline, + RunningPipelinerunLevel: config.DefaultRunningPipelinerunLevel, DurationTaskrunType: config.DurationPipelinerunTypeHistogram, DurationPipelinerunType: config.DurationPipelinerunTypeHistogram, CountWithReason: false, diff --git a/pkg/apis/config/testdata/config-observability-namespacelevel.yaml b/pkg/apis/config/testdata/config-observability-namespacelevel.yaml index 5029ee0099f..65a72ede515 100644 --- a/pkg/apis/config/testdata/config-observability-namespacelevel.yaml +++ b/pkg/apis/config/testdata/config-observability-namespacelevel.yaml @@ -27,4 +27,5 @@ data: metrics.taskrun.level: "namespace" metrics.taskrun.duration-type: "histogram" metrics.pipelinerun.level: "namespace" + metrics.running-pipelinerun.level: "namespace" metrics.pipelinerun.duration-type: "lastvalue" diff --git a/pkg/apis/config/testdata/config-observability-throttle.yaml b/pkg/apis/config/testdata/config-observability-throttle.yaml index 2b418e176cd..08fe6ac9d5a 100644 --- a/pkg/apis/config/testdata/config-observability-throttle.yaml +++ b/pkg/apis/config/testdata/config-observability-throttle.yaml @@ -27,6 +27,7 @@ data: metrics.taskrun.level: "namespace" metrics.taskrun.duration-type: "histogram" metrics.pipelinerun.level: "namespace" + metrics.running-pipelinerun.level: "pipeline" metrics.pipelinerun.duration-type: "lastvalue" metrics.count.enable-reason: "true" metrics.taskrun.throttle.enable-namespace: "true" diff --git a/pkg/pipelinerunmetrics/metrics.go b/pkg/pipelinerunmetrics/metrics.go index d528681db9f..225c689d746 100644 --- a/pkg/pipelinerunmetrics/metrics.go +++ b/pkg/pipelinerunmetrics/metrics.go @@ -39,6 +39,13 @@ import ( "knative.dev/pkg/metrics" ) +const ( + runningPRLevelPipelinerun = "pipelinerun" + runningPRLevelPipeline = "pipeline" + runningPRLevelNamespace = "namespace" + runningPRLevelCluster = "" +) + var ( pipelinerunTag = tag.MustNewKey("pipelinerun") pipelineTag = tag.MustNewKey("pipeline") @@ -134,6 +141,7 @@ func NewRecorder(ctx context.Context) (*Recorder, error) { } cfg := config.FromContextOrDefaults(ctx) + r.cfg = cfg.Metrics errRegistering = viewRegister(cfg.Metrics) if errRegistering != nil { r.initialized = false @@ -149,7 +157,6 @@ func viewRegister(cfg *config.Metrics) error { defer r.mutex.Unlock() var prunTag []tag.Key - switch cfg.PipelinerunLevel { case config.PipelinerunLevelAtPipelinerun: prunTag = []tag.Key{pipelinerunTag, pipelineTag} @@ -164,6 +171,18 @@ func viewRegister(cfg *config.Metrics) error { return errors.New("invalid config for PipelinerunLevel: " + cfg.PipelinerunLevel) } + var runningPRTag []tag.Key + switch cfg.RunningPipelinerunLevel { + case config.PipelinerunLevelAtPipelinerun: + runningPRTag = []tag.Key{pipelinerunTag, pipelineTag, namespaceTag} + case config.PipelinerunLevelAtPipeline: + runningPRTag = []tag.Key{pipelineTag, namespaceTag} + case config.PipelinerunLevelAtNS: + runningPRTag = []tag.Key{namespaceTag} + default: + runningPRTag = []tag.Key{} + } + distribution := view.Distribution(10, 30, 60, 300, 900, 1800, 3600, 5400, 10800, 21600, 43200, 86400) if cfg.PipelinerunLevel == config.PipelinerunLevelAtPipelinerun { @@ -213,6 +232,7 @@ func viewRegister(cfg *config.Metrics) error { Description: runningPRs.Description(), Measure: runningPRs, Aggregation: view.LastValue(), + TagKeys: runningPRTag, } runningPRsWaitingOnPipelineResolutionCountView = &view.View{ @@ -326,7 +346,7 @@ func (r *Recorder) updateConfig(cfg *config.Metrics) { // DurationAndCount logs the duration of PipelineRun execution and // count for number of PipelineRuns succeed or failed -// returns an error if its failed to log the metrics +// returns an error if it fails to log the metrics func (r *Recorder) DurationAndCount(pr *v1.PipelineRun, beforeCondition *apis.Condition) error { if !r.initialized { return fmt.Errorf("ignoring the metrics recording for %s , failed to initialize the metrics recorder", pr.Name) @@ -379,11 +399,10 @@ func (r *Recorder) DurationAndCount(pr *v1.PipelineRun, beforeCondition *apis.Co } // RunningPipelineRuns logs the number of PipelineRuns running right now -// returns an error if its failed to log the metrics +// returns an error if it fails to log the metrics func (r *Recorder) RunningPipelineRuns(lister listers.PipelineRunLister) error { r.mutex.Lock() defer r.mutex.Unlock() - if !r.initialized { return errors.New("ignoring the metrics recording, failed to initialize the metrics recorder") } @@ -396,9 +415,38 @@ func (r *Recorder) RunningPipelineRuns(lister listers.PipelineRunLister) error { var runningPipelineRuns int var trsWaitResolvingTaskRef int var prsWaitResolvingPipelineRef int + countMap := map[string]int{} for _, pr := range prs { + pipelineName := getPipelineTagName(pr) + pipelineRunKey := "" + mutators := []tag.Mutator{ + tag.Insert(namespaceTag, pr.Namespace), + tag.Insert(pipelineTag, pipelineName), + tag.Insert(pipelinerunTag, pr.Name), + } + if r.cfg != nil { + switch r.cfg.RunningPipelinerunLevel { + case runningPRLevelPipelinerun: + pipelineRunKey = pipelineRunKey + "#" + pr.Name + fallthrough + case runningPRLevelPipeline: + pipelineRunKey = pipelineRunKey + "#" + pipelineName + fallthrough + case runningPRLevelNamespace: + pipelineRunKey = pipelineRunKey + "#" + pr.Namespace + case runningPRLevelCluster: + default: + return fmt.Errorf("RunningPipelineRunLevel value \"%s\" is not valid ", r.cfg.RunningPipelinerunLevel) + } + } + ctx_, err_ := tag.New(context.Background(), mutators...) + if err_ != nil { + return err + } if !pr.IsDone() { + countMap[pipelineRunKey]++ + metrics.Record(ctx_, runningPRs.M(float64(countMap[pipelineRunKey]))) runningPipelineRuns++ succeedCondition := pr.Status.GetCondition(apis.ConditionSucceeded) if succeedCondition != nil && succeedCondition.Status == corev1.ConditionUnknown { @@ -409,6 +457,13 @@ func (r *Recorder) RunningPipelineRuns(lister listers.PipelineRunLister) error { prsWaitResolvingPipelineRef++ } } + } else { + // In case there are no running PipelineRuns for the pipelineRunKey, set the metric value to 0 to ensure + // the metric is set for the key. + if _, exists := countMap[pipelineRunKey]; !exists { + countMap[pipelineRunKey] = 0 + metrics.Record(ctx_, runningPRs.M(0)) + } } } @@ -421,7 +476,6 @@ func (r *Recorder) RunningPipelineRuns(lister listers.PipelineRunLister) error { metrics.Record(ctx, runningPRsWaitingOnTaskResolutionCount.M(float64(trsWaitResolvingTaskRef))) metrics.Record(ctx, runningPRsWaitingOnTaskResolution.M(float64(trsWaitResolvingTaskRef))) metrics.Record(ctx, runningPRsCount.M(float64(runningPipelineRuns))) - metrics.Record(ctx, runningPRs.M(float64(runningPipelineRuns))) return nil } diff --git a/pkg/pipelinerunmetrics/metrics_test.go b/pkg/pipelinerunmetrics/metrics_test.go index 23703e64539..418563e09f4 100644 --- a/pkg/pipelinerunmetrics/metrics_test.go +++ b/pkg/pipelinerunmetrics/metrics_test.go @@ -23,6 +23,9 @@ import ( "testing" "time" + "go.opencensus.io/metric/metricproducer" + "go.opencensus.io/stats/view" + "github.com/tektoncd/pipeline/pkg/apis/pipeline" "github.com/tektoncd/pipeline/pkg/apis/config" @@ -50,6 +53,7 @@ func getConfigContext(countWithReason bool) context.Context { Metrics: &config.Metrics{ TaskrunLevel: config.TaskrunLevelAtTaskrun, PipelinerunLevel: config.PipelinerunLevelAtPipelinerun, + RunningPipelinerunLevel: config.DefaultRunningPipelinerunLevel, DurationTaskrunType: config.DefaultDurationTaskrunType, DurationPipelinerunType: config.DefaultDurationPipelinerunType, CountWithReason: countWithReason, @@ -58,6 +62,21 @@ func getConfigContext(countWithReason bool) context.Context { return config.ToContext(ctx, cfg) } +func getConfigContextRunningPRLevel(runningPipelinerunLevel string) context.Context { + ctx := context.Background() + cfg := &config.Config{ + Metrics: &config.Metrics{ + TaskrunLevel: config.TaskrunLevelAtTaskrun, + PipelinerunLevel: config.PipelinerunLevelAtPipelinerun, + DurationTaskrunType: config.DefaultDurationTaskrunType, + DurationPipelinerunType: config.DefaultDurationPipelinerunType, + CountWithReason: false, + RunningPipelinerunLevel: runningPipelinerunLevel, + }, + } + return config.ToContext(ctx, cfg) +} + func TestUninitializedMetrics(t *testing.T) { metrics := Recorder{} @@ -504,6 +523,204 @@ func TestRecordRunningPipelineRunsCount(t *testing.T) { metricstest.CheckLastValueData(t, "running_pipelineruns", map[string]string{}, 1) } +func TestRecordRunningPipelineRunsCountAtPipelineRunLevel(t *testing.T) { + unregisterMetrics() + + newPipelineRun := func(status corev1.ConditionStatus, pipelineRun, namespace string) *v1.PipelineRun { + return &v1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: pipelineRun, Namespace: namespace}, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: apis.ConditionSucceeded, + Status: status, + }}, + }, + }, + } + } + + ctx, _ := ttesting.SetupFakeContext(t) + informer := fakepipelineruninformer.Get(ctx) + // Add N randomly-named PipelineRuns with differently-succeeded statuses. + for _, pipelineRun := range []*v1.PipelineRun{ + newPipelineRun(corev1.ConditionUnknown, "testpr1", "testns1"), + newPipelineRun(corev1.ConditionUnknown, "testpr1", "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testpr2", "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testpr1", "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testpr2", "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testpr3", "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testpr4", "testns3"), + } { + if err := informer.Informer().GetIndexer().Add(pipelineRun); err != nil { + t.Fatalf("Adding TaskRun to informer: %v", err) + } + } + + ctx = getConfigContextRunningPRLevel("pipelinerun") + recorder, err := NewRecorder(ctx) + if err != nil { + t.Fatalf("NewRecorder: %v", err) + } + + if err := recorder.RunningPipelineRuns(informer.Lister()); err != nil { + t.Errorf("RunningPipelineRuns: %v", err) + } + + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns1", "pipeline": "anonymous", "pipelinerun": "testpr1"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns2", "pipeline": "anonymous", "pipelinerun": "testpr1"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns2", "pipeline": "anonymous", "pipelinerun": "testpr2"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns3", "pipeline": "anonymous", "pipelinerun": "testpr1"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns3", "pipeline": "anonymous", "pipelinerun": "testpr2"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns3", "pipeline": "anonymous", "pipelinerun": "testpr3"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns3", "pipeline": "anonymous", "pipelinerun": "testpr4"}, 1) +} + +func TestRecordRunningPipelineRunsCountAtPipelineLevel(t *testing.T) { + unregisterMetrics() + + newPipelineRun := func(status corev1.ConditionStatus, namespace string) *v1.PipelineRun { + return &v1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pipelinerun-"), Namespace: namespace}, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: apis.ConditionSucceeded, + Status: status, + }}, + }, + }, + } + } + + ctx, _ := ttesting.SetupFakeContext(t) + informer := fakepipelineruninformer.Get(ctx) + // Add N randomly-named PipelineRuns with differently-succeeded statuses. + for _, pipelineRun := range []*v1.PipelineRun{ + newPipelineRun(corev1.ConditionUnknown, "testns1"), + newPipelineRun(corev1.ConditionUnknown, "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + } { + if err := informer.Informer().GetIndexer().Add(pipelineRun); err != nil { + t.Fatalf("Adding TaskRun to informer: %v", err) + } + } + + ctx = getConfigContextRunningPRLevel("pipeline") + recorder, err := NewRecorder(ctx) + if err != nil { + t.Fatalf("NewRecorder: %v", err) + } + + if err := recorder.RunningPipelineRuns(informer.Lister()); err != nil { + t.Errorf("RunningPipelineRuns: %v", err) + } + + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns1", "pipeline": "anonymous"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns2", "pipeline": "anonymous"}, 2) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns3", "pipeline": "anonymous"}, 4) +} + +func TestRecordRunningPipelineRunsCountAtNamespaceLevel(t *testing.T) { + unregisterMetrics() + + newPipelineRun := func(status corev1.ConditionStatus, namespace string) *v1.PipelineRun { + return &v1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pipelinerun-"), Namespace: namespace}, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: apis.ConditionSucceeded, + Status: status, + }}, + }, + }, + } + } + + ctx, _ := ttesting.SetupFakeContext(t) + informer := fakepipelineruninformer.Get(ctx) + // Add N randomly-named PipelineRuns with differently-succeeded statuses. + for _, pipelineRun := range []*v1.PipelineRun{ + newPipelineRun(corev1.ConditionUnknown, "testns1"), + newPipelineRun(corev1.ConditionUnknown, "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + } { + if err := informer.Informer().GetIndexer().Add(pipelineRun); err != nil { + t.Fatalf("Adding TaskRun to informer: %v", err) + } + } + + ctx = getConfigContextRunningPRLevel("namespace") + recorder, err := NewRecorder(ctx) + if err != nil { + t.Fatalf("NewRecorder: %v", err) + } + + if err := recorder.RunningPipelineRuns(informer.Lister()); err != nil { + t.Errorf("RunningPipelineRuns: %v", err) + } + + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns1"}, 1) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns2"}, 2) + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{"namespace": "testns3"}, 4) +} + +func TestRecordRunningPipelineRunsCountAtClusterLevel(t *testing.T) { + unregisterMetrics() + + newPipelineRun := func(status corev1.ConditionStatus, namespace string) *v1.PipelineRun { + return &v1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pipelinerun-"), Namespace: namespace}, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: apis.ConditionSucceeded, + Status: status, + }}, + }, + }, + } + } + + ctx, _ := ttesting.SetupFakeContext(t) + informer := fakepipelineruninformer.Get(ctx) + // Add N randomly-named PipelineRuns with differently-succeeded statuses. + for _, pipelineRun := range []*v1.PipelineRun{ + newPipelineRun(corev1.ConditionUnknown, "testns1"), + newPipelineRun(corev1.ConditionUnknown, "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testns2"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + newPipelineRun(corev1.ConditionUnknown, "testns3"), + } { + if err := informer.Informer().GetIndexer().Add(pipelineRun); err != nil { + t.Fatalf("Adding TaskRun to informer: %v", err) + } + } + + ctx = getConfigContextRunningPRLevel("") + recorder, err := NewRecorder(ctx) + if err != nil { + t.Fatalf("NewRecorder: %v", err) + } + + if err := recorder.RunningPipelineRuns(informer.Lister()); err != nil { + t.Errorf("RunningPipelineRuns: %v", err) + } + + checkLastValueDataForTags(t, "running_pipelineruns", map[string]string{}, 7) +} + func TestRecordRunningPipelineRunsResolutionWaitCounts(t *testing.T) { multiplier := 3 for _, tc := range []struct { @@ -596,3 +813,40 @@ func unregisterMetrics() { r = nil errRegistering = nil } + +// We have to write this function as knative package does not provide the feature to validate multiple records for same metric. +func checkLastValueDataForTags(t *testing.T, name string, wantTags map[string]string, expected float64) { + t.Helper() + for _, producer := range metricproducer.GlobalManager().GetAll() { + meter := producer.(view.Meter) + data, err := meter.RetrieveData(name) + if err != nil || len(data) == 0 { + continue + } + val := getLastValueData(data, wantTags) + if expected != val.Value { + t.Error("Value did not match for ", name, wantTags, ", expected", expected, "got", val.Value) + } + } +} + +// Returns the LastValueData from the matching row. If no row is matched then returns nil +func getLastValueData(rows []*view.Row, wantTags map[string]string) *view.LastValueData { + for _, row := range rows { + if len(wantTags) != len(row.Tags) { + continue + } + matched := true + for _, got := range row.Tags { + n := got.Key.Name() + if wantTags[n] != got.Value { + matched = false + break + } + } + if matched { + return row.Data.(*view.LastValueData) + } + } + return nil +}