github.com/argoproj/argo-cd/v3@v3.2.1/controller/metrics/metrics_test.go (about) 1 package metrics 2 3 import ( 4 "context" 5 "log" 6 "net/http" 7 "net/http/httptest" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/mock" 13 14 "github.com/argoproj/argo-cd/v3/util/db/mocks" 15 16 gitopsCache "github.com/argoproj/gitops-engine/pkg/cache" 17 "github.com/argoproj/gitops-engine/pkg/sync/common" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/runtime" 22 "k8s.io/client-go/rest" 23 "k8s.io/client-go/tools/cache" 24 "k8s.io/client-go/util/workqueue" 25 "sigs.k8s.io/yaml" 26 27 argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 28 appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake" 29 appinformer "github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions" 30 applister "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1" 31 32 "sigs.k8s.io/controller-runtime/pkg/controller" 33 "sigs.k8s.io/controller-runtime/pkg/manager" 34 ) 35 36 const fakeApp = ` 37 apiVersion: argoproj.io/v1alpha1 38 kind: Application 39 metadata: 40 name: my-app 41 namespace: argocd 42 labels: 43 team-name: my-team 44 team-bu: bu-id 45 argoproj.io/cluster: test-cluster 46 spec: 47 destination: 48 namespace: dummy-namespace 49 name: cluster1 50 project: important-project 51 source: 52 path: some/path 53 repoURL: https://github.com/argoproj/argocd-example-apps.git 54 status: 55 sync: 56 status: Synced 57 health: 58 status: Healthy 59 ` 60 61 const fakeApp2 = ` 62 apiVersion: argoproj.io/v1alpha1 63 kind: Application 64 metadata: 65 name: my-app-2 66 namespace: argocd 67 labels: 68 team-name: my-team 69 team-bu: bu-id 70 argoproj.io/cluster: test-cluster 71 spec: 72 destination: 73 namespace: dummy-namespace 74 name: cluster1 75 project: important-project 76 source: 77 path: some/path 78 repoURL: https://github.com/argoproj/argocd-example-apps.git 79 syncPolicy: 80 automated: 81 selfHeal: false 82 prune: true 83 status: 84 sync: 85 status: Synced 86 health: 87 status: Healthy 88 operation: 89 sync: 90 dryRun: true 91 revision: 041eab7439ece92c99b043f0e171788185b8fc1d 92 syncStrategy: 93 hook: {} 94 ` 95 96 const fakeApp3 = ` 97 apiVersion: argoproj.io/v1alpha1 98 kind: Application 99 metadata: 100 name: my-app-3 101 namespace: argocd 102 deletionTimestamp: "2020-03-16T09:17:45Z" 103 labels: 104 team-name: my-team 105 team-bu: bu-id 106 argoproj.io/cluster: test-cluster 107 spec: 108 destination: 109 namespace: dummy-namespace 110 name: cluster1 111 project: important-project 112 source: 113 path: some/path 114 repoURL: https://github.com/argoproj/argocd-example-apps.git 115 syncPolicy: 116 automated: 117 selfHeal: true 118 prune: false 119 status: 120 sync: 121 status: OutOfSync 122 health: 123 status: Degraded 124 ` 125 126 const fakeApp4 = ` 127 apiVersion: argoproj.io/v1alpha1 128 kind: Application 129 metadata: 130 name: my-app-4 131 namespace: argocd 132 labels: 133 team-name: my-team 134 team-bu: bu-id 135 argoproj.io/cluster: test-cluster 136 spec: 137 destination: 138 namespace: dummy-namespace 139 name: cluster1 140 project: important-project 141 source: 142 path: some/path 143 repoURL: https://github.com/argoproj/argocd-example-apps.git 144 status: 145 sync: 146 status: OutOfSync 147 health: 148 status: Degraded 149 conditions: 150 - lastTransitionTime: "2024-08-07T12:25:40Z" 151 message: Application has 1 orphaned resources 152 type: OrphanedResourceWarning 153 - lastTransitionTime: "2024-08-07T12:25:40Z" 154 message: Resource Pod standalone-pod is excluded in the settings 155 type: ExcludedResourceWarning 156 - lastTransitionTime: "2024-08-07T12:25:40Z" 157 message: Resource Endpoint raw-endpoint is excluded in the settings 158 type: ExcludedResourceWarning 159 ` 160 161 const fakeDefaultApp = ` 162 apiVersion: argoproj.io/v1alpha1 163 kind: Application 164 metadata: 165 name: my-app 166 namespace: argocd 167 spec: 168 destination: 169 namespace: dummy-namespace 170 name: cluster1 171 source: 172 path: some/path 173 repoURL: https://github.com/argoproj/argocd-example-apps.git 174 status: 175 sync: 176 status: Synced 177 health: 178 status: Healthy 179 ` 180 181 const fakeAppOperationRunning = ` 182 apiVersion: argoproj.io/v1alpha1 183 kind: Application 184 metadata: 185 name: my-app 186 namespace: argocd 187 labels: 188 team-name: my-team 189 team-bu: bu-id 190 argoproj.io/cluster: test-cluster 191 spec: 192 destination: 193 namespace: dummy-namespace 194 name: cluster1 195 project: important-project 196 source: 197 path: some/path 198 repoURL: https://github.com/argoproj/argocd-example-apps.git 199 status: 200 sync: 201 status: OutOfSync 202 health: 203 status: Progressing 204 operationState: 205 phase: Running 206 startedAt: "2025-01-29T08:42:34Z" 207 ` 208 209 const fakeAppOperationFinished = ` 210 apiVersion: argoproj.io/v1alpha1 211 kind: Application 212 metadata: 213 name: my-app 214 namespace: argocd 215 labels: 216 team-name: my-team 217 team-bu: bu-id 218 argoproj.io/cluster: test-cluster 219 spec: 220 destination: 221 namespace: dummy-namespace 222 name: cluster1 223 project: important-project 224 source: 225 path: some/path 226 repoURL: https://github.com/argoproj/argocd-example-apps.git 227 status: 228 sync: 229 status: Synced 230 health: 231 status: Healthy 232 operationState: 233 phase: Succeeded 234 startedAt: "2025-01-29T08:42:34Z" 235 finishedAt: "2025-01-29T08:42:35Z" 236 ` 237 238 var noOpHealthCheck = func(_ *http.Request) error { 239 return nil 240 } 241 242 var appFilter = func(_ any) bool { 243 return true 244 } 245 246 func init() { 247 // Create a fake manager 248 mgr, err := manager.New(&rest.Config{}, manager.Options{}) 249 if err != nil { 250 panic(err) 251 } 252 // Create a fake controller so we initialize the internal controller metrics. 253 // https://github.com/kubernetes-sigs/controller-runtime/blob/4000e996a202917ad7d40f02ed8a2079a9ce25e9/pkg/internal/controller/metrics/metrics.go 254 _, _ = controller.New("test-controller", mgr, controller.Options{}) 255 } 256 257 func newFakeApp(fakeAppYAML string) *argoappv1.Application { 258 var app argoappv1.Application 259 err := yaml.Unmarshal([]byte(fakeAppYAML), &app) 260 if err != nil { 261 panic(err) 262 } 263 return &app 264 } 265 266 func newFakeLister(fakeAppYAMLs ...string) (context.CancelFunc, applister.ApplicationLister) { 267 ctx, cancel := context.WithCancel(context.Background()) 268 defer cancel() 269 var fakeApps []runtime.Object 270 for _, appYAML := range fakeAppYAMLs { 271 a := newFakeApp(appYAML) 272 fakeApps = append(fakeApps, a) 273 } 274 appClientset := appclientset.NewSimpleClientset(fakeApps...) 275 factory := appinformer.NewSharedInformerFactoryWithOptions(appClientset, 0, appinformer.WithNamespace("argocd"), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {})) 276 appInformer := factory.Argoproj().V1alpha1().Applications().Informer() 277 go appInformer.Run(ctx.Done()) 278 if !cache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) { 279 log.Fatal("Timed out waiting for caches to sync") 280 } 281 return cancel, factory.Argoproj().V1alpha1().Applications().Lister() 282 } 283 284 func testApp(t *testing.T, fakeAppYAMLs []string, expectedResponse string) { 285 t.Helper() 286 testMetricServer(t, fakeAppYAMLs, expectedResponse, []string{}, []string{}) 287 } 288 289 type fakeClusterInfo struct { 290 clustersInfo []gitopsCache.ClusterInfo 291 } 292 293 func (f *fakeClusterInfo) GetClustersInfo() []gitopsCache.ClusterInfo { 294 return f.clustersInfo 295 } 296 297 type TestMetricServerConfig struct { 298 FakeAppYAMLs []string 299 ExpectedResponse string 300 AppLabels []string 301 AppConditions []string 302 ClusterLabels []string 303 ClustersInfo []gitopsCache.ClusterInfo 304 ClusterLister ClusterLister 305 } 306 307 func testMetricServer(t *testing.T, fakeAppYAMLs []string, expectedResponse string, appLabels []string, appConditions []string) { 308 t.Helper() 309 cfg := TestMetricServerConfig{ 310 FakeAppYAMLs: fakeAppYAMLs, 311 ExpectedResponse: expectedResponse, 312 AppLabels: appLabels, 313 AppConditions: appConditions, 314 ClusterLabels: []string{}, 315 ClustersInfo: []gitopsCache.ClusterInfo{}, 316 } 317 runTest(t, cfg) 318 } 319 320 func runTest(t *testing.T, cfg TestMetricServerConfig) { 321 t.Helper() 322 cancel, appLister := newFakeLister(cfg.FakeAppYAMLs...) 323 defer cancel() 324 mockDB := mocks.NewArgoDB(t) 325 mockDB.On("GetClusterServersByName", mock.Anything, "cluster1").Return([]string{"https://localhost:6443"}, nil) 326 mockDB.On("GetCluster", mock.Anything, "https://localhost:6443").Return(&argoappv1.Cluster{Name: "cluster1", Server: "https://localhost:6443"}, nil) 327 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, cfg.AppLabels, cfg.AppConditions, mockDB) 328 require.NoError(t, err) 329 330 if len(cfg.ClustersInfo) > 0 { 331 ci := &fakeClusterInfo{clustersInfo: cfg.ClustersInfo} 332 collector := NewClusterCollector(t.Context(), ci, cfg.ClusterLister, cfg.ClusterLabels) 333 metricsServ.registry.MustRegister(collector) 334 } 335 336 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 337 require.NoError(t, err) 338 rr := httptest.NewRecorder() 339 metricsServ.Handler.ServeHTTP(rr, req) 340 assert.Equal(t, http.StatusOK, rr.Code) 341 body := rr.Body.String() 342 assertMetricsPrinted(t, cfg.ExpectedResponse, body) 343 } 344 345 type testCombination struct { 346 applications []string 347 responseContains string 348 } 349 350 func TestMetrics(t *testing.T) { 351 combinations := []testCombination{ 352 { 353 applications: []string{fakeApp, fakeApp2, fakeApp3}, 354 responseContains: ` 355 # HELP argocd_app_info Information about application. 356 # TYPE argocd_app_info gauge 357 argocd_app_info{autosync_enabled="true",dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Degraded",name="my-app-3",namespace="argocd",operation="delete",project="important-project",repo="https://github.com/argoproj/argocd-example-apps",sync_status="OutOfSync"} 1 358 argocd_app_info{autosync_enabled="false",dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Healthy",name="my-app",namespace="argocd",operation="",project="important-project",repo="https://github.com/argoproj/argocd-example-apps",sync_status="Synced"} 1 359 argocd_app_info{autosync_enabled="true",dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Healthy",name="my-app-2",namespace="argocd",operation="sync",project="important-project",repo="https://github.com/argoproj/argocd-example-apps",sync_status="Synced"} 1 360 `, 361 }, 362 { 363 applications: []string{fakeDefaultApp}, 364 responseContains: ` 365 # HELP argocd_app_info Information about application. 366 # TYPE argocd_app_info gauge 367 argocd_app_info{autosync_enabled="false",dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Healthy",name="my-app",namespace="argocd",operation="",project="default",repo="https://github.com/argoproj/argocd-example-apps",sync_status="Synced"} 1 368 `, 369 }, 370 } 371 372 for _, combination := range combinations { 373 testApp(t, combination.applications, combination.responseContains) 374 } 375 } 376 377 func TestMetricLabels(t *testing.T) { 378 type testCases struct { 379 testCombination 380 description string 381 metricLabels []string 382 } 383 cases := []testCases{ 384 { 385 description: "will return the labels metrics successfully", 386 metricLabels: []string{"team-name", "team-bu", "argoproj.io/cluster"}, 387 testCombination: testCombination{ 388 applications: []string{fakeApp, fakeApp2, fakeApp3}, 389 responseContains: ` 390 # TYPE argocd_app_labels gauge 391 argocd_app_labels{label_argoproj_io_cluster="test-cluster",label_team_bu="bu-id",label_team_name="my-team",name="my-app",namespace="argocd",project="important-project"} 1 392 argocd_app_labels{label_argoproj_io_cluster="test-cluster",label_team_bu="bu-id",label_team_name="my-team",name="my-app-2",namespace="argocd",project="important-project"} 1 393 argocd_app_labels{label_argoproj_io_cluster="test-cluster",label_team_bu="bu-id",label_team_name="my-team",name="my-app-3",namespace="argocd",project="important-project"} 1 394 `, 395 }, 396 }, 397 { 398 description: "metric will have empty label value if not present in the application", 399 metricLabels: []string{"non-existing"}, 400 testCombination: testCombination{ 401 applications: []string{fakeApp, fakeApp2, fakeApp3}, 402 responseContains: ` 403 # TYPE argocd_app_labels gauge 404 argocd_app_labels{label_non_existing="",name="my-app",namespace="argocd",project="important-project"} 1 405 argocd_app_labels{label_non_existing="",name="my-app-2",namespace="argocd",project="important-project"} 1 406 argocd_app_labels{label_non_existing="",name="my-app-3",namespace="argocd",project="important-project"} 1 407 `, 408 }, 409 }, 410 } 411 412 for _, c := range cases { 413 c := c 414 t.Run(c.description, func(t *testing.T) { 415 testMetricServer(t, c.applications, c.responseContains, c.metricLabels, []string{}) 416 }) 417 } 418 } 419 420 func TestMetricConditions(t *testing.T) { 421 type testCases struct { 422 testCombination 423 description string 424 metricConditions []string 425 } 426 cases := []testCases{ 427 { 428 description: "metric will only output OrphanedResourceWarning", 429 metricConditions: []string{"OrphanedResourceWarning"}, 430 testCombination: testCombination{ 431 applications: []string{fakeApp4}, 432 responseContains: ` 433 # HELP argocd_app_condition Report application conditions. 434 # TYPE argocd_app_condition gauge 435 argocd_app_condition{condition="OrphanedResourceWarning",name="my-app-4",namespace="argocd",project="important-project"} 1 436 `, 437 }, 438 }, 439 { 440 description: "metric will only output ExcludedResourceWarning", 441 metricConditions: []string{"ExcludedResourceWarning"}, 442 testCombination: testCombination{ 443 applications: []string{fakeApp4}, 444 responseContains: ` 445 # HELP argocd_app_condition Report application conditions. 446 # TYPE argocd_app_condition gauge 447 argocd_app_condition{condition="ExcludedResourceWarning",name="my-app-4",namespace="argocd",project="important-project"} 2 448 `, 449 }, 450 }, 451 { 452 description: "metric will only output both OrphanedResourceWarning and ExcludedResourceWarning", 453 metricConditions: []string{"ExcludedResourceWarning", "OrphanedResourceWarning"}, 454 testCombination: testCombination{ 455 applications: []string{fakeApp4}, 456 responseContains: ` 457 # HELP argocd_app_condition Report application conditions. 458 # TYPE argocd_app_condition gauge 459 argocd_app_condition{condition="OrphanedResourceWarning",name="my-app-4",namespace="argocd",project="important-project"} 1 460 argocd_app_condition{condition="ExcludedResourceWarning",name="my-app-4",namespace="argocd",project="important-project"} 2 461 `, 462 }, 463 }, 464 } 465 466 for _, c := range cases { 467 c := c 468 t.Run(c.description, func(t *testing.T) { 469 testMetricServer(t, c.applications, c.responseContains, []string{}, c.metricConditions) 470 }) 471 } 472 } 473 474 func TestMetricsSyncCounter(t *testing.T) { 475 cancel, appLister := newFakeLister() 476 defer cancel() 477 mockDB := mocks.NewArgoDB(t) 478 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 479 require.NoError(t, err) 480 481 appSyncTotal := ` 482 # HELP argocd_app_sync_total Number of application syncs. 483 # TYPE argocd_app_sync_total counter 484 argocd_app_sync_total{dest_server="https://localhost:6443",dry_run="false",name="my-app",namespace="argocd",phase="Error",project="important-project"} 1 485 argocd_app_sync_total{dest_server="https://localhost:6443",dry_run="false",name="my-app",namespace="argocd",phase="Failed",project="important-project"} 1 486 argocd_app_sync_total{dest_server="https://localhost:6443",dry_run="false",name="my-app",namespace="argocd",phase="Succeeded",project="important-project"} 2 487 ` 488 489 fakeApp := newFakeApp(fakeApp) 490 metricsServ.IncSync(fakeApp, "https://localhost:6443", &argoappv1.OperationState{Phase: common.OperationRunning}) 491 metricsServ.IncSync(fakeApp, "https://localhost:6443", &argoappv1.OperationState{Phase: common.OperationFailed}) 492 metricsServ.IncSync(fakeApp, "https://localhost:6443", &argoappv1.OperationState{Phase: common.OperationError}) 493 metricsServ.IncSync(fakeApp, "https://localhost:6443", &argoappv1.OperationState{Phase: common.OperationSucceeded}) 494 metricsServ.IncSync(fakeApp, "https://localhost:6443", &argoappv1.OperationState{Phase: common.OperationSucceeded}) 495 496 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 497 require.NoError(t, err) 498 rr := httptest.NewRecorder() 499 metricsServ.Handler.ServeHTTP(rr, req) 500 assert.Equal(t, http.StatusOK, rr.Code) 501 body := rr.Body.String() 502 log.Println(body) 503 assertMetricsPrinted(t, appSyncTotal, body) 504 } 505 506 // assertMetricsPrinted asserts every line in the expected lines appears in the body 507 func assertMetricsPrinted(t *testing.T, expectedLines, body string) { 508 t.Helper() 509 for _, line := range strings.Split(expectedLines, "\n") { 510 if line == "" { 511 continue 512 } 513 assert.Contains(t, body, line, "expected metrics mismatch for line: %s", line) 514 } 515 } 516 517 // assertMetricsNotPrinted 518 func assertMetricsNotPrinted(t *testing.T, expectedLines, body string) { 519 t.Helper() 520 for _, line := range strings.Split(expectedLines, "\n") { 521 if line == "" { 522 continue 523 } 524 assert.NotContains(t, body, expectedLines) 525 } 526 } 527 528 func TestMetricsSyncDuration(t *testing.T) { 529 cancel, appLister := newFakeLister() 530 defer cancel() 531 mockDB := mocks.NewArgoDB(t) 532 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 533 require.NoError(t, err) 534 535 t.Run("metric is not generated during Operation Running.", func(t *testing.T) { 536 fakeAppOperationRunning := newFakeApp(fakeAppOperationRunning) 537 metricsServ.IncAppSyncDuration(fakeAppOperationRunning, "https://localhost:6443", fakeAppOperationRunning.Status.OperationState) 538 539 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 540 require.NoError(t, err) 541 rr := httptest.NewRecorder() 542 metricsServ.Handler.ServeHTTP(rr, req) 543 assert.Equal(t, http.StatusOK, rr.Code) 544 body := rr.Body.String() 545 log.Println(body) 546 assertMetricsNotPrinted(t, "argocd_app_sync_duration_seconds_total", body) 547 }) 548 549 t.Run("metric is created when Operation Finished.", func(t *testing.T) { 550 fakeAppOperationFinished := newFakeApp(fakeAppOperationFinished) 551 metricsServ.IncAppSyncDuration(fakeAppOperationFinished, "https://localhost:6443", fakeAppOperationFinished.Status.OperationState) 552 553 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 554 require.NoError(t, err) 555 rr := httptest.NewRecorder() 556 metricsServ.Handler.ServeHTTP(rr, req) 557 assert.Equal(t, http.StatusOK, rr.Code) 558 body := rr.Body.String() 559 appSyncDurationTotal := ` 560 # HELP argocd_app_sync_duration_seconds_total Application sync performance in seconds total. 561 # TYPE argocd_app_sync_duration_seconds_total counter 562 argocd_app_sync_duration_seconds_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",project="important-project"} 1 563 ` 564 log.Println(body) 565 assertMetricsPrinted(t, appSyncDurationTotal, body) 566 }) 567 } 568 569 func TestReconcileMetrics(t *testing.T) { 570 cancel, appLister := newFakeLister() 571 defer cancel() 572 mockDB := mocks.NewArgoDB(t) 573 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 574 require.NoError(t, err) 575 576 appReconcileMetrics := ` 577 # HELP argocd_app_reconcile Application reconciliation performance in seconds. 578 # TYPE argocd_app_reconcile histogram 579 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="0.25"} 0 580 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="0.5"} 0 581 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="1"} 0 582 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="2"} 0 583 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="4"} 0 584 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="8"} 1 585 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="16"} 1 586 argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="+Inf"} 1 587 argocd_app_reconcile_sum{dest_server="https://localhost:6443",namespace="argocd"} 5 588 argocd_app_reconcile_count{dest_server="https://localhost:6443",namespace="argocd"} 1 589 ` 590 fakeApp := newFakeApp(fakeApp) 591 metricsServ.IncReconcile(fakeApp, "https://localhost:6443", 5*time.Second) 592 593 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 594 require.NoError(t, err) 595 rr := httptest.NewRecorder() 596 metricsServ.Handler.ServeHTTP(rr, req) 597 assert.Equal(t, http.StatusOK, rr.Code) 598 body := rr.Body.String() 599 log.Println(body) 600 assertMetricsPrinted(t, appReconcileMetrics, body) 601 } 602 603 func TestOrphanedResourcesMetric(t *testing.T) { 604 cancel, appLister := newFakeLister() 605 defer cancel() 606 mockDB := mocks.NewArgoDB(t) 607 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 608 require.NoError(t, err) 609 610 expectedMetrics := ` 611 # HELP argocd_app_orphaned_resources_count Number of orphaned resources per application 612 # TYPE argocd_app_orphaned_resources_count gauge 613 argocd_app_orphaned_resources_count{name="my-app-4",namespace="argocd",project="important-project"} 1 614 ` 615 app := newFakeApp(fakeApp4) 616 numOrphanedResources := 1 617 metricsServ.SetOrphanedResourcesMetric(app, numOrphanedResources) 618 619 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 620 require.NoError(t, err) 621 rr := httptest.NewRecorder() 622 metricsServ.Handler.ServeHTTP(rr, req) 623 assert.Equal(t, http.StatusOK, rr.Code) 624 body := rr.Body.String() 625 log.Println(body) 626 assertMetricsPrinted(t, expectedMetrics, body) 627 } 628 629 func TestMetricsReset(t *testing.T) { 630 cancel, appLister := newFakeLister() 631 defer cancel() 632 mockDB := mocks.NewArgoDB(t) 633 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 634 require.NoError(t, err) 635 636 appSyncTotal := ` 637 # HELP argocd_app_sync_total Number of application syncs. 638 # TYPE argocd_app_sync_total counter 639 argocd_app_sync_total{dest_server="https://localhost:6443",dry_run="false",name="my-app",namespace="argocd",phase="Error",project="important-project"} 1 640 argocd_app_sync_total{dest_server="https://localhost:6443",dry_run="false",name="my-app",namespace="argocd",phase="Failed",project="important-project"} 1 641 argocd_app_sync_total{dest_server="https://localhost:6443",dry_run="false",name="my-app",namespace="argocd",phase="Succeeded",project="important-project"} 2 642 ` 643 644 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 645 require.NoError(t, err) 646 rr := httptest.NewRecorder() 647 metricsServ.Handler.ServeHTTP(rr, req) 648 assert.Equal(t, http.StatusOK, rr.Code) 649 body := rr.Body.String() 650 assertMetricsPrinted(t, appSyncTotal, body) 651 652 err = metricsServ.SetExpiration(time.Second) 653 require.NoError(t, err) 654 time.Sleep(2 * time.Second) 655 req, err = http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 656 require.NoError(t, err) 657 rr = httptest.NewRecorder() 658 metricsServ.Handler.ServeHTTP(rr, req) 659 assert.Equal(t, http.StatusOK, rr.Code) 660 body = rr.Body.String() 661 log.Println(body) 662 assertMetricsNotPrinted(t, appSyncTotal, body) 663 err = metricsServ.SetExpiration(time.Second) 664 require.Error(t, err) 665 } 666 667 func TestWorkqueueMetrics(t *testing.T) { 668 cancel, appLister := newFakeLister() 669 defer cancel() 670 mockDB := mocks.NewArgoDB(t) 671 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 672 require.NoError(t, err) 673 674 expectedMetrics := ` 675 # TYPE workqueue_adds_total counter 676 workqueue_adds_total{controller="test",name="test"} 677 # TYPE workqueue_depth gauge 678 workqueue_depth{controller="test",name="test",priority=""} 679 # TYPE workqueue_longest_running_processor_seconds gauge 680 workqueue_longest_running_processor_seconds{controller="test",name="test"} 681 # TYPE workqueue_queue_duration_seconds histogram 682 # TYPE workqueue_unfinished_work_seconds gauge 683 workqueue_unfinished_work_seconds{controller="test",name="test"} 684 # TYPE workqueue_work_duration_seconds histogram 685 ` 686 workqueue.NewNamed("test") 687 688 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 689 require.NoError(t, err) 690 rr := httptest.NewRecorder() 691 metricsServ.Handler.ServeHTTP(rr, req) 692 assert.Equal(t, http.StatusOK, rr.Code) 693 body := rr.Body.String() 694 log.Println(body) 695 assertMetricsPrinted(t, expectedMetrics, body) 696 } 697 698 func TestGoMetrics(t *testing.T) { 699 cancel, appLister := newFakeLister() 700 defer cancel() 701 mockDB := mocks.NewArgoDB(t) 702 metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{}, []string{}, mockDB) 703 require.NoError(t, err) 704 705 expectedMetrics := ` 706 # TYPE go_gc_duration_seconds summary 707 go_gc_duration_seconds_sum 708 go_gc_duration_seconds_count 709 # TYPE go_goroutines gauge 710 go_goroutines 711 # TYPE go_info gauge 712 go_info 713 # TYPE go_memstats_alloc_bytes gauge 714 go_memstats_alloc_bytes 715 # TYPE go_memstats_sys_bytes gauge 716 go_memstats_sys_bytes 717 # TYPE go_threads gauge 718 go_threads 719 ` 720 721 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 722 require.NoError(t, err) 723 rr := httptest.NewRecorder() 724 metricsServ.Handler.ServeHTTP(rr, req) 725 assert.Equal(t, http.StatusOK, rr.Code) 726 body := rr.Body.String() 727 log.Println(body) 728 assertMetricsPrinted(t, expectedMetrics, body) 729 }