From a3904b5d226b1e2e1f83da33e65c5d1458bb6d77 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 9 Sep 2024 16:11:22 +0200 Subject: [PATCH 01/32] Added resources endpoint --- api/applications/applications_controller.go | 46 ++++++++++ api/applications/applications_handler.go | 19 +++++ api/applications/models/used_resources.go | 51 +++++++++++ go.mod | 9 +- go.sum | 20 +++-- swaggerui/html/swagger.json | 95 +++++++++++++++++++++ 6 files changed, 228 insertions(+), 12 deletions(-) create mode 100644 api/applications/models/used_resources.go diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index 6ee6ada1..5ef009d6 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -133,6 +133,11 @@ func (ac *applicationController) GetRoutes() models.Routes { Method: "POST", HandlerFunc: ac.RegenerateDeployKeyHandler, }, + models.Route{ + Path: appPath + "/resources", + Method: "GET", + HandlerFunc: ac.GetUsedResources, + }, } return routes @@ -992,3 +997,44 @@ func (ac *applicationController) TriggerPipelinePromote(accounts models.Accounts ac.JSONResponse(w, r, &jobSummary) } + +// GetUsedResources Gets used resources for the application +func (ac *applicationController) GetUsedResources(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /applications/{appName}/resources application resources + // --- + // summary: Gets used resources for the application + // parameters: + // - name: appName + // in: path + // description: Name of application + // type: string + // required: true + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: Successful trigger pipeline + // schema: + // "$ref": "#/definitions/UsedResources" + // "404": + // description: "Not found" + appName := mux.Vars(r)["appName"] + + handler := ac.applicationHandlerFactory.Create(accounts) + jobSummary, err := handler.GetUsedResources(r.Context(), appName, r) + + if err != nil { + ac.ErrorResponse(w, r, err) + return + } + + ac.JSONResponse(w, r, &jobSummary) +} diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index a983d179..f0df49ea 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -455,6 +455,25 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam return jobSummary, nil } +// GetUsedResources Returns the used resources for an application +func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName string, r *http.Request) (*applicationModels.UsedResources, error) { + now := time.Now() + return &applicationModels.UsedResources{ + From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), + To: radixutils.FormatTimestamp(now), + CPU: &applicationModels.UsedResource{ + Min: "10m", + Max: "500m", + Average: "223m", + }, + Memory: &applicationModels.UsedResource{ + Min: "10Mi", + Max: "500Mi", + Average: "223Mi", + }, + }, nil +} + func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { radixDeployment, err := kubequery.GetRadixDeploymentByName(ctx, ah.accounts.UserAccount.RadixClient, appName, envName, deploymentName) if err == nil { diff --git a/api/applications/models/used_resources.go b/api/applications/models/used_resources.go new file mode 100644 index 00000000..2b0f0026 --- /dev/null +++ b/api/applications/models/used_resources.go @@ -0,0 +1,51 @@ +package models + +// UsedResources holds information about used resources +// swagger:model UsedResources +type UsedResources struct { + // From timestamp + // + // required: true + // example: 2006-01-02T15:04:05Z + From string `json:"from"` + + // To timestamp + // + // required: true + // example: 2006-01-02T15:04:05Z + To string `json:"to"` + + // CPU used + // + // required: false + // example: 120m + CPU *UsedResource `json:"cpu,omitempty"` + + // CPU used + // + // required: false + // example: 120m + Memory *UsedResource `json:"memory,omitempty"` +} + +// UsedResource holds information about used resource +// swagger:model UsedResource +type UsedResource struct { + // Min resource used + // + // required: false + // example: 120m + Min string `json:"min"` + + // Max resource used + // + // required: false + // example: 120m + Max string `json:"max"` + + // Average resource used + // + // required: false + // example: 120m + Average string `json:"average"` +} diff --git a/go.mod b/go.mod index 07e35409..117ecdfa 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.5 require ( github.com/cert-manager/cert-manager v1.15.0 - github.com/equinor/radix-common v1.9.3 + github.com/equinor/radix-common v1.9.4 github.com/equinor/radix-job-scheduler v1.11.0 github.com/equinor/radix-operator v1.58.1 github.com/evanphx/json-patch/v5 v5.9.0 @@ -18,7 +18,7 @@ require ( github.com/marstr/guid v1.1.0 github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus-operator/prometheus-operator/pkg/client v0.75.2 - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.20.3 github.com/rs/cors v1.11.0 github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.33.0 @@ -72,6 +72,7 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -85,8 +86,8 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.75.2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.53.0 // indirect - github.com/prometheus/procfs v0.15.0 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect diff --git a/go.sum b/go.sum index 0e89b5a7..a31f5781 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/equinor/radix-common v1.9.3 h1:dLKFzYy8/XyEG9Zygi0rMWIYGCddai/ILwUqjBiGYxQ= -github.com/equinor/radix-common v1.9.3/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= +github.com/equinor/radix-common v1.9.4 h1:ErSnB2tqlRwaQuQdaA0qzsReDtHDgubcvqRO098ncEw= +github.com/equinor/radix-common v1.9.4/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.11.0 h1:8wCmXOVl/1cto8q2WJQEE06Cw68/QmfoifYVR49vzkY= github.com/equinor/radix-job-scheduler v1.11.0/go.mod h1:yPXn3kDcMY0Z3kBkosjuefsdY1x2g0NlBeybMmHz5hc= github.com/equinor/radix-operator v1.58.1 h1:Wb/UOP1m4wUdWCL/gynPcnf6axz01Z24fBvK2DRL5m0= @@ -248,6 +248,8 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -258,6 +260,8 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -311,8 +315,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -325,16 +329,16 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= -github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= -github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 52325b6a..cc7804b7 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -4875,6 +4875,47 @@ } } }, + "/applications/{appName}/resources": { + "get": { + "tags": [ + "application" + ], + "summary": "Gets used resources for the application", + "operationId": "resources", + "parameters": [ + { + "type": "string", + "description": "Name of application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Successful trigger pipeline", + "schema": { + "$ref": "#/definitions/UsedResources" + } + }, + "404": { + "description": "Not found" + } + } + } + }, "/applications/{appName}/restart": { "post": { "tags": [ @@ -8000,6 +8041,60 @@ }, "x-go-package": "github.com/equinor/radix-api/api/alerting/models" }, + "UsedResource": { + "description": "UsedResource holds information about used resource", + "type": "object", + "properties": { + "average": { + "description": "Average resource used", + "type": "string", + "x-go-name": "Average", + "example": "120m" + }, + "max": { + "description": "Max resource used", + "type": "string", + "x-go-name": "Max", + "example": "120m" + }, + "min": { + "description": "Min resource used", + "type": "string", + "x-go-name": "Min", + "example": "120m" + } + }, + "x-go-package": "github.com/equinor/radix-api/api/applications/models" + }, + "UsedResources": { + "description": "UsedResources holds information about used resources", + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "cpu": { + "$ref": "#/definitions/UsedResource" + }, + "from": { + "description": "From timestamp", + "type": "string", + "x-go-name": "From", + "example": "2006-01-02T15:04:05Z" + }, + "memory": { + "$ref": "#/definitions/UsedResource" + }, + "to": { + "description": "To timestamp", + "type": "string", + "x-go-name": "To", + "example": "2006-01-02T15:04:05Z" + } + }, + "x-go-package": "github.com/equinor/radix-api/api/applications/models" + }, "X509Certificate": { "description": "X509Certificate holds information about a X509 certificate", "type": "object", From 2e91f355e0b88bcfd9e5b0a0a724858d463203a8 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 11 Sep 2024 08:27:00 +0200 Subject: [PATCH 02/32] Added prometheus client --- api/applications/applications_handler.go | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index f0df49ea..601dc243 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -29,6 +29,8 @@ import ( v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/radixvalidators" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" + prometheusApi "github.com/prometheus/client_golang/api" + prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/rs/zerolog/log" authorizationapi "k8s.io/api/authorization/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -457,6 +459,39 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam // GetUsedResources Returns the used resources for an application func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName string, r *http.Request) (*applicationModels.UsedResources, error) { + address := "http://localhost:9090" + client, err := prometheusApi.NewClient(prometheusApi.Config{ + Address: address, + // Address: "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090", + }) + if err != nil { + return nil, err + } + + // Create a new API v1 client + v1api := prometheusV1.NewAPI(client) + + // Create a context + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Query the Prometheus API over a time range + period := prometheusV1.Range{ + // Start: time.Now().Add(time.Hour * -24 * 30), + Start: time.Now().Add(time.Hour * -24), + End: time.Now(), + Step: time.Minute * 5, + } + result, warnings, err := v1api.QueryRange(ctx, "max(irate(container_cpu_usage_seconds_total{namespace=~\"radix-web-console-qa\", container!=\"\"}[60m]))", + period) + if err != nil { + return nil, err + } + if len(warnings) > 0 { + log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) + } + log.Ctx(ctx).Info().Msgf("Result: %v\n", result) + now := time.Now() return &applicationModels.UsedResources{ From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), From abdb3d27358ba6b795f7a377a19e22073e539f9f Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 11 Sep 2024 09:43:34 +0200 Subject: [PATCH 03/32] Added multiple prometheus queries --- api/applications/applications_handler.go | 47 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index 601dc243..1bdc173f 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -31,6 +31,7 @@ import ( operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/prometheus/common/model" "github.com/rs/zerolog/log" authorizationapi "k8s.io/api/authorization/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -459,6 +460,11 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam // GetUsedResources Returns the used resources for an application func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName string, r *http.Request) (*applicationModels.UsedResources, error) { + _, err := ah.getUserAccount().RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + address := "http://localhost:9090" client, err := prometheusApi.NewClient(prometheusApi.Config{ Address: address, @@ -475,24 +481,35 @@ func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName stri ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - // Query the Prometheus API over a time range - period := prometheusV1.Range{ - // Start: time.Now().Add(time.Hour * -24 * 30), - Start: time.Now().Add(time.Hour * -24), - End: time.Now(), - Step: time.Minute * 5, - } - result, warnings, err := v1api.QueryRange(ctx, "max(irate(container_cpu_usage_seconds_total{namespace=~\"radix-web-console-qa\", container!=\"\"}[60m]))", - period) - if err != nil { - return nil, err + period := "30d" + cpuUsageQuery := fmt.Sprintf(`(sum(rate(container_cpu_usage_seconds_total{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) + memoryUsageQuery := fmt.Sprintf(`(sum(rate(container_memory_usage_bytes{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) + results := make(map[string]model.Value) + queries := map[string]string{ + "CPU Max": fmt.Sprintf("max_over_time%s", cpuUsageQuery), // Max CPU usage + "CPU Min": fmt.Sprintf("min_over_time%s", cpuUsageQuery), // Min CPU usage + "CPU Avg": fmt.Sprintf("avg_over_time%s", cpuUsageQuery), // Average CPU usage + "Memory Max": fmt.Sprintf("max_over_time%s", memoryUsageQuery), // Max Memory usage + "Memory Min": fmt.Sprintf("min_over_time%s", memoryUsageQuery), // Min Memory usage + "Memory Avg": fmt.Sprintf("avg_over_time%s", memoryUsageQuery), // Average Memory usage } - if len(warnings) > 0 { - log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) - } - log.Ctx(ctx).Info().Msgf("Result: %v\n", result) now := time.Now() + for metricName, query := range queries { + result, warnings, err := v1api.Query(ctx, query, now) + if err != nil { + return nil, err + } + if len(warnings) > 0 { + log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) + } + // Print results + for metricName, result := range results { + fmt.Printf("%s: %v\n", metricName, result) + } + results[metricName] = result + } + return &applicationModels.UsedResources{ From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), To: radixutils.FormatTimestamp(now), From 84ae9a5f468ed463b12d9fa908e505f1424dab7e Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 11 Sep 2024 12:47:24 +0200 Subject: [PATCH 04/32] Extracted prometheus metrics logic --- api/applications/applications_handler.go | 69 ++------------ api/utils/prometheus/prometheus.go | 113 +++++++++++++++++++++++ 2 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 api/utils/prometheus/prometheus.go diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index 1bdc173f..0903294d 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -17,6 +17,7 @@ import ( jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/kubequery" apimodels "github.com/equinor/radix-api/api/models" + prometheusUtils "github.com/equinor/radix-api/api/utils/prometheus" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" @@ -29,9 +30,6 @@ import ( v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/radixvalidators" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" - prometheusApi "github.com/prometheus/client_golang/api" - prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" - "github.com/prometheus/common/model" "github.com/rs/zerolog/log" authorizationapi "k8s.io/api/authorization/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -464,66 +462,11 @@ func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName stri if err != nil { return nil, err } - - address := "http://localhost:9090" - client, err := prometheusApi.NewClient(prometheusApi.Config{ - Address: address, - // Address: "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090", - }) - if err != nil { - return nil, err - } - - // Create a new API v1 client - v1api := prometheusV1.NewAPI(client) - - // Create a context - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - period := "30d" - cpuUsageQuery := fmt.Sprintf(`(sum(rate(container_cpu_usage_seconds_total{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) - memoryUsageQuery := fmt.Sprintf(`(sum(rate(container_memory_usage_bytes{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) - results := make(map[string]model.Value) - queries := map[string]string{ - "CPU Max": fmt.Sprintf("max_over_time%s", cpuUsageQuery), // Max CPU usage - "CPU Min": fmt.Sprintf("min_over_time%s", cpuUsageQuery), // Min CPU usage - "CPU Avg": fmt.Sprintf("avg_over_time%s", cpuUsageQuery), // Average CPU usage - "Memory Max": fmt.Sprintf("max_over_time%s", memoryUsageQuery), // Max Memory usage - "Memory Min": fmt.Sprintf("min_over_time%s", memoryUsageQuery), // Min Memory usage - "Memory Avg": fmt.Sprintf("avg_over_time%s", memoryUsageQuery), // Average Memory usage - } - - now := time.Now() - for metricName, query := range queries { - result, warnings, err := v1api.Query(ctx, query, now) - if err != nil { - return nil, err - } - if len(warnings) > 0 { - log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) - } - // Print results - for metricName, result := range results { - fmt.Printf("%s: %v\n", metricName, result) - } - results[metricName] = result - } - - return &applicationModels.UsedResources{ - From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), - To: radixutils.FormatTimestamp(now), - CPU: &applicationModels.UsedResource{ - Min: "10m", - Max: "500m", - Average: "223m", - }, - Memory: &applicationModels.UsedResource{ - Min: "10Mi", - Max: "500Mi", - Average: "223Mi", - }, - }, nil + prometheusUrl := "http://localhost:9090" + // prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" + envs := []string{"qa", "prod"} + components := []string{"api", "web"} + return prometheusUtils.GetUsedResources(ctx, appName, "30d", prometheusUrl, envs, components) } func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go new file mode 100644 index 00000000..3b1e16ac --- /dev/null +++ b/api/utils/prometheus/prometheus.go @@ -0,0 +1,113 @@ +package prometheus + +import ( + "context" + "fmt" + "time" + + applicationModels "github.com/equinor/radix-api/api/applications/models" + radixutils "github.com/equinor/radix-common/utils" + prometheusApi "github.com/prometheus/client_golang/api" + prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/prometheus/common/model" + "github.com/rs/zerolog/log" + "k8s.io/apimachinery/pkg/api/resource" +) + +type queryName string + +const ( + cpuMax queryName = "cpuMax" + cpuMin queryName = "cpuMin" + cpuAvg queryName = "cpuAvg" + memoryMax queryName = "memoryMax" + memoryMin queryName = "memoryMin" + memoryAvg queryName = "memoryAvg" +) + +func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string, _, _ []string) (*applicationModels.UsedResources, error) { + client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) + if err != nil { + return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + api := prometheusV1.NewAPI(client) + results := make(map[queryName]model.Value) + now := time.Now() + for metricName, query := range getPrometheusQueries(appName, period) { + result, warnings, err := api.Query(ctx, query, now) + if err != nil { + return nil, fmt.Errorf("failed to get Prometheus metrics: %w", err) + } + if len(warnings) > 0 { + log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) + } + results[metricName] = result + } + return &applicationModels.UsedResources{ + From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), + To: radixutils.FormatTimestamp(now), + CPU: &applicationModels.UsedResource{ + Min: getCpuMetricValue(ctx, results, cpuMin), + Max: getCpuMetricValue(ctx, results, cpuMax), + Average: getCpuMetricValue(ctx, results, cpuAvg), + }, + Memory: &applicationModels.UsedResource{ + Min: getMemoryMetricValue(ctx, results, memoryMin), + Max: getMemoryMetricValue(ctx, results, memoryMax), + Average: getMemoryMetricValue(ctx, results, memoryAvg), + }, + }, nil +} + +func getCpuMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { + metricsExist, value := getSummedMetricsValue(ctx, queryResults, queryName) + if metricsExist { + return resource.NewMilliQuantity(int64(value), resource.BinarySI).String() + } + return "" +} + +func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { + metricsExist, value := getSummedMetricsValue(ctx, queryResults, queryName) + if metricsExist { + return resource.NewQuantity(int64(value), resource.BinarySI).String() + } + return "" +} + +func getSummedMetricsValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) (bool, float64) { + queryResult, ok := queryResults[queryName] + if !ok { + return false, 0 + } + groupedMetrics, ok := queryResult.(model.Vector) + if !ok { + log.Ctx(ctx).Error().Msgf("Failed to convert metrics query %s result to Vector", queryName) + return false, 0 + } + metricsExist := false + var memoryUsageBytes float64 + for _, sample := range groupedMetrics { + memoryUsageBytes += float64(sample.Value) + metricsExist = true + } + return metricsExist, memoryUsageBytes +} + +func getPrometheusQueries(appName string, period string) map[queryName]string { + cpuUsageQuery := fmt.Sprintf(`(sum(rate(container_cpu_usage_seconds_total{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) + memoryUsageQuery := fmt.Sprintf(`(sum(rate(container_memory_usage_bytes{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) + + return map[queryName]string{ + cpuMax: fmt.Sprintf("max_over_time%s", cpuUsageQuery), + cpuMin: fmt.Sprintf("min_over_time%s", cpuUsageQuery), + cpuAvg: fmt.Sprintf("avg_over_time%s", cpuUsageQuery), + memoryMax: fmt.Sprintf("max_over_time%s", memoryUsageQuery), + memoryMin: fmt.Sprintf("min_over_time%s", memoryUsageQuery), + memoryAvg: fmt.Sprintf("avg_over_time%s", memoryUsageQuery), + } +} From 46a26c229187b5bbf6c7d8c356a7d3777d438a60 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 11 Sep 2024 14:08:31 +0200 Subject: [PATCH 05/32] Corrected min-max-avg values --- api/applications/applications_handler.go | 2 +- api/utils/prometheus/prometheus.go | 61 +++++++++++++++++------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index 0903294d..fe22130b 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -462,7 +462,7 @@ func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName stri if err != nil { return nil, err } - prometheusUrl := "http://localhost:9090" + prometheusUrl := "http://localhost:9091" // prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" envs := []string{"qa", "prod"} components := []string{"api", "web"} diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index 3b1e16ac..81433479 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -7,6 +7,7 @@ import ( applicationModels "github.com/equinor/radix-api/api/applications/models" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/slice" prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" @@ -31,7 +32,7 @@ func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) // replace with 10*time.Seconds defer cancel() api := prometheusV1.NewAPI(client) @@ -47,7 +48,7 @@ func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string } results[metricName] = result } - return &applicationModels.UsedResources{ + resources := applicationModels.UsedResources{ From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), To: radixutils.FormatTimestamp(now), CPU: &applicationModels.UsedResource{ @@ -60,42 +61,66 @@ func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string Max: getMemoryMetricValue(ctx, results, memoryMax), Average: getMemoryMetricValue(ctx, results, memoryAvg), }, - }, nil + } + return &resources, nil } func getCpuMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { - metricsExist, value := getSummedMetricsValue(ctx, queryResults, queryName) - if metricsExist { - return resource.NewMilliQuantity(int64(value), resource.BinarySI).String() + if value, ok := getSummedMetricsValue(ctx, queryResults, queryName); ok { + quantity := resource.NewMilliQuantity(int64(value*1000.0), resource.BinarySI) + return quantity.String() } return "" } func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { - metricsExist, value := getSummedMetricsValue(ctx, queryResults, queryName) - if metricsExist { - return resource.NewQuantity(int64(value), resource.BinarySI).String() + if value, ok := getSummedMetricsValue(ctx, queryResults, queryName); ok { + quantity := resource.NewScaledQuantity(int64(value/1000.0), resource.Mega) + return quantity.String() } return "" } -func getSummedMetricsValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) (bool, float64) { +func getSummedMetricsValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { - return false, 0 + return 0, false } groupedMetrics, ok := queryResult.(model.Vector) if !ok { log.Ctx(ctx).Error().Msgf("Failed to convert metrics query %s result to Vector", queryName) - return false, 0 + return 0, false + } + values := slice.Reduce(groupedMetrics, make([]float64, 0), func(acc []float64, sample *model.Sample) []float64 { + return append(acc, float64(sample.Value)) + }) + if len(values) == 0 { + return 0, false } - metricsExist := false - var memoryUsageBytes float64 - for _, sample := range groupedMetrics { - memoryUsageBytes += float64(sample.Value) - metricsExist = true + switch queryName { + case cpuMax, memoryMax: + max := slice.Reduce(values, values[0], func(maxValue, sample float64) float64 { + if maxValue < sample { + return sample + } + return maxValue + }) + return max, true + case cpuMin, memoryMin: + min := slice.Reduce(values, values[0], func(minValue, sample float64) float64 { + if minValue > sample { + return sample + } + return minValue + }) + return min, true + case cpuAvg, memoryAvg: + avg := slice.Reduce(values, 0, func(sum, sample float64) float64 { + return sum + sample + }) / float64(len(values)) + return avg, true } - return metricsExist, memoryUsageBytes + return 0, false } func getPrometheusQueries(appName string, period string) map[queryName]string { From f1a942aa0dbe08311999fadc372f57da427e4db5 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 11 Sep 2024 15:14:25 +0200 Subject: [PATCH 06/32] Added comment and correct URL --- api/applications/applications_handler.go | 3 +-- api/utils/prometheus/prometheus.go | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index fe22130b..4640c937 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -462,8 +462,7 @@ func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName stri if err != nil { return nil, err } - prometheusUrl := "http://localhost:9091" - // prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" + prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" envs := []string{"qa", "prod"} components := []string{"api", "web"} return prometheusUtils.GetUsedResources(ctx, appName, "30d", prometheusUrl, envs, components) diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index 81433479..c6e60252 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -26,7 +26,9 @@ const ( memoryAvg queryName = "memoryAvg" ) +// GetUsedResources Get used resources for the application func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string, _, _ []string) (*applicationModels.UsedResources, error) { + log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) if err != nil { return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) @@ -62,6 +64,7 @@ func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string Average: getMemoryMetricValue(ctx, results, memoryAvg), }, } + log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) return &resources, nil } From 2045c747b59db71168124c441072dba66aa751fb Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 11 Sep 2024 16:07:06 +0200 Subject: [PATCH 07/32] Added query arguments --- api/applications/applications_controller.go | 5 +- api/applications/applications_handler.go | 6 +- api/utils/prometheus/prometheus.go | 126 +++++++++++++------- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index 5ef009d6..7a650f68 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -1027,9 +1027,12 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht // "404": // description: "Not found" appName := mux.Vars(r)["appName"] + envName := r.FormValue("env") + componentName := r.FormValue("component") + period := r.FormValue("period") handler := ac.applicationHandlerFactory.Create(accounts) - jobSummary, err := handler.GetUsedResources(r.Context(), appName, r) + jobSummary, err := handler.GetUsedResources(r.Context(), appName, envName, componentName, period) if err != nil { ac.ErrorResponse(w, r, err) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index 4640c937..fde11b4f 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -457,15 +457,13 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam } // GetUsedResources Returns the used resources for an application -func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName string, r *http.Request) (*applicationModels.UsedResources, error) { +func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, envName, componentName, period string) (*applicationModels.UsedResources, error) { _, err := ah.getUserAccount().RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) if err != nil { return nil, err } prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" - envs := []string{"qa", "prod"} - components := []string{"api", "web"} - return prometheusUtils.GetUsedResources(ctx, appName, "30d", prometheusUrl, envs, components) + return prometheusUtils.GetUsedResources(ctx, prometheusUrl, appName, envName, componentName, period) } func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index c6e60252..c5c2c6f7 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -2,12 +2,15 @@ package prometheus import ( "context" + "errors" "fmt" + "regexp" "time" applicationModels "github.com/equinor/radix-api/api/applications/models" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" + "github.com/equinor/radix-operator/pkg/apis/utils" prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" @@ -18,40 +21,38 @@ import ( type queryName string const ( - cpuMax queryName = "cpuMax" - cpuMin queryName = "cpuMin" - cpuAvg queryName = "cpuAvg" - memoryMax queryName = "memoryMax" - memoryMin queryName = "memoryMin" - memoryAvg queryName = "memoryAvg" + cpuMax queryName = "cpuMax" + cpuMin queryName = "cpuMin" + cpuAvg queryName = "cpuAvg" + memoryMax queryName = "memoryMax" + memoryMin queryName = "memoryMin" + memoryAvg queryName = "memoryAvg" + periodExpression = `^[0-9]{1,5}[mhdw]$` ) // GetUsedResources Get used resources for the application -func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string, _, _ []string) (*applicationModels.UsedResources, error) { - log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) - client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) +func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, period string) (*applicationModels.UsedResources, error) { + err := validatePeriod(period) if err != nil { - return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) + return nil, err } - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) // replace with 10*time.Seconds + ctx, cancel := context.WithTimeout(ctx, time.Minute) // replace with 10*time.Seconds defer cancel() - api := prometheusV1.NewAPI(client) - results := make(map[queryName]model.Value) - now := time.Now() - for metricName, query := range getPrometheusQueries(appName, period) { - result, warnings, err := api.Query(ctx, query, now) - if err != nil { - return nil, fmt.Errorf("failed to get Prometheus metrics: %w", err) - } - if len(warnings) > 0 { - log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) - } - results[metricName] = result + log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) + results, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, period) + if err != nil { + return nil, err } + resources := getUsedResourcesByMetrics(ctx, results) + log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) + return resources, nil +} + +func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]model.Value) *applicationModels.UsedResources { + now := time.Now() resources := applicationModels.UsedResources{ - From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), + From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), // TODO change this corresponding the requested period To: radixutils.FormatTimestamp(now), CPU: &applicationModels.UsedResource{ Min: getCpuMetricValue(ctx, results, cpuMin), @@ -64,12 +65,39 @@ func GetUsedResources(ctx context.Context, appName, period, prometheusUrl string Average: getMemoryMetricValue(ctx, results, memoryAvg), }, } - log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) - return &resources, nil + return &resources +} + +func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, componentName, period string) (map[queryName]model.Value, error) { + client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) + if err != nil { + return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) + } + api := prometheusV1.NewAPI(client) + results := make(map[queryName]model.Value) + now := time.Now() + for metricName, query := range getPrometheusQueries(appName, envName, componentName, period) { + result, warnings, err := api.Query(ctx, query, now) + if err != nil { + return nil, fmt.Errorf("failed to get Prometheus metrics: %w", err) + } + if len(warnings) > 0 { + log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) + } + results[metricName] = result + } + return results, nil +} + +func validatePeriod(period string) error { + if len(period) > 0 && !regexp.MustCompile(periodExpression).MatchString(period) { + return errors.New("invalid period format") + } + return nil } func getCpuMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { - if value, ok := getSummedMetricsValue(ctx, queryResults, queryName); ok { + if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { quantity := resource.NewMilliQuantity(int64(value*1000.0), resource.BinarySI) return quantity.String() } @@ -77,14 +105,14 @@ func getCpuMetricValue(ctx context.Context, queryResults map[queryName]model.Val } func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { - if value, ok := getSummedMetricsValue(ctx, queryResults, queryName); ok { + if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { quantity := resource.NewScaledQuantity(int64(value/1000.0), resource.Mega) return quantity.String() } return "" } -func getSummedMetricsValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) (float64, bool) { +func getMetricsValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { return 0, false @@ -102,40 +130,46 @@ func getSummedMetricsValue(ctx context.Context, queryResults map[queryName]model } switch queryName { case cpuMax, memoryMax: - max := slice.Reduce(values, values[0], func(maxValue, sample float64) float64 { + maxVal := slice.Reduce(values, values[0], func(maxValue, sample float64) float64 { if maxValue < sample { return sample } return maxValue }) - return max, true + return maxVal, true case cpuMin, memoryMin: - min := slice.Reduce(values, values[0], func(minValue, sample float64) float64 { + minVal := slice.Reduce(values, values[0], func(minValue, sample float64) float64 { if minValue > sample { return sample } return minValue }) - return min, true + return minVal, true case cpuAvg, memoryAvg: - avg := slice.Reduce(values, 0, func(sum, sample float64) float64 { + avgVal := slice.Reduce(values, 0, func(sum, sample float64) float64 { return sum + sample }) / float64(len(values)) - return avg, true + return avgVal, true } return 0, false } -func getPrometheusQueries(appName string, period string) map[queryName]string { - cpuUsageQuery := fmt.Sprintf(`(sum(rate(container_cpu_usage_seconds_total{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) - memoryUsageQuery := fmt.Sprintf(`(sum(rate(container_memory_usage_bytes{namespace=~"%[1]s-.*",namespace!="%[1]s-app",}[5m])) by (namespace,container)[%s:])`, appName, period) - +func getPrometheusQueries(appName, envName, componentName, period string) map[queryName]string { + if period == "" { + period = "30d" + } + environmentFilter := radixutils.TernaryString(envName == "", + fmt.Sprintf(`,namespace=~"%s-.*"`, appName), + fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) + componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) + cpuUsageRateQuery := fmt.Sprintf(`rate(container_cpu_usage_seconds_total{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, period) + memoryUsageRateQuery := fmt.Sprintf(`rate(container_memory_usage_bytes{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, period) return map[queryName]string{ - cpuMax: fmt.Sprintf("max_over_time%s", cpuUsageQuery), - cpuMin: fmt.Sprintf("min_over_time%s", cpuUsageQuery), - cpuAvg: fmt.Sprintf("avg_over_time%s", cpuUsageQuery), - memoryMax: fmt.Sprintf("max_over_time%s", memoryUsageQuery), - memoryMin: fmt.Sprintf("min_over_time%s", memoryUsageQuery), - memoryAvg: fmt.Sprintf("avg_over_time%s", memoryUsageQuery), + cpuMax: fmt.Sprintf("max_over_time(sum(%s)", cpuUsageRateQuery), + cpuMin: fmt.Sprintf("min_over_time(sum(%s)", cpuUsageRateQuery), + cpuAvg: fmt.Sprintf("avg_over_time(sum(%s)", cpuUsageRateQuery), + memoryMax: fmt.Sprintf("max_over_time(sum(%s)", memoryUsageRateQuery), + memoryMin: fmt.Sprintf("min_over_time(sum(%s)", memoryUsageRateQuery), + memoryAvg: fmt.Sprintf("avg_over_time(sum(%s)", memoryUsageRateQuery), } } From 23242e1c9ab0eff3f4ecacf097a90b2c1e0f386d Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 12 Sep 2024 11:01:09 +0200 Subject: [PATCH 08/32] Added query arguments --- api/applications/applications_controller.go | 4 +-- api/applications/applications_handler.go | 7 ++-- api/utils/prometheus/prometheus.go | 38 +++++++++++---------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index 7a650f68..eab42804 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -1029,10 +1029,10 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht appName := mux.Vars(r)["appName"] envName := r.FormValue("env") componentName := r.FormValue("component") - period := r.FormValue("period") + duration := r.FormValue("duration") handler := ac.applicationHandlerFactory.Create(accounts) - jobSummary, err := handler.GetUsedResources(r.Context(), appName, envName, componentName, period) + jobSummary, err := handler.GetUsedResources(r.Context(), appName, envName, componentName, duration) if err != nil { ac.ErrorResponse(w, r, err) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index fde11b4f..b22419fc 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -457,13 +457,14 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam } // GetUsedResources Returns the used resources for an application -func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, envName, componentName, period string) (*applicationModels.UsedResources, error) { +func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, envName, componentName, duration string) (*applicationModels.UsedResources, error) { _, err := ah.getUserAccount().RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) if err != nil { return nil, err } - prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" - return prometheusUtils.GetUsedResources(ctx, prometheusUrl, appName, envName, componentName, period) + prometheusUrl := "http://localhost:9091" + // prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" + return prometheusUtils.GetUsedResources(ctx, prometheusUrl, appName, envName, componentName, duration) } func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index c5c2c6f7..a7bf13be 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -2,7 +2,6 @@ package prometheus import ( "context" - "errors" "fmt" "regexp" "time" @@ -14,6 +13,7 @@ import ( prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" + prometheusModel "github.com/prometheus/common/model" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/resource" ) @@ -21,18 +21,19 @@ import ( type queryName string const ( - cpuMax queryName = "cpuMax" - cpuMin queryName = "cpuMin" - cpuAvg queryName = "cpuAvg" - memoryMax queryName = "memoryMax" - memoryMin queryName = "memoryMin" - memoryAvg queryName = "memoryAvg" - periodExpression = `^[0-9]{1,5}[mhdw]$` + cpuMax queryName = "cpuMax" + cpuMin queryName = "cpuMin" + cpuAvg queryName = "cpuAvg" + memoryMax queryName = "memoryMax" + memoryMin queryName = "memoryMin" + memoryAvg queryName = "memoryAvg" + durationExpression = `^[0-9]{1,5}[mhdw]$` + defaultDuration = "30d" ) // GetUsedResources Get used resources for the application -func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, period string) (*applicationModels.UsedResources, error) { - err := validatePeriod(period) +func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, durationString string) (*applicationModels.UsedResources, error) { + durationValue, err := getMinimumPeriodTime(durationString) if err != nil { return nil, err } @@ -40,19 +41,19 @@ func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, comp defer cancel() log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) - results, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, period) + results, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, durationString) if err != nil { return nil, err } - resources := getUsedResourcesByMetrics(ctx, results) + resources := getUsedResourcesByMetrics(ctx, results, durationValue) log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) return resources, nil } -func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]model.Value) *applicationModels.UsedResources { +func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]prometheusModel.Value, queryDuration time.Duration) *applicationModels.UsedResources { now := time.Now() resources := applicationModels.UsedResources{ - From: radixutils.FormatTimestamp(now.Add(-time.Hour * 24 * 30)), // TODO change this corresponding the requested period + From: radixutils.FormatTimestamp(now.Add(-queryDuration)), To: radixutils.FormatTimestamp(now), CPU: &applicationModels.UsedResource{ Min: getCpuMetricValue(ctx, results, cpuMin), @@ -89,11 +90,12 @@ func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, return results, nil } -func validatePeriod(period string) error { - if len(period) > 0 && !regexp.MustCompile(periodExpression).MatchString(period) { - return errors.New("invalid period format") +func getMinimumPeriodTime(duration string) (time.Duration, error) { + if len(duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(duration) { + duration = defaultDuration } - return nil + parsedDuration, err := prometheusModel.ParseDuration(duration) + return time.Duration(parsedDuration), err } func getCpuMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { From d47f6809d47d0afcc2359b229cc9e73474d368d1 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 12 Sep 2024 16:31:50 +0200 Subject: [PATCH 09/32] Added extra query arguments --- api/applications/applications_controller.go | 42 +++++++++- api/applications/applications_handler.go | 4 +- api/applications/models/used_resources.go | 24 +++++- api/utils/prometheus/prometheus.go | 92 ++++++++++++--------- swaggerui/html/swagger.json | 21 +++++ 5 files changed, 135 insertions(+), 48 deletions(-) diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index eab42804..a6d46b42 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -1000,15 +1000,43 @@ func (ac *applicationController) TriggerPipelinePromote(accounts models.Accounts // GetUsedResources Gets used resources for the application func (ac *applicationController) GetUsedResources(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /applications/{appName}/resources application resources + // swagger:operation GET /applications/{appName}/resources application getResources // --- // summary: Gets used resources for the application // parameters: // - name: appName // in: path - // description: Name of application + // description: Name of the application // type: string // required: true + // - name: environment + // in: query + // description: Name of the application environment + // type: string + // required: false + // - name: component + // in: query + // description: Name of the application component in an environment + // type: string + // required: false + // - name: duration + // in: query + // description: Duration of the period, default is 30d (30 days). E.g. 10m, 2h, 1w: m-minutes, h-hours, d-days, w-weeks + // type: string + // required: false + // example: "10h" + // - name: since + // in: query + // description: End time-point of the period in the past, default is now. E.g. 10m, 2h, 1w: m-minutes, h-hours, d-days, w-weeks + // type: string + // required: false + // example: "10h" + // - name: ignorezero + // in: query + // description: Ignore metrics with zero value if true, default is false + // type: string + // format: boolean + // required: false // - name: Impersonate-User // in: header // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) @@ -1027,12 +1055,18 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht // "404": // description: "Not found" appName := mux.Vars(r)["appName"] - envName := r.FormValue("env") + envName := r.FormValue("environment") componentName := r.FormValue("component") duration := r.FormValue("duration") + since := r.FormValue("since") + ignoreZeroArg := r.FormValue("ignorezero") + var ignoreZero = false + if strings.TrimSpace(ignoreZeroArg) != "" { + ignoreZero, _ = strconv.ParseBool(ignoreZeroArg) + } handler := ac.applicationHandlerFactory.Create(accounts) - jobSummary, err := handler.GetUsedResources(r.Context(), appName, envName, componentName, duration) + jobSummary, err := handler.GetUsedResources(r.Context(), appName, envName, componentName, duration, since, ignoreZero) if err != nil { ac.ErrorResponse(w, r, err) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index b22419fc..abb1b2ba 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -457,14 +457,14 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam } // GetUsedResources Returns the used resources for an application -func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, envName, componentName, duration string) (*applicationModels.UsedResources, error) { +func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { _, err := ah.getUserAccount().RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) if err != nil { return nil, err } prometheusUrl := "http://localhost:9091" // prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" - return prometheusUtils.GetUsedResources(ctx, prometheusUrl, appName, envName, componentName, duration) + return prometheusUtils.GetUsedResources(ctx, prometheusUrl, appName, envName, componentName, duration, since, ignoreZero) } func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { diff --git a/api/applications/models/used_resources.go b/api/applications/models/used_resources.go index 2b0f0026..7d2d4009 100644 --- a/api/applications/models/used_resources.go +++ b/api/applications/models/used_resources.go @@ -35,17 +35,35 @@ type UsedResource struct { // // required: false // example: 120m - Min string `json:"min"` + Min string `json:"min,omitempty"` // Max resource used // // required: false // example: 120m - Max string `json:"max"` + Max string `json:"max,omitempty"` // Average resource used // // required: false // example: 120m - Average string `json:"average"` + Average string `json:"average,omitempty"` + + // MinActual actual precise resource used + // + // required: false + // example: 0.00012 + MinActual *float64 `json:"minActual,omitempty"` + + // MaxActual actual precise resource used + // + // required: false + // example: 0.00037 + MaxActual *float64 `json:"maxActual,omitempty"` + + // AvgActual actual precise resource used + // + // required: false + // example: 0.00012 + AvgActual *float64 `json:"avgActual,omitempty"` } diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index a7bf13be..03ec0cd1 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -3,11 +3,13 @@ package prometheus import ( "context" "fmt" + "math" "regexp" "time" applicationModels "github.com/equinor/radix-api/api/applications/models" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/utils" prometheusApi "github.com/prometheus/client_golang/api" @@ -32,8 +34,12 @@ const ( ) // GetUsedResources Get used resources for the application -func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, durationString string) (*applicationModels.UsedResources, error) { - durationValue, err := getMinimumPeriodTime(durationString) +func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { + durationValue, err := parseQueryDuration(duration, defaultDuration) + if err != nil { + return nil, err + } + sinceValue, err := parseQueryDuration(since, "") if err != nil { return nil, err } @@ -41,35 +47,34 @@ func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, comp defer cancel() log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) - results, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, durationString) + results, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, duration, since) if err != nil { return nil, err } - resources := getUsedResourcesByMetrics(ctx, results, durationValue) + resources := getUsedResourcesByMetrics(ctx, results, durationValue, sinceValue, ignoreZero) log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) return resources, nil } -func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]prometheusModel.Value, queryDuration time.Duration) *applicationModels.UsedResources { +func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration, ignoreZero bool) *applicationModels.UsedResources { + usedCpuResource := applicationModels.UsedResource{} + usedCpuResource.Min, usedCpuResource.MinActual = getCpuMetricValue(ctx, results, cpuMin, ignoreZero) + usedCpuResource.Max, usedCpuResource.MaxActual = getCpuMetricValue(ctx, results, cpuMax, ignoreZero) + usedCpuResource.Average, usedCpuResource.AvgActual = getCpuMetricValue(ctx, results, cpuAvg, ignoreZero) + usedMemoryResource := applicationModels.UsedResource{} + usedMemoryResource.Min, usedMemoryResource.MinActual = getMemoryMetricValue(ctx, results, memoryMin, ignoreZero) + usedMemoryResource.Max, usedMemoryResource.MaxActual = getMemoryMetricValue(ctx, results, memoryMax, ignoreZero) + usedMemoryResource.Average, usedMemoryResource.AvgActual = getMemoryMetricValue(ctx, results, memoryAvg, ignoreZero) now := time.Now() - resources := applicationModels.UsedResources{ - From: radixutils.FormatTimestamp(now.Add(-queryDuration)), - To: radixutils.FormatTimestamp(now), - CPU: &applicationModels.UsedResource{ - Min: getCpuMetricValue(ctx, results, cpuMin), - Max: getCpuMetricValue(ctx, results, cpuMax), - Average: getCpuMetricValue(ctx, results, cpuAvg), - }, - Memory: &applicationModels.UsedResource{ - Min: getMemoryMetricValue(ctx, results, memoryMin), - Max: getMemoryMetricValue(ctx, results, memoryMax), - Average: getMemoryMetricValue(ctx, results, memoryAvg), - }, + return &applicationModels.UsedResources{ + From: radixutils.FormatTimestamp(now.Add(-queryDuration)), + To: radixutils.FormatTimestamp(now.Add(-querySince)), + CPU: &usedCpuResource, + Memory: &usedMemoryResource, } - return &resources } -func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, componentName, period string) (map[queryName]model.Value, error) { +func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string) (map[queryName]prometheusModel.Value, error) { client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) if err != nil { return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) @@ -77,7 +82,7 @@ func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, api := prometheusV1.NewAPI(client) results := make(map[queryName]model.Value) now := time.Now() - for metricName, query := range getPrometheusQueries(appName, envName, componentName, period) { + for metricName, query := range getPrometheusQueries(appName, envName, componentName, duration, since) { result, warnings, err := api.Query(ctx, query, now) if err != nil { return nil, fmt.Errorf("failed to get Prometheus metrics: %w", err) @@ -90,31 +95,37 @@ func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, return results, nil } -func getMinimumPeriodTime(duration string) (time.Duration, error) { +func parseQueryDuration(duration string, defaultValue string) (time.Duration, error) { if len(duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(duration) { - duration = defaultDuration + duration = defaultValue } parsedDuration, err := prometheusModel.ParseDuration(duration) return time.Duration(parsedDuration), err } -func getCpuMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { - if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { - quantity := resource.NewMilliQuantity(int64(value*1000.0), resource.BinarySI) - return quantity.String() +func roundActualValue(num float64) float64 { + return math.Round(num*1e6) / 1e6 +} + +func getCpuMetricValue(ctx context.Context, queryResults map[queryName]prometheusModel.Value, queryName queryName, ignoreZero bool) (string, *float64) { + if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { + valueInMillicores := value * 1000.0 + quantity := resource.NewMilliQuantity(int64(valueInMillicores), resource.BinarySI) + return quantity.String(), pointers.Ptr(roundActualValue(valueInMillicores)) } - return "" + return "", nil } -func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) string { - if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { - quantity := resource.NewScaledQuantity(int64(value/1000.0), resource.Mega) - return quantity.String() +func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]prometheusModel.Value, queryName queryName, ignoreZero bool) (string, *float64) { + if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { + valueInMegabytes := value / 1000.0 + quantity := resource.NewScaledQuantity(int64(valueInMegabytes), resource.Mega) + return quantity.String(), pointers.Ptr(roundActualValue(valueInMegabytes)) } - return "" + return "", nil } -func getMetricsValue(ctx context.Context, queryResults map[queryName]model.Value, queryName queryName) (float64, bool) { +func getMetricsValue(ctx context.Context, queryResults map[queryName]prometheusModel.Value, queryName queryName, ignoreZero bool) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { return 0, false @@ -125,6 +136,9 @@ func getMetricsValue(ctx context.Context, queryResults map[queryName]model.Value return 0, false } values := slice.Reduce(groupedMetrics, make([]float64, 0), func(acc []float64, sample *model.Sample) []float64 { + if ignoreZero && sample.Value <= 0 { + return acc + } return append(acc, float64(sample.Value)) }) if len(values) == 0 { @@ -156,16 +170,16 @@ func getMetricsValue(ctx context.Context, queryResults map[queryName]model.Value return 0, false } -func getPrometheusQueries(appName, envName, componentName, period string) map[queryName]string { - if period == "" { - period = "30d" +func getPrometheusQueries(appName, envName, componentName, duration, since string) map[queryName]string { + if duration == "" { + duration = "30d" } environmentFilter := radixutils.TernaryString(envName == "", fmt.Sprintf(`,namespace=~"%s-.*"`, appName), fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) - cpuUsageRateQuery := fmt.Sprintf(`rate(container_cpu_usage_seconds_total{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, period) - memoryUsageRateQuery := fmt.Sprintf(`rate(container_memory_usage_bytes{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, period) + cpuUsageRateQuery := fmt.Sprintf(`rate(container_cpu_usage_seconds_total{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:%s]`, appName, environmentFilter, componentFilter, duration, since) + memoryUsageRateQuery := fmt.Sprintf(`rate(container_memory_usage_bytes{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:%s]`, appName, environmentFilter, componentFilter, duration, since) return map[queryName]string{ cpuMax: fmt.Sprintf("max_over_time(sum(%s)", cpuUsageRateQuery), cpuMin: fmt.Sprintf("min_over_time(sum(%s)", cpuUsageRateQuery), diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index cc7804b7..b0a893bb 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -8051,17 +8051,38 @@ "x-go-name": "Average", "example": "120m" }, + "avgActual": { + "description": "AvgActual actual precise resource used", + "type": "number", + "format": "double", + "x-go-name": "AvgActual", + "example": 0.00012 + }, "max": { "description": "Max resource used", "type": "string", "x-go-name": "Max", "example": "120m" }, + "maxActual": { + "description": "MaxActual actual precise resource used", + "type": "number", + "format": "double", + "x-go-name": "MaxActual", + "example": 0.00037 + }, "min": { "description": "Min resource used", "type": "string", "x-go-name": "Min", "example": "120m" + }, + "minActual": { + "description": "MinActual actual precise resource used", + "type": "number", + "format": "double", + "x-go-name": "MinActual", + "example": 0.00012 } }, "x-go-package": "github.com/equinor/radix-api/api/applications/models" From 9e380d7024ec744550ac29006a7464758a02149e Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 12 Sep 2024 16:45:53 +0200 Subject: [PATCH 10/32] Added extra query arguments --- api/applications/applications_controller.go | 6 ++-- swaggerui/html/swagger.json | 35 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index a6d46b42..055f1ddc 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -1021,16 +1021,14 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht // required: false // - name: duration // in: query - // description: Duration of the period, default is 30d (30 days). E.g. 10m, 2h, 1w: m-minutes, h-hours, d-days, w-weeks + // description: Duration of the period, default is 30d (30 days). Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks // type: string // required: false - // example: "10h" // - name: since // in: query - // description: End time-point of the period in the past, default is now. E.g. 10m, 2h, 1w: m-minutes, h-hours, d-days, w-weeks + // description: End time-point of the period in the past, default is now. Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks // type: string // required: false - // example: "10h" // - name: ignorezero // in: query // description: Ignore metrics with zero value if true, default is false diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index b0a893bb..92686d1c 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -4881,15 +4881,46 @@ "application" ], "summary": "Gets used resources for the application", - "operationId": "resources", + "operationId": "getResources", "parameters": [ { "type": "string", - "description": "Name of application", + "description": "Name of the application", "name": "appName", "in": "path", "required": true }, + { + "type": "string", + "description": "Name of the application environment", + "name": "environment", + "in": "query" + }, + { + "type": "string", + "description": "Name of the application component in an environment", + "name": "component", + "in": "query" + }, + { + "type": "string", + "description": "Duration of the period, default is 30d (30 days). Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks", + "name": "duration", + "in": "query" + }, + { + "type": "string", + "description": "End time-point of the period in the past, default is now. Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks", + "name": "since", + "in": "query" + }, + { + "type": "string", + "format": "boolean", + "description": "Ignore metrics with zero value if true, default is false", + "name": "ignorezero", + "in": "query" + }, { "type": "string", "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", From 0601fffa8b5caee55cb1e108993a5285d5241e45 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 13 Sep 2024 13:17:42 +0200 Subject: [PATCH 11/32] Updated queries --- api/utils/prometheus/prometheus.go | 34 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index 03ec0cd1..8f88bac7 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -35,11 +35,11 @@ const ( // GetUsedResources Get used resources for the application func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { - durationValue, err := parseQueryDuration(duration, defaultDuration) + durationValue, duration, err := parseQueryDuration(duration, defaultDuration) if err != nil { return nil, err } - sinceValue, err := parseQueryDuration(since, "") + sinceValue, since, err := parseQueryDuration(since, "") if err != nil { return nil, err } @@ -95,12 +95,15 @@ func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, return results, nil } -func parseQueryDuration(duration string, defaultValue string) (time.Duration, error) { +func parseQueryDuration(duration string, defaultValue string) (time.Duration, string, error) { if len(duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(duration) { duration = defaultValue } + if len(duration) == 0 { + return 0, duration, nil + } parsedDuration, err := prometheusModel.ParseDuration(duration) - return time.Duration(parsedDuration), err + return time.Duration(parsedDuration), duration, err } func roundActualValue(num float64) float64 { @@ -171,21 +174,20 @@ func getMetricsValue(ctx context.Context, queryResults map[queryName]prometheusM } func getPrometheusQueries(appName, envName, componentName, duration, since string) map[queryName]string { - if duration == "" { - duration = "30d" - } environmentFilter := radixutils.TernaryString(envName == "", fmt.Sprintf(`,namespace=~"%s-.*"`, appName), fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) - cpuUsageRateQuery := fmt.Sprintf(`rate(container_cpu_usage_seconds_total{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:%s]`, appName, environmentFilter, componentFilter, duration, since) - memoryUsageRateQuery := fmt.Sprintf(`rate(container_memory_usage_bytes{namespace!="%s-app"%s%s}[5m])) by (namespace,container)[%s:%s]`, appName, environmentFilter, componentFilter, duration, since) - return map[queryName]string{ - cpuMax: fmt.Sprintf("max_over_time(sum(%s)", cpuUsageRateQuery), - cpuMin: fmt.Sprintf("min_over_time(sum(%s)", cpuUsageRateQuery), - cpuAvg: fmt.Sprintf("avg_over_time(sum(%s)", cpuUsageRateQuery), - memoryMax: fmt.Sprintf("max_over_time(sum(%s)", memoryUsageRateQuery), - memoryMin: fmt.Sprintf("min_over_time(sum(%s)", memoryUsageRateQuery), - memoryAvg: fmt.Sprintf("avg_over_time(sum(%s)", memoryUsageRateQuery), + offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) + cpuUsageQuery := fmt.Sprintf(`sum(rate(container_cpu_usage_seconds_total{namespace!="%s-app" %s %s}[5m] %s )) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, offsetFilter, duration) + memoryUsageQuery := fmt.Sprintf(`sum(rate(container_memory_usage_bytes{namespace!="%s-app" %s %s}[5m] %s )) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, offsetFilter, duration) + queries := map[queryName]string{ + cpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), + cpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), + cpuAvg: fmt.Sprintf("avg_over_time(%s)", cpuUsageQuery), + memoryMax: fmt.Sprintf("max_over_time(%s)", memoryUsageQuery), + memoryMin: fmt.Sprintf("min_over_time(%s)", memoryUsageQuery), + memoryAvg: fmt.Sprintf("avg_over_time(%s)", memoryUsageQuery), } + return queries } From 292abae4acbd654a6f52b907f808d875d8f0de31 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 13 Sep 2024 13:22:34 +0200 Subject: [PATCH 12/32] Cleanup --- api/utils/prometheus/prometheus.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index 8f88bac7..d0b4ba70 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -35,11 +35,11 @@ const ( // GetUsedResources Get used resources for the application func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { - durationValue, duration, err := parseQueryDuration(duration, defaultDuration) + durationValue, err := parseQueryDuration(&duration, defaultDuration) if err != nil { return nil, err } - sinceValue, since, err := parseQueryDuration(since, "") + sinceValue, err := parseQueryDuration(&since, "") if err != nil { return nil, err } @@ -95,15 +95,15 @@ func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, return results, nil } -func parseQueryDuration(duration string, defaultValue string) (time.Duration, string, error) { - if len(duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(duration) { - duration = defaultValue +func parseQueryDuration(duration *string, defaultValue string) (time.Duration, error) { + if duration == nil || len(*duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(*duration) { + duration = pointers.Ptr(defaultValue) } - if len(duration) == 0 { - return 0, duration, nil + if duration == nil || len(*duration) == 0 { + return 0, nil } - parsedDuration, err := prometheusModel.ParseDuration(duration) - return time.Duration(parsedDuration), duration, err + parsedDuration, err := prometheusModel.ParseDuration(*duration) + return time.Duration(parsedDuration), err } func roundActualValue(num float64) float64 { From 5cb54d64f65a68a8fe16468886e8c27d2219b3b2 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 13 Sep 2024 13:53:20 +0200 Subject: [PATCH 13/32] Cleanup --- .vscode/launch.json | 1 + README.md | 5 +++ api/applications/applications_handler.go | 4 +- .../applications_handler_config.go | 2 + api/applications/models/used_resources.go | 5 +++ api/utils/prometheus/prometheus.go | 40 +++++++++++-------- radixconfig.yaml | 1 + 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 63fdd4e0..7528b136 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,7 @@ "REQUIRE_APP_CONFIGURATION_ITEM": "true", "REQUIRE_APP_AD_GROUPS": "true", "RADIX_ENVIRONMENT":"qa", + "RADIX_PROMETHEUS_URL":"http://localhost:9091", "RADIX_APP":"radix-api", "LOG_LEVEL":"info", "LOG_PRETTY":"true" diff --git a/README.md b/README.md index 210e279a..01f5140a 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ The following env vars are needed. Useful default values in brackets. - `RADIX_CONTAINER_REGISTRY` - (`radixdev.azurecr.io`) - `PIPELINE_IMG_TAG` - (`master-latest`) - `TEKTON_IMG_TAG` - (`release-latest`) +- `RADIX_PROMETHEUS_URL` - `http://localhost:9091` use this to get Prometheus metrics running the following command (the local port 9090 is used by the API server `/metrics` endpoint, in-cluster URL is http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090): + ``` + kubectl -n monitor port-forward svc/prometheus-operator-prometheus 9091:9090 + ``` You also probably want to start with the argument `--useOutClusterClient=false`. When `useOutClusterClient` is `false`, several debugging settings are enabled: * a service principal with superpowers is used to authorize the requests, and the client's `Authorization` bearer token is ignored. @@ -52,6 +56,7 @@ You also probably want to start with the argument `--useOutClusterClient=false`. * the server CORS settings are modified to accept the `X-Requested-With` header in incoming requests. This is necessary to allow direct requests from web browser while e.g. debugging [radix-web-console](https://github.com/equinor/radix-web-console). * verbose debugging output from CORS rule evaluation is logged to console. + If you are using VSCode, there is a convenient launch configuration in `.vscode`. #### Validate code diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index abb1b2ba..0e947a2f 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -462,9 +462,7 @@ func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, env if err != nil { return nil, err } - prometheusUrl := "http://localhost:9091" - // prometheusUrl := "http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090" - return prometheusUtils.GetUsedResources(ctx, prometheusUrl, appName, envName, componentName, duration, since, ignoreZero) + return prometheusUtils.GetUsedResources(ctx, ah.config.PrometheusUrl, appName, envName, componentName, duration, since, ignoreZero) } func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { diff --git a/api/applications/applications_handler_config.go b/api/applications/applications_handler_config.go index 87c749a2..e8777ee5 100644 --- a/api/applications/applications_handler_config.go +++ b/api/applications/applications_handler_config.go @@ -32,6 +32,7 @@ type ApplicationHandlerConfig struct { AppName string `cfg:"radix_app" flag:"radix-app"` EnvironmentName string `cfg:"radix_environment" flag:"radix-environment"` DNSZone string `cfg:"radix_dns_zone" flag:"radix-dns-zone"` + PrometheusUrl string `cfg:"radix_prometheus_url" flag:"radix-prometheus-url"` } func ApplicationHandlerConfigFlagSet() *pflag.FlagSet { @@ -42,5 +43,6 @@ func ApplicationHandlerConfigFlagSet() *pflag.FlagSet { flagset.String("radix-app", "", "Application name") flagset.String("radix-environment", "", "Environment name") flagset.String("radix-dns-zone", "", "Radix DNS zone") + flagset.String("radix-prometheus-url", "", "Prometheus URL") return flagset } diff --git a/api/applications/models/used_resources.go b/api/applications/models/used_resources.go index 7d2d4009..2a19e560 100644 --- a/api/applications/models/used_resources.go +++ b/api/applications/models/used_resources.go @@ -26,6 +26,11 @@ type UsedResources struct { // required: false // example: 120m Memory *UsedResource `json:"memory,omitempty"` + + // Warning messages + // + // required: false + Warnings []string `json:"warnings,omitempty"` } // UsedResource holds information about used resource diff --git a/api/utils/prometheus/prometheus.go b/api/utils/prometheus/prometheus.go index d0b4ba70..2eee12c0 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/utils/prometheus/prometheus.go @@ -12,6 +12,7 @@ import ( "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/pkg/errors" prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" @@ -35,11 +36,11 @@ const ( // GetUsedResources Get used resources for the application func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { - durationValue, err := parseQueryDuration(&duration, defaultDuration) + durationValue, duration, err := parseQueryDuration(duration, defaultDuration) if err != nil { return nil, err } - sinceValue, err := parseQueryDuration(&since, "") + sinceValue, since, err := parseQueryDuration(since, "") if err != nil { return nil, err } @@ -47,11 +48,12 @@ func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, comp defer cancel() log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) - results, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, duration, since) + results, warnings, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, duration, since) if err != nil { return nil, err } resources := getUsedResourcesByMetrics(ctx, results, durationValue, sinceValue, ignoreZero) + resources.Warnings = warnings log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) return resources, nil } @@ -74,36 +76,40 @@ func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]promet } } -func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string) (map[queryName]prometheusModel.Value, error) { +func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string) (map[queryName]prometheusModel.Value, []string, error) { client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) if err != nil { - return nil, fmt.Errorf("failed to create the Prometheus client: %w", err) + log.Ctx(ctx).Error().Msgf("failed to create the Prometheus client: %v", err) + return nil, nil, errors.New("Failed to create the Prometheus client") } api := prometheusV1.NewAPI(client) results := make(map[queryName]model.Value) now := time.Now() + var warnings []string for metricName, query := range getPrometheusQueries(appName, envName, componentName, duration, since) { - result, warnings, err := api.Query(ctx, query, now) + result, resultWarnings, err := api.Query(ctx, query, now) if err != nil { - return nil, fmt.Errorf("failed to get Prometheus metrics: %w", err) + log.Ctx(ctx).Error().Msgf("Failed to get Prometheus metrics: %v", err) + return nil, nil, errors.New("failed to get Prometheus metrics") } - if len(warnings) > 0 { - log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", warnings) + if len(resultWarnings) > 0 { + log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", resultWarnings) + warnings = append(warnings, resultWarnings...) } results[metricName] = result } - return results, nil + return results, warnings, nil } -func parseQueryDuration(duration *string, defaultValue string) (time.Duration, error) { - if duration == nil || len(*duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(*duration) { - duration = pointers.Ptr(defaultValue) +func parseQueryDuration(duration string, defaultValue string) (time.Duration, string, error) { + if len(duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(duration) { + duration = defaultValue } - if duration == nil || len(*duration) == 0 { - return 0, nil + if len(duration) == 0 { + return 0, duration, nil } - parsedDuration, err := prometheusModel.ParseDuration(*duration) - return time.Duration(parsedDuration), err + parsedDuration, err := prometheusModel.ParseDuration(duration) + return time.Duration(parsedDuration), duration, err } func roundActualValue(num float64) float64 { diff --git a/radixconfig.yaml b/radixconfig.yaml index b6a37fa1..f030f810 100644 --- a/radixconfig.yaml +++ b/radixconfig.yaml @@ -34,6 +34,7 @@ spec: USE_PROFILER: "false" LOG_LEVEL: info LOG_PRETTY: "false" + RADIX_PROMETHEUS_URL: http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090 environmentConfig: - environment: qa horizontalScaling: From eacca9a32c3bb9f780c5af70312014c2a7e7f4bf Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 13 Sep 2024 16:00:14 +0200 Subject: [PATCH 14/32] Extracted prometheus handler --- Makefile | 2 + api/applications/applications_controller.go | 11 +- .../applications_controller_test.go | 124 ++++++++++-------- api/applications/applications_handler.go | 10 -- .../applications_handler_factory.go | 1 + api/metrics/mock/prometheus_client_mock.go | 53 ++++++++ api/metrics/mock/prometheus_handler_mock.go | 52 ++++++++ api/metrics/prometheus_client.go | 55 ++++++++ .../prometheus_handler.go} | 88 ++++++------- main.go | 19 ++- swaggerui/html/swagger.json | 8 ++ 11 files changed, 295 insertions(+), 128 deletions(-) create mode 100644 api/metrics/mock/prometheus_client_mock.go create mode 100644 api/metrics/mock/prometheus_handler_mock.go create mode 100644 api/metrics/prometheus_client.go rename api/{utils/prometheus/prometheus.go => metrics/prometheus_handler.go} (71%) diff --git a/Makefile b/Makefile index 147637f8..213a81fe 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,8 @@ build: $(BINS) mocks: bootstrap mockgen -source ./api/buildstatus/models/buildstatus.go -destination ./api/test/mock/buildstatus_mock.go -package mock mockgen -source ./api/deployments/deployment_handler.go -destination ./api/deployments/mock/deployment_handler_mock.go -package mock + mockgen -source ./api/metrics/prometheus_handler.go -destination ./api/metrics/mock/prometheus_handler_mock.go -package mock + mockgen -source ./api/metrics/prometheus_client.go -destination ./api/metrics/mock/prometheus_client_mock.go -package mock mockgen -source ./api/environments/job_handler.go -destination ./api/environments/mock/job_handler_mock.go -package mock mockgen -source ./api/environments/environment_handler.go -destination ./api/environments/mock/environment_handler_mock.go -package mock mockgen -source ./api/utils/tlsvalidation/interface.go -destination ./api/utils/tlsvalidation/mock/tls_secret_validator_mock.go -package mock diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index 055f1ddc..92468fb0 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -8,6 +8,7 @@ import ( "strings" applicationModels "github.com/equinor/radix-api/api/applications/models" + "github.com/equinor/radix-api/api/metrics" "github.com/equinor/radix-api/models" "github.com/gorilla/mux" ) @@ -19,10 +20,11 @@ type applicationController struct { *models.DefaultController hasAccessToRR applicationHandlerFactory ApplicationHandlerFactory + prometheusHandler metrics.PrometheusHandler } // NewApplicationController Constructor -func NewApplicationController(hasAccessTo hasAccessToRR, applicationHandlerFactory ApplicationHandlerFactory) models.Controller { +func NewApplicationController(hasAccessTo hasAccessToRR, applicationHandlerFactory ApplicationHandlerFactory, prometheusHandler metrics.PrometheusHandler) models.Controller { if hasAccessTo == nil { hasAccessTo = hasAccess } @@ -30,6 +32,7 @@ func NewApplicationController(hasAccessTo hasAccessToRR, applicationHandlerFacto return &applicationController{ hasAccessToRR: hasAccessTo, applicationHandlerFactory: applicationHandlerFactory, + prometheusHandler: prometheusHandler, } } @@ -1063,13 +1066,11 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht ignoreZero, _ = strconv.ParseBool(ignoreZeroArg) } - handler := ac.applicationHandlerFactory.Create(accounts) - jobSummary, err := handler.GetUsedResources(r.Context(), appName, envName, componentName, duration, since, ignoreZero) - + usedResources, err := ac.prometheusHandler.GetUsedResources(r.Context(), accounts.UserAccount.RadixClient, appName, envName, componentName, duration, since, ignoreZero) if err != nil { ac.ErrorResponse(w, r, err) return } - ac.JSONResponse(w, r, &jobSummary) + ac.JSONResponse(w, r, &usedResources) } diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 4ca72971..c0dc0098 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -16,6 +16,7 @@ import ( applicationModels "github.com/equinor/radix-api/api/applications/models" environmentModels "github.com/equinor/radix-api/api/environments/models" jobModels "github.com/equinor/radix-api/api/jobs/models" + "github.com/equinor/radix-api/api/metrics/mock" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/models" @@ -32,6 +33,7 @@ import ( commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/golang/mock/gomock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" "github.com/stretchr/testify/assert" @@ -72,6 +74,7 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) require.NoError(t, err) _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) // controllerTestUtils is used for issuing HTTP request and processing responses controllerTestUtils := controllertest.NewTestUtils( @@ -80,17 +83,25 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory kedaClient, secretproviderclient, certClient, - NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, - handlerFactory, - ), + NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, handlerFactory, prometheusHandlerMock), ) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient } +func createPrometheusHandlerMock(t *testing.T, radixclient *fake.Clientset, mockHandler *func(handler *mock.MockPrometheusHandler)) *mock.MockPrometheusHandler { + ctrl := gomock.NewController(t) + mockPrometheusHandler := mock.NewMockPrometheusHandler(ctrl) + if mockHandler != nil { + (*mockHandler)(mockPrometheusHandler) + } else { + mockPrometheusHandler.EXPECT().GetUsedResources(context.Background(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) + } + return mockPrometheusHandler +} + func TestGetApplications_HasAccessToSomeRR(t *testing.T) { commonTestUtils, _, kubeclient, radixclient, kedaClient, _, secretproviderclient, certClient := setupTest(t, true, true) @@ -102,19 +113,19 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { require.NoError(t, err) t.Run("no access", func(t *testing.T) { + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) controllerTestUtils := controllertest.NewTestUtils( kubeclient, radixclient, kedaClient, secretproviderclient, certClient, - NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return false, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return false, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -125,13 +136,13 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { }) t.Run("access to single app", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, rr v1.RadixRegistration) (bool, error) { - return rr.GetName() == "my-second-app", nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, rr v1.RadixRegistration) (bool, error) { + return rr.GetName() == "my-second-app", nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -142,13 +153,13 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { }) t.Run("access to all app", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + }), prometheusHandlerMock)) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -177,7 +188,7 @@ func TestGetApplications_WithFilterOnSSHRepo_Filter(t *testing.T) { assert.Equal(t, 1, len(applications)) }) - t.Run("unmatching repo", func(t *testing.T) { + t.Run("not matching repo", func(t *testing.T) { responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications?sshRepo=%s", url.QueryEscape("git@github.com:Equinor/my-app2.git"))) response := <-responseChannel @@ -223,13 +234,13 @@ func TestSearchApplicationsPost(t *testing.T) { ) require.NoError(t, err) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + }), prometheusHandlerMock)) // Tests t.Run("search for "+appNames[0], func(t *testing.T) { @@ -305,13 +316,13 @@ func TestSearchApplicationsPost(t *testing.T) { }) t.Run("search for "+appNames[0]+" - no access", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return false, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return false, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) params := applicationModels.ApplicationsSearchRequest{Names: []string{appNames[0]}} responseChannel := controllerTestUtils.ExecuteRequestWithParameters("POST", "/api/v1/applications/_search", ¶ms) response := <-responseChannel @@ -401,13 +412,13 @@ func TestSearchApplicationsGet(t *testing.T) { ) require.NoError(t, err) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + }), prometheusHandlerMock)) // Tests t.Run("search for "+appNames[0], func(t *testing.T) { @@ -473,13 +484,13 @@ func TestSearchApplicationsGet(t *testing.T) { }) t.Run("search for "+appNames[0]+" - no access", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return false, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }))) + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return false, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) params := "apps=" + appNames[0] responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications/_search?"+params) response := <-responseChannel @@ -1627,7 +1638,6 @@ func TestHandleTriggerPipeline_Promote_JobHasCorrectParameters(t *testing.T) { const ( appName = "an-app" commitId = "475f241c-478b-49da-adfb-3c336aaab8d2" - deploymentName = "a-deployment" fromEnvironment = "origin" toEnvironment = "target" ) @@ -1747,8 +1757,8 @@ func TestDeleteApplication_ApplicationIsDeleted(t *testing.T) { func TestGetApplication_WithAppAlias_ContainsAppAlias(t *testing.T) { // Setup - commonTestUtils, controllerTestUtils, client, radixclient, kedaClient, promclient, secretproviderclient, certClient := setupTest(t, true, true) - err := utils.ApplyDeploymentWithSync(client, radixclient, kedaClient, promclient, commonTestUtils, secretproviderclient, certClient, builders.ARadixDeployment(). + commonTestUtils, controllerTestUtils, client, radixclient, kedaClient, promClient, secretproviderclient, certClient := setupTest(t, true, true) + err := utils.ApplyDeploymentWithSync(client, radixclient, kedaClient, promClient, commonTestUtils, secretproviderclient, certClient, builders.ARadixDeployment(). WithAppName("any-app"). WithEnvironment("prod"). WithComponents( @@ -1775,7 +1785,7 @@ func TestGetApplication_WithAppAlias_ContainsAppAlias(t *testing.T) { assert.Equal(t, fmt.Sprintf("%s.%s", "any-app", appAliasDNSZone), application.AppAlias.URL) } -func TestListPipeline_ReturnesAvailablePipelines(t *testing.T) { +func TestListPipeline_ReturnsAvailablePipelines(t *testing.T) { supportedPipelines := jobPipeline.GetSupportedPipelines() // Setup diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index 0e947a2f..a983d179 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -17,7 +17,6 @@ import ( jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/kubequery" apimodels "github.com/equinor/radix-api/api/models" - prometheusUtils "github.com/equinor/radix-api/api/utils/prometheus" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" @@ -456,15 +455,6 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam return jobSummary, nil } -// GetUsedResources Returns the used resources for an application -func (ah *ApplicationHandler) GetUsedResources(ctx context.Context, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { - _, err := ah.getUserAccount().RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return prometheusUtils.GetUsedResources(ctx, ah.config.PrometheusUrl, appName, envName, componentName, duration, since, ignoreZero) -} - func (ah *ApplicationHandler) getRadixDeploymentForPromotePipeline(ctx context.Context, appName string, envName, deploymentName string) (*v1.RadixDeployment, error) { radixDeployment, err := kubequery.GetRadixDeploymentByName(ctx, ah.accounts.UserAccount.RadixClient, appName, envName, deploymentName) if err == nil { diff --git a/api/applications/applications_handler_factory.go b/api/applications/applications_handler_factory.go index d54a24a9..711e61f9 100644 --- a/api/applications/applications_handler_factory.go +++ b/api/applications/applications_handler_factory.go @@ -2,6 +2,7 @@ package applications import ( "context" + "github.com/equinor/radix-api/api/utils/access" "github.com/equinor/radix-api/models" authorizationapi "k8s.io/api/authorization/v1" diff --git a/api/metrics/mock/prometheus_client_mock.go b/api/metrics/mock/prometheus_client_mock.go new file mode 100644 index 00000000..b8880f2c --- /dev/null +++ b/api/metrics/mock/prometheus_client_mock.go @@ -0,0 +1,53 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./api/metrics/prometheus_client.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + metrics "github.com/equinor/radix-api/api/metrics" + gomock "github.com/golang/mock/gomock" + model "github.com/prometheus/common/model" +) + +// MockPrometheusClient is a mock of PrometheusClient interface. +type MockPrometheusClient struct { + ctrl *gomock.Controller + recorder *MockPrometheusClientMockRecorder +} + +// MockPrometheusClientMockRecorder is the mock recorder for MockPrometheusClient. +type MockPrometheusClientMockRecorder struct { + mock *MockPrometheusClient +} + +// NewMockPrometheusClient creates a new mock instance. +func NewMockPrometheusClient(ctrl *gomock.Controller) *MockPrometheusClient { + mock := &MockPrometheusClient{ctrl: ctrl} + mock.recorder = &MockPrometheusClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPrometheusClient) EXPECT() *MockPrometheusClientMockRecorder { + return m.recorder +} + +// GetMetrics mocks base method. +func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[metrics.QueryName]model.Value, []string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMetrics", ctx, appName, envName, componentName, duration, since) + ret0, _ := ret[0].(map[metrics.QueryName]model.Value) + ret1, _ := ret[1].([]string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetMetrics indicates an expected call of GetMetrics. +func (mr *MockPrometheusClientMockRecorder) GetMetrics(ctx, appName, envName, componentName, duration, since interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetrics", reflect.TypeOf((*MockPrometheusClient)(nil).GetMetrics), ctx, appName, envName, componentName, duration, since) +} diff --git a/api/metrics/mock/prometheus_handler_mock.go b/api/metrics/mock/prometheus_handler_mock.go new file mode 100644 index 00000000..a6183aa3 --- /dev/null +++ b/api/metrics/mock/prometheus_handler_mock.go @@ -0,0 +1,52 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./api/metrics/prometheus_handler.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + models "github.com/equinor/radix-api/api/applications/models" + versioned "github.com/equinor/radix-operator/pkg/client/clientset/versioned" + gomock "github.com/golang/mock/gomock" +) + +// MockPrometheusHandler is a mock of PrometheusHandler interface. +type MockPrometheusHandler struct { + ctrl *gomock.Controller + recorder *MockPrometheusHandlerMockRecorder +} + +// MockPrometheusHandlerMockRecorder is the mock recorder for MockPrometheusHandler. +type MockPrometheusHandlerMockRecorder struct { + mock *MockPrometheusHandler +} + +// NewMockPrometheusHandler creates a new mock instance. +func NewMockPrometheusHandler(ctrl *gomock.Controller) *MockPrometheusHandler { + mock := &MockPrometheusHandler{ctrl: ctrl} + mock.recorder = &MockPrometheusHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPrometheusHandler) EXPECT() *MockPrometheusHandlerMockRecorder { + return m.recorder +} + +// GetUsedResources mocks base method. +func (m *MockPrometheusHandler) GetUsedResources(ctx context.Context, radixClient versioned.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*models.UsedResources, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsedResources", ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) + ret0, _ := ret[0].(*models.UsedResources) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUsedResources indicates an expected call of GetUsedResources. +func (mr *MockPrometheusHandlerMockRecorder) GetUsedResources(ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsedResources", reflect.TypeOf((*MockPrometheusHandler)(nil).GetUsedResources), ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) +} diff --git a/api/metrics/prometheus_client.go b/api/metrics/prometheus_client.go new file mode 100644 index 00000000..ad8e07a8 --- /dev/null +++ b/api/metrics/prometheus_client.go @@ -0,0 +1,55 @@ +package metrics + +import ( + "context" + "errors" + "time" + + prometheusApi "github.com/prometheus/client_golang/api" + prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/prometheus/common/model" + prometheusModel "github.com/prometheus/common/model" + "github.com/rs/zerolog/log" +) + +// PrometheusClient Interface for Prometheus client +type PrometheusClient interface { + // GetMetrics Get metrics for the application + GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[QueryName]prometheusModel.Value, []string, error) +} + +// NewPrometheusClient Constructor for Prometheus client +func NewPrometheusClient(prometheusUrl string) (PrometheusClient, error) { + apiClient, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) + if err != nil { + return nil, errors.New("failed to create the Prometheus API client") + } + api := prometheusV1.NewAPI(apiClient) + return &client{ + api: api, + }, nil +} + +type client struct { + api prometheusV1.API +} + +// GetMetrics Get metrics for the application +func (c *client) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[QueryName]prometheusModel.Value, []string, error) { + results := make(map[QueryName]model.Value) + now := time.Now() + var warnings []string + for metricName, query := range getPrometheusQueries(appName, envName, componentName, duration, since) { + result, resultWarnings, err := c.api.Query(ctx, query, now) + if err != nil { + log.Ctx(ctx).Error().Msgf("Failed to get Prometheus metrics: %v", err) + return nil, nil, errors.New("failed to get Prometheus metrics") + } + if len(resultWarnings) > 0 { + log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", resultWarnings) + warnings = append(warnings, resultWarnings...) + } + results[metricName] = result + } + return results, warnings, nil +} diff --git a/api/utils/prometheus/prometheus.go b/api/metrics/prometheus_handler.go similarity index 71% rename from api/utils/prometheus/prometheus.go rename to api/metrics/prometheus_handler.go index 2eee12c0..5c1ac8d8 100644 --- a/api/utils/prometheus/prometheus.go +++ b/api/metrics/prometheus_handler.go @@ -1,4 +1,4 @@ -package prometheus +package metrics import ( "context" @@ -12,43 +12,64 @@ import ( "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/utils" - "github.com/pkg/errors" - prometheusApi "github.com/prometheus/client_golang/api" - prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" + radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "github.com/prometheus/common/model" prometheusModel "github.com/prometheus/common/model" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type queryName string +// QueryName Prometheus query name +type QueryName string const ( - cpuMax queryName = "cpuMax" - cpuMin queryName = "cpuMin" - cpuAvg queryName = "cpuAvg" - memoryMax queryName = "memoryMax" - memoryMin queryName = "memoryMin" - memoryAvg queryName = "memoryAvg" + cpuMax QueryName = "cpuMax" + cpuMin QueryName = "cpuMin" + cpuAvg QueryName = "cpuAvg" + memoryMax QueryName = "memoryMax" + memoryMin QueryName = "memoryMin" + memoryAvg QueryName = "memoryAvg" durationExpression = `^[0-9]{1,5}[mhdw]$` defaultDuration = "30d" + defaultOffset = "0s" ) +// PrometheusHandler Interface for Prometheus handler +type PrometheusHandler interface { + GetUsedResources(ctx context.Context, radixClient radixclient.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) +} + +type handler struct { + client PrometheusClient +} + +// NewPrometheusHandler Constructor for Prometheus handler +func NewPrometheusHandler(client PrometheusClient) PrometheusHandler { + return &handler{ + client: client, + } +} + // GetUsedResources Get used resources for the application -func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { +func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { + _, err := radixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) + if err != nil { + return nil, err + } durationValue, duration, err := parseQueryDuration(duration, defaultDuration) if err != nil { return nil, err } - sinceValue, since, err := parseQueryDuration(since, "") + sinceValue, since, err := parseQueryDuration(since, defaultOffset) if err != nil { return nil, err } - ctx, cancel := context.WithTimeout(ctx, time.Minute) // replace with 10*time.Seconds + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) - results, warnings, err := getPrometheusMetrics(ctx, prometheusUrl, appName, envName, componentName, duration, since) + results, warnings, err := pc.client.GetMetrics(ctx, appName, envName, componentName, duration, since) if err != nil { return nil, err } @@ -58,7 +79,7 @@ func GetUsedResources(ctx context.Context, prometheusUrl, appName, envName, comp return resources, nil } -func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration, ignoreZero bool) *applicationModels.UsedResources { +func getUsedResourcesByMetrics(ctx context.Context, results map[QueryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration, ignoreZero bool) *applicationModels.UsedResources { usedCpuResource := applicationModels.UsedResource{} usedCpuResource.Min, usedCpuResource.MinActual = getCpuMetricValue(ctx, results, cpuMin, ignoreZero) usedCpuResource.Max, usedCpuResource.MaxActual = getCpuMetricValue(ctx, results, cpuMax, ignoreZero) @@ -76,31 +97,6 @@ func getUsedResourcesByMetrics(ctx context.Context, results map[queryName]promet } } -func getPrometheusMetrics(ctx context.Context, prometheusUrl, appName, envName, componentName, duration, since string) (map[queryName]prometheusModel.Value, []string, error) { - client, err := prometheusApi.NewClient(prometheusApi.Config{Address: prometheusUrl}) - if err != nil { - log.Ctx(ctx).Error().Msgf("failed to create the Prometheus client: %v", err) - return nil, nil, errors.New("Failed to create the Prometheus client") - } - api := prometheusV1.NewAPI(client) - results := make(map[queryName]model.Value) - now := time.Now() - var warnings []string - for metricName, query := range getPrometheusQueries(appName, envName, componentName, duration, since) { - result, resultWarnings, err := api.Query(ctx, query, now) - if err != nil { - log.Ctx(ctx).Error().Msgf("Failed to get Prometheus metrics: %v", err) - return nil, nil, errors.New("failed to get Prometheus metrics") - } - if len(resultWarnings) > 0 { - log.Ctx(ctx).Warn().Msgf("Warnings: %v\n", resultWarnings) - warnings = append(warnings, resultWarnings...) - } - results[metricName] = result - } - return results, warnings, nil -} - func parseQueryDuration(duration string, defaultValue string) (time.Duration, string, error) { if len(duration) == 0 || !regexp.MustCompile(durationExpression).MatchString(duration) { duration = defaultValue @@ -116,7 +112,7 @@ func roundActualValue(num float64) float64 { return math.Round(num*1e6) / 1e6 } -func getCpuMetricValue(ctx context.Context, queryResults map[queryName]prometheusModel.Value, queryName queryName, ignoreZero bool) (string, *float64) { +func getCpuMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (string, *float64) { if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { valueInMillicores := value * 1000.0 quantity := resource.NewMilliQuantity(int64(valueInMillicores), resource.BinarySI) @@ -125,7 +121,7 @@ func getCpuMetricValue(ctx context.Context, queryResults map[queryName]prometheu return "", nil } -func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]prometheusModel.Value, queryName queryName, ignoreZero bool) (string, *float64) { +func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (string, *float64) { if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { valueInMegabytes := value / 1000.0 quantity := resource.NewScaledQuantity(int64(valueInMegabytes), resource.Mega) @@ -134,7 +130,7 @@ func getMemoryMetricValue(ctx context.Context, queryResults map[queryName]promet return "", nil } -func getMetricsValue(ctx context.Context, queryResults map[queryName]prometheusModel.Value, queryName queryName, ignoreZero bool) (float64, bool) { +func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { return 0, false @@ -179,7 +175,7 @@ func getMetricsValue(ctx context.Context, queryResults map[queryName]prometheusM return 0, false } -func getPrometheusQueries(appName, envName, componentName, duration, since string) map[queryName]string { +func getPrometheusQueries(appName, envName, componentName, duration, since string) map[QueryName]string { environmentFilter := radixutils.TernaryString(envName == "", fmt.Sprintf(`,namespace=~"%s-.*"`, appName), fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) @@ -187,7 +183,7 @@ func getPrometheusQueries(appName, envName, componentName, duration, since strin offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) cpuUsageQuery := fmt.Sprintf(`sum(rate(container_cpu_usage_seconds_total{namespace!="%s-app" %s %s}[5m] %s )) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, offsetFilter, duration) memoryUsageQuery := fmt.Sprintf(`sum(rate(container_memory_usage_bytes{namespace!="%s-app" %s %s}[5m] %s )) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, offsetFilter, duration) - queries := map[queryName]string{ + queries := map[QueryName]string{ cpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), cpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), cpuAvg: fmt.Sprintf("avg_over_time(%s)", cpuUsageQuery), diff --git a/main.go b/main.go index acca65de..e326148c 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + "github.com/equinor/radix-api/api/metrics" "github.com/equinor/radix-api/api/secrets" "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-operator/pkg/apis/defaults" @@ -168,13 +169,19 @@ func setupLogger() { func getControllers() ([]models.Controller, error) { buildStatus := build_models.NewPipelineBadge() - applicationHandlerFactory, err := getApplicationHandlerFactory() + cfg, err := applications.LoadApplicationHandlerConfig(os.Args[1:]) if err != nil { return nil, err } + prometheusClient, err := metrics.NewPrometheusClient(cfg.PrometheusUrl) + if err != nil { + return nil, err + } + prometheusHandler := metrics.NewPrometheusHandler(prometheusClient) + applicationHandlerFactory := applications.NewApplicationHandlerFactory(cfg) return []models.Controller{ - applications.NewApplicationController(nil, applicationHandlerFactory), + applications.NewApplicationController(nil, applicationHandlerFactory, prometheusHandler), deployments.NewDeploymentController(), jobs.NewJobController(), environments.NewEnvironmentController(environments.NewEnvironmentHandlerFactory()), @@ -187,14 +194,6 @@ func getControllers() ([]models.Controller, error) { }, nil } -func getApplicationHandlerFactory() (applications.ApplicationHandlerFactory, error) { - cfg, err := applications.LoadApplicationHandlerConfig(os.Args[1:]) - if err != nil { - return nil, err - } - return applications.NewApplicationHandlerFactory(cfg), nil -} - func initializeFlagSet() *pflag.FlagSet { // Flag domain. fs := pflag.NewFlagSet("default", pflag.ContinueOnError) diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 92686d1c..c1d7e2f5 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -8143,6 +8143,14 @@ "type": "string", "x-go-name": "To", "example": "2006-01-02T15:04:05Z" + }, + "warnings": { + "description": "Warning messages", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Warnings" } }, "x-go-package": "github.com/equinor/radix-api/api/applications/models" From ee63d27a14886483fff3ae8270a853dc90a7fc28 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 16 Sep 2024 15:35:34 +0200 Subject: [PATCH 15/32] Adding unit-tests --- .../applications_controller_test.go | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index c0dc0098..bff08f76 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -97,7 +97,7 @@ func createPrometheusHandlerMock(t *testing.T, radixclient *fake.Clientset, mock if mockHandler != nil { (*mockHandler)(mockPrometheusHandler) } else { - mockPrometheusHandler.EXPECT().GetUsedResources(context.Background(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) + mockPrometheusHandler.EXPECT().GetUsedResources(gomock.Any(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) } return mockPrometheusHandler } @@ -1914,6 +1914,61 @@ func TestRegenerateDeployKey_InvalidKeyInParam_ErrorIsReturned(t *testing.T) { assert.Equal(t, http.StatusBadRequest, response.Code) } +func Test_GetUsedResources(t *testing.T) { + const ( + appName1 = "app-1" + envName1 = "prod" + componentName1 = "component1" + ) + + type scenario struct { + name string + expectedUsedResources *applicationModels.UsedResources + expectedError error + } + + scenarios := []scenario{ + { + name: "Get used resources for an application with no components", + expectedUsedResources: &applicationModels.UsedResources{}, + }, + { + name: "Get used resources for an application with one component", + expectedUsedResources: &applicationModels.UsedResources{}, + }, + } + + for _, ts := range scenarios { + t.Run(ts.name, func(t *testing.T) { + commonTestUtils, _, kubeclient, radixclient, kedaClient, _, secretproviderclient, certClient := setupTest(t, true, true) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) + require.NoError(t, err) + + prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) + + responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/resources", appName1)) + response := <-responseChannel + if ts.expectedError != nil { + assert.Equal(t, http.StatusBadRequest, response.Code) + errorResponse, _ := controllertest.GetErrorResponse(response) + assert.Equal(t, ts.expectedError.Error(), errorResponse.Message) + return + } + assert.Equal(t, http.StatusOK, response.Code) + actualUsedResources := &applicationModels.UsedResources{} + err = controllertest.GetResponseBody(response, &actualUsedResources) + require.NoError(t, err) + assert.Equal(t, ts.expectedUsedResources, actualUsedResources) + }) + } +} + func createRadixJob(commonTestUtils *commontest.Utils, appName, jobName string, started time.Time) error { _, err := commonTestUtils.ApplyJob( builders.ARadixBuildDeployJob(). From 439677f8a672f65f4f6e42d2e612c1e8ed8a3a0a Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 16 Sep 2024 16:20:20 +0200 Subject: [PATCH 16/32] Adding unit-tests --- .../applications_controller_test.go | 87 ++++++++++++++++--- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index bff08f76..8b74d8c4 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" "net/url" @@ -1921,38 +1922,78 @@ func Test_GetUsedResources(t *testing.T) { componentName1 = "component1" ) + type expectedArgs struct { + environment string + component string + duration string + since string + ignoreZero bool + } + type scenario struct { - name string - expectedUsedResources *applicationModels.UsedResources - expectedError error + name string + expectedUsedResources *applicationModels.UsedResources + expectedError error + queryString string + expectedArgs expectedArgs + expectedUsedResourcesError error } scenarios := []scenario{ { - name: "Get used resources for an application with no components", - expectedUsedResources: &applicationModels.UsedResources{}, + name: "Get used resources", + expectedUsedResources: getTestUsedResources(), + expectedArgs: expectedArgs{}, + }, + { + name: "Get used resources with arguments", + queryString: "?environment=prod&component=component1&duration=10d&since=2w&ignorezero=true", + expectedUsedResources: getTestUsedResources(), + expectedArgs: expectedArgs{ + environment: envName1, + component: componentName1, + duration: "10d", + since: "2w", + ignoreZero: true, + }, }, { - name: "Get used resources for an application with one component", - expectedUsedResources: &applicationModels.UsedResources{}, + name: "Invalid boolean query parameter falls back to false", + expectedUsedResources: getTestUsedResources(), + queryString: "?ignorezero=abc", + expectedArgs: expectedArgs{ + ignoreZero: false, + }, + }, + { + name: "UsedResources returns an error", + expectedUsedResources: getTestUsedResources(), + expectedUsedResourcesError: errors.New("error-123"), + expectedError: errors.New("Error: error-123"), }, } for _, ts := range scenarios { t.Run(ts.name, func(t *testing.T) { - commonTestUtils, _, kubeclient, radixclient, kedaClient, _, secretproviderclient, certClient := setupTest(t, true, true) + commonTestUtils, _, kubeClient, radixClient, kedaClient, _, secretProviderClient, certClient := setupTest(t, true, true) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) require.NoError(t, err) - prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + mockHandlerModifier := func(handler *mock.MockPrometheusHandler) { + args := ts.expectedArgs + handler.EXPECT().GetUsedResources(gomock.Any(), radixClient, appName1, args.environment, args.component, args.duration, args.since, args.ignoreZero). + Times(1). + Return(ts.expectedUsedResources, ts.expectedUsedResourcesError) + } + prometheusHandlerMock := createPrometheusHandlerMock(t, radixClient, &mockHandlerModifier) + controllerTestUtils := controllertest.NewTestUtils(kubeClient, radixClient, kedaClient, secretProviderClient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }), prometheusHandlerMock)) - responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/resources", appName1)) + responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/resources%s", appName1, ts.queryString)) response := <-responseChannel if ts.expectedError != nil { assert.Equal(t, http.StatusBadRequest, response.Code) @@ -1969,6 +2010,30 @@ func Test_GetUsedResources(t *testing.T) { } } +func getTestUsedResources() *applicationModels.UsedResources { + return &applicationModels.UsedResources{ + From: radixutils.FormatTimestamp(time.Now().Add(time.Minute * -10)), + To: radixutils.FormatTimestamp(time.Now()), + CPU: &applicationModels.UsedResource{ + Min: "1m", + Max: "10m", + Average: "5m", + MinActual: pointers.Ptr(1.1), + MaxActual: pointers.Ptr(10.12), + AvgActual: pointers.Ptr(5.56), + }, + Memory: &applicationModels.UsedResource{ + Min: "100M", + Max: "1000M", + Average: "500M", + MinActual: pointers.Ptr(100.1), + MaxActual: pointers.Ptr(1000.12), + AvgActual: pointers.Ptr(500.56), + }, + Warnings: []string{"warning1", "warning2"}, + } +} + func createRadixJob(commonTestUtils *commontest.Utils, appName, jobName string, started time.Time) error { _, err := commonTestUtils.ApplyJob( builders.ARadixBuildDeployJob(). From b3482b0596166d2b5c1c15a3763fc4abed00b7b2 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 16 Sep 2024 17:03:54 +0200 Subject: [PATCH 17/32] Adding unit-tests --- .../applications_controller_test.go | 12 ++-- api/metrics/prometheus_handler_test.go | 68 +++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 api/metrics/prometheus_handler_test.go diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 8b74d8c4..3a98bd98 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -33,7 +33,7 @@ import ( "github.com/equinor/radix-operator/pkg/apis/radixvalidators" commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" - "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/golang/mock/gomock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" @@ -52,7 +52,7 @@ const ( subscriptionId = "12347718-c8f8-4995-bfbb-02655ff1f89c" ) -func setupTest(t *testing.T, requireAppConfigurationItem, requireAppADGroups bool) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *fake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { +func setupTest(t *testing.T, requireAppConfigurationItem, requireAppADGroups bool) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { return setupTestWithFactory(t, newTestApplicationHandlerFactory( ApplicationHandlerConfig{RequireAppConfigurationItem: requireAppConfigurationItem, RequireAppADGroups: requireAppADGroups}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { @@ -61,10 +61,10 @@ func setupTest(t *testing.T, requireAppConfigurationItem, requireAppADGroups boo )) } -func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *fake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { +func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { // Setup kubeclient := kubefake.NewSimpleClientset() - radixclient := fake.NewSimpleClientset() + radixclient := radixfake.NewSimpleClientset() kedaClient := kedafake.NewSimpleClientset() prometheusclient := prometheusfake.NewSimpleClientset() secretproviderclient := secretproviderfake.NewSimpleClientset() @@ -92,7 +92,7 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient } -func createPrometheusHandlerMock(t *testing.T, radixclient *fake.Clientset, mockHandler *func(handler *mock.MockPrometheusHandler)) *mock.MockPrometheusHandler { +func createPrometheusHandlerMock(t *testing.T, radixclient *radixfake.Clientset, mockHandler *func(handler *mock.MockPrometheusHandler)) *mock.MockPrometheusHandler { ctrl := gomock.NewController(t) mockPrometheusHandler := mock.NewMockPrometheusHandler(ctrl) if mockHandler != nil { @@ -2054,7 +2054,7 @@ func createRadixJob(commonTestUtils *commontest.Utils, appName, jobName string, return err } -func getJobsInNamespace(radixclient *fake.Clientset, appNamespace string) ([]v1.RadixJob, error) { +func getJobsInNamespace(radixclient *radixfake.Clientset, appNamespace string) ([]v1.RadixJob, error) { jobs, err := radixclient.RadixV1().RadixJobs(appNamespace).List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go new file mode 100644 index 00000000..eb12f0ca --- /dev/null +++ b/api/metrics/prometheus_handler_test.go @@ -0,0 +1,68 @@ +package metrics + +import ( + "context" + "reflect" + "testing" + + applicationModels "github.com/equinor/radix-api/api/applications/models" + "github.com/equinor/radix-api/api/metrics/mock" + commontest "github.com/equinor/radix-operator/pkg/apis/test" + builders "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/golang/mock/gomock" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" +) + +func Test_handler_GetUsedResources(t *testing.T) { + const ( + appName1 = "app1" + ) + + type args struct { + envName string + componentName string + duration string + since string + ignoreZero bool + } + + type scenario struct { + name string + args args + expectedUsedResources *applicationModels.UsedResources + expectedMetrics map[QueryName]model.Value + expectedWarnings []string + expectedError error + } + + scenarios := []scenario{ + { + name: "Test GetUsedResources", args: args{}}, + } + for _, ts := range scenarios { + t.Run(ts.name, func(t *testing.T) { + radixClient := fake.NewSimpleClientset() + commonTestUtils := commontest.NewTestUtils(nil, radixClient, nil, nil) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) + require.NoError(t, err) + ctrl := gomock.NewController(t) + mockPrometheusClient := mock.NewMockPrometheusClient(ctrl) + mockPrometheusClient.EXPECT().GetMetrics(gomock.Any(), appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since). + Return(ts.expectedMetrics, ts.expectedWarnings, ts.expectedError) + + ph := &handler{ + client: mockPrometheusClient, + } + got, err := ph.GetUsedResources(context.Background(), radixClient, appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since, ts.args.ignoreZero) + if err != ts.expectedError { + t.Errorf("GetUsedResources() error = %v, expectedError %v", err, ts.expectedError) + return + } + if !reflect.DeepEqual(got, ts.expectedUsedResources) { + t.Errorf("GetUsedResources() got = %v, expectedUsedResources %v", got, ts.expectedUsedResources) + } + }) + } +} From 6803ee589b4c80b27c20540d28bd9b02a0e6614d Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 19 Sep 2024 10:14:36 +0200 Subject: [PATCH 18/32] Adding unit-tests --- api/metrics/prometheus_handler.go | 4 ++-- api/metrics/prometheus_handler_test.go | 14 +++++++++++--- go.mod | 2 +- go.sum | 6 ++++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index 5c1ac8d8..34937e95 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -32,7 +32,7 @@ const ( memoryAvg QueryName = "memoryAvg" durationExpression = `^[0-9]{1,5}[mhdw]$` defaultDuration = "30d" - defaultOffset = "0s" + defaultOffset = "" ) // PrometheusHandler Interface for Prometheus handler @@ -65,7 +65,7 @@ func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient if err != nil { return nil, err } - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() log.Ctx(ctx).Debug().Msgf("Getting used resources for application %s", appName) diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index eb12f0ca..a42d9fe9 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -21,6 +21,7 @@ func Test_handler_GetUsedResources(t *testing.T) { ) type args struct { + appName string envName string componentName string duration string @@ -31,15 +32,22 @@ func Test_handler_GetUsedResources(t *testing.T) { type scenario struct { name string args args + clientReturnsMetrics map[QueryName]model.Value expectedUsedResources *applicationModels.UsedResources - expectedMetrics map[QueryName]model.Value expectedWarnings []string expectedError error } scenarios := []scenario{ { - name: "Test GetUsedResources", args: args{}}, + name: "Got used resources", + args: args{ + appName: appName1, + }, + clientReturnsMetrics: map[QueryName]model.Value{ + cpuMax: model.Vector{&model.Sample{Metric: map[model.LabelName]model.LabelValue{}, Value: 1}}, + }, + }, } for _, ts := range scenarios { t.Run(ts.name, func(t *testing.T) { @@ -50,7 +58,7 @@ func Test_handler_GetUsedResources(t *testing.T) { ctrl := gomock.NewController(t) mockPrometheusClient := mock.NewMockPrometheusClient(ctrl) mockPrometheusClient.EXPECT().GetMetrics(gomock.Any(), appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since). - Return(ts.expectedMetrics, ts.expectedWarnings, ts.expectedError) + Return(ts.clientReturnsMetrics, ts.expectedWarnings, ts.expectedError) ph := &handler{ client: mockPrometheusClient, diff --git a/go.mod b/go.mod index 5f151029..f9221f73 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.0 github.com/prometheus/client_golang v1.20.3 + github.com/prometheus/common v0.55.0 github.com/rs/cors v1.11.0 github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.33.0 @@ -86,7 +87,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a34541a0..5ac86b82 100644 --- a/go.sum +++ b/go.sum @@ -233,6 +233,7 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -288,6 +289,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= @@ -316,8 +318,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= -github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= From 13cda7663518e453e57dbeb6444840dc1b338406 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 20 Sep 2024 14:52:54 +0200 Subject: [PATCH 19/32] Adding unit-tests --- .../applications_controller_test.go | 8 +- .../{mock => }/prometheus_client_mock.go | 7 +- .../{mock => }/prometheus_handler_mock.go | 2 +- api/metrics/prometheus_handler_test.go | 83 ++++++++++++++++--- 4 files changed, 81 insertions(+), 19 deletions(-) rename api/metrics/{mock => }/prometheus_client_mock.go (88%) rename api/metrics/{mock => }/prometheus_handler_mock.go (99%) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 3a98bd98..fd72b92f 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -17,7 +17,7 @@ import ( applicationModels "github.com/equinor/radix-api/api/applications/models" environmentModels "github.com/equinor/radix-api/api/environments/models" jobModels "github.com/equinor/radix-api/api/jobs/models" - "github.com/equinor/radix-api/api/metrics/mock" + "github.com/equinor/radix-api/api/metrics" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/models" @@ -92,9 +92,9 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient } -func createPrometheusHandlerMock(t *testing.T, radixclient *radixfake.Clientset, mockHandler *func(handler *mock.MockPrometheusHandler)) *mock.MockPrometheusHandler { +func createPrometheusHandlerMock(t *testing.T, radixclient *radixfake.Clientset, mockHandler *func(handler *metrics.MockPrometheusHandler)) *metrics.MockPrometheusHandler { ctrl := gomock.NewController(t) - mockPrometheusHandler := mock.NewMockPrometheusHandler(ctrl) + mockPrometheusHandler := metrics.NewMockPrometheusHandler(ctrl) if mockHandler != nil { (*mockHandler)(mockPrometheusHandler) } else { @@ -1979,7 +1979,7 @@ func Test_GetUsedResources(t *testing.T) { _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) require.NoError(t, err) - mockHandlerModifier := func(handler *mock.MockPrometheusHandler) { + mockHandlerModifier := func(handler *metrics.MockPrometheusHandler) { args := ts.expectedArgs handler.EXPECT().GetUsedResources(gomock.Any(), radixClient, appName1, args.environment, args.component, args.duration, args.since, args.ignoreZero). Times(1). diff --git a/api/metrics/mock/prometheus_client_mock.go b/api/metrics/prometheus_client_mock.go similarity index 88% rename from api/metrics/mock/prometheus_client_mock.go rename to api/metrics/prometheus_client_mock.go index b8880f2c..843c384b 100644 --- a/api/metrics/mock/prometheus_client_mock.go +++ b/api/metrics/prometheus_client_mock.go @@ -2,13 +2,12 @@ // Source: ./api/metrics/prometheus_client.go // Package mock is a generated GoMock package. -package mock +package metrics import ( context "context" reflect "reflect" - metrics "github.com/equinor/radix-api/api/metrics" gomock "github.com/golang/mock/gomock" model "github.com/prometheus/common/model" ) @@ -37,10 +36,10 @@ func (m *MockPrometheusClient) EXPECT() *MockPrometheusClientMockRecorder { } // GetMetrics mocks base method. -func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[metrics.QueryName]model.Value, []string, error) { +func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[QueryName]model.Value, []string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMetrics", ctx, appName, envName, componentName, duration, since) - ret0, _ := ret[0].(map[metrics.QueryName]model.Value) + ret0, _ := ret[0].(map[QueryName]model.Value) ret1, _ := ret[1].([]string) ret2, _ := ret[2].(error) return ret0, ret1, ret2 diff --git a/api/metrics/mock/prometheus_handler_mock.go b/api/metrics/prometheus_handler_mock.go similarity index 99% rename from api/metrics/mock/prometheus_handler_mock.go rename to api/metrics/prometheus_handler_mock.go index a6183aa3..8abc102b 100644 --- a/api/metrics/mock/prometheus_handler_mock.go +++ b/api/metrics/prometheus_handler_mock.go @@ -2,7 +2,7 @@ // Source: ./api/metrics/prometheus_handler.go // Package mock is a generated GoMock package. -package mock +package metrics import ( context "context" diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index a42d9fe9..51c28ef0 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -2,22 +2,25 @@ package metrics import ( "context" - "reflect" + "errors" "testing" applicationModels "github.com/equinor/radix-api/api/applications/models" - "github.com/equinor/radix-api/api/metrics/mock" + "github.com/equinor/radix-common/utils/pointers" commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/golang/mock/gomock" "github.com/prometheus/common/model" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_handler_GetUsedResources(t *testing.T) { const ( - appName1 = "app1" + appName1 = "app1" + metricsKeyContainer = "container" + metricsKeyNamespace = "namespace" ) type args struct { @@ -42,10 +45,52 @@ func Test_handler_GetUsedResources(t *testing.T) { { name: "Got used resources", args: args{ - appName: appName1, + appName: appName1, + duration: defaultDuration, }, clientReturnsMetrics: map[QueryName]model.Value{ - cpuMax: model.Vector{&model.Sample{Metric: map[model.LabelName]model.LabelValue{}, Value: 1}}, + cpuMax: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.008123134}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.126576764}, + }, + cpuAvg: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0023213546}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.047546577}, + }, + cpuMin: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0019874}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.02321456}, + }, + memoryMax: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 123456.3475613}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 234567.34575412}, + }, + memoryAvg: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 90654.81}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 150654.12398771}, + }, + memoryMin: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 56731.2324654}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 112234.456789}, + }, + }, + expectedUsedResources: &applicationModels.UsedResources{ + CPU: &applicationModels.UsedResource{ + Min: "1m", + Max: "126m", + Average: "24m", + MinActual: pointers.Ptr(1.9874), + MaxActual: pointers.Ptr(126.576764), + AvgActual: pointers.Ptr(24.933966), + }, + Memory: &applicationModels.UsedResource{ + Min: "56M", + Max: "234M", + Average: "120M", + MinActual: pointers.Ptr(56.731232), + MaxActual: pointers.Ptr(234.567346), + AvgActual: pointers.Ptr(120.654467), + }, }, }, } @@ -56,7 +101,7 @@ func Test_handler_GetUsedResources(t *testing.T) { _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) require.NoError(t, err) ctrl := gomock.NewController(t) - mockPrometheusClient := mock.NewMockPrometheusClient(ctrl) + mockPrometheusClient := NewMockPrometheusClient(ctrl) mockPrometheusClient.EXPECT().GetMetrics(gomock.Any(), appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since). Return(ts.clientReturnsMetrics, ts.expectedWarnings, ts.expectedError) @@ -64,13 +109,31 @@ func Test_handler_GetUsedResources(t *testing.T) { client: mockPrometheusClient, } got, err := ph.GetUsedResources(context.Background(), radixClient, appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since, ts.args.ignoreZero) - if err != ts.expectedError { + if !errors.Is(ts.expectedError, err) { t.Errorf("GetUsedResources() error = %v, expectedError %v", err, ts.expectedError) return } - if !reflect.DeepEqual(got, ts.expectedUsedResources) { - t.Errorf("GetUsedResources() got = %v, expectedUsedResources %v", got, ts.expectedUsedResources) - } + assert.ElementsMatch(t, ts.expectedWarnings, got.Warnings, "Warnings") + assert.Equal(t, ts.expectedUsedResources.CPU.Min, got.CPU.Min, "CPU.Min") + assert.Equal(t, ts.expectedUsedResources.CPU.Max, got.CPU.Max, "CPU.Max") + assert.Equal(t, ts.expectedUsedResources.CPU.Average, got.CPU.Average, "CPU.Average") + assert.NotNil(t, got.CPU.MinActual, "nil CPU.MinActual") + assert.NotNil(t, got.CPU.MaxActual, "nil CPU.MaxActual") + assert.NotNil(t, *got.CPU.AvgActual, "nil CPU.AvgActual") + assert.Equal(t, *ts.expectedUsedResources.CPU.MinActual, *got.CPU.MinActual, "CPU.MinActual") + assert.Equal(t, *ts.expectedUsedResources.CPU.MaxActual, *got.CPU.MaxActual, "CPU.MaxActual") + assert.Equal(t, *ts.expectedUsedResources.CPU.AvgActual, *got.CPU.AvgActual, "CPU.AvgActual") + assert.Equal(t, ts.expectedUsedResources.Memory.Min, got.Memory.Min, "Memory.Min") + assert.Equal(t, ts.expectedUsedResources.Memory.Max, got.Memory.Max, "Memory.Max") + assert.Equal(t, ts.expectedUsedResources.Memory.Average, got.Memory.Average, "Memory.Average") + assert.NotNil(t, got.Memory.MinActual, "nil Memory.MinActual") + assert.NotNil(t, got.Memory.MaxActual, "nil Memory.MaxActual") + assert.NotNil(t, got.Memory.AvgActual, "nil Memory.AvgActual") + assert.Equal(t, *ts.expectedUsedResources.Memory.MinActual, *got.Memory.MinActual, "Memory.MinActual") + assert.Equal(t, *ts.expectedUsedResources.Memory.MaxActual, *got.Memory.MaxActual, "Memory.MaxActual") + assert.Equal(t, *ts.expectedUsedResources.Memory.AvgActual, *got.Memory.AvgActual, "Memory.AvgActual") + assert.NotEmpty(t, got.From, "From") + assert.NotEmpty(t, got.To, "To") }) } } From 7b06569a35faa8d22ca23a0e21503f217e831a9e Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 20 Sep 2024 15:18:39 +0200 Subject: [PATCH 20/32] Adding unit-tests --- api/metrics/prometheus_handler_test.go | 224 +++++++++++++++---------- 1 file changed, 135 insertions(+), 89 deletions(-) diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index 51c28ef0..6decc1a0 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -16,31 +16,31 @@ import ( "github.com/stretchr/testify/require" ) -func Test_handler_GetUsedResources(t *testing.T) { - const ( - appName1 = "app1" - metricsKeyContainer = "container" - metricsKeyNamespace = "namespace" - ) +type args struct { + appName string + envName string + componentName string + duration string + since string + ignoreZero bool +} - type args struct { - appName string - envName string - componentName string - duration string - since string - ignoreZero bool - } +type scenario struct { + name string + args args + clientReturnsMetrics map[QueryName]model.Value + expectedUsedResources *applicationModels.UsedResources + expectedWarnings []string + expectedError error +} - type scenario struct { - name string - args args - clientReturnsMetrics map[QueryName]model.Value - expectedUsedResources *applicationModels.UsedResources - expectedWarnings []string - expectedError error - } +const ( + appName1 = "app1" + metricsKeyContainer = "container" + metricsKeyNamespace = "namespace" +) +func Test_handler_GetUsedResources(t *testing.T) { scenarios := []scenario{ { name: "Got used resources", @@ -48,50 +48,39 @@ func Test_handler_GetUsedResources(t *testing.T) { appName: appName1, duration: defaultDuration, }, - clientReturnsMetrics: map[QueryName]model.Value{ - cpuMax: model.Vector{ - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.008123134}, - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.126576764}, - }, - cpuAvg: model.Vector{ - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0023213546}, - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.047546577}, - }, - cpuMin: model.Vector{ - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0019874}, - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.02321456}, - }, - memoryMax: model.Vector{ - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 123456.3475613}, - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 234567.34575412}, - }, - memoryAvg: model.Vector{ - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 90654.81}, - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 150654.12398771}, - }, - memoryMin: model.Vector{ - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 56731.2324654}, - &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 112234.456789}, - }, + clientReturnsMetrics: getClientReturnsMetrics(), + expectedUsedResources: getExpectedUsedResources(), + }, + { + name: "Got used resources with warnings", + args: args{ + appName: appName1, + duration: defaultDuration, }, - expectedUsedResources: &applicationModels.UsedResources{ - CPU: &applicationModels.UsedResource{ - Min: "1m", - Max: "126m", - Average: "24m", - MinActual: pointers.Ptr(1.9874), - MaxActual: pointers.Ptr(126.576764), - AvgActual: pointers.Ptr(24.933966), - }, - Memory: &applicationModels.UsedResource{ - Min: "56M", - Max: "234M", - Average: "120M", - MinActual: pointers.Ptr(56.731232), - MaxActual: pointers.Ptr(234.567346), - AvgActual: pointers.Ptr(120.654467), - }, + clientReturnsMetrics: getClientReturnsMetrics(), + expectedUsedResources: getExpectedUsedResources("Warning1", "Warning2"), + expectedWarnings: []string{"Warning1", "Warning2"}, + }, + { + name: "Requested with arguments", + args: args{ + appName: appName1, + envName: "dev", + componentName: "component1", + duration: defaultDuration, + since: "2d", + ignoreZero: true, }, + clientReturnsMetrics: getClientReturnsMetrics(), + expectedUsedResources: getExpectedUsedResources(), + }, + { + name: "With error", + args: args{ + appName: appName1, + duration: defaultDuration, + }, + expectedError: errors.New("failed to get Prometheus metrics"), }, } for _, ts := range scenarios { @@ -105,35 +94,92 @@ func Test_handler_GetUsedResources(t *testing.T) { mockPrometheusClient.EXPECT().GetMetrics(gomock.Any(), appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since). Return(ts.clientReturnsMetrics, ts.expectedWarnings, ts.expectedError) - ph := &handler{ + prometheusHandler := &handler{ client: mockPrometheusClient, } - got, err := ph.GetUsedResources(context.Background(), radixClient, appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since, ts.args.ignoreZero) - if !errors.Is(ts.expectedError, err) { - t.Errorf("GetUsedResources() error = %v, expectedError %v", err, ts.expectedError) + got, err := prometheusHandler.GetUsedResources(context.Background(), radixClient, appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since, ts.args.ignoreZero) + if ts.expectedError != nil { + assert.ErrorIs(t, err, ts.expectedError, "Missing or not matching GetUsedResources() error") return + } else { + require.NoError(t, err, "Missing or not matching GetUsedResources() error") } - assert.ElementsMatch(t, ts.expectedWarnings, got.Warnings, "Warnings") - assert.Equal(t, ts.expectedUsedResources.CPU.Min, got.CPU.Min, "CPU.Min") - assert.Equal(t, ts.expectedUsedResources.CPU.Max, got.CPU.Max, "CPU.Max") - assert.Equal(t, ts.expectedUsedResources.CPU.Average, got.CPU.Average, "CPU.Average") - assert.NotNil(t, got.CPU.MinActual, "nil CPU.MinActual") - assert.NotNil(t, got.CPU.MaxActual, "nil CPU.MaxActual") - assert.NotNil(t, *got.CPU.AvgActual, "nil CPU.AvgActual") - assert.Equal(t, *ts.expectedUsedResources.CPU.MinActual, *got.CPU.MinActual, "CPU.MinActual") - assert.Equal(t, *ts.expectedUsedResources.CPU.MaxActual, *got.CPU.MaxActual, "CPU.MaxActual") - assert.Equal(t, *ts.expectedUsedResources.CPU.AvgActual, *got.CPU.AvgActual, "CPU.AvgActual") - assert.Equal(t, ts.expectedUsedResources.Memory.Min, got.Memory.Min, "Memory.Min") - assert.Equal(t, ts.expectedUsedResources.Memory.Max, got.Memory.Max, "Memory.Max") - assert.Equal(t, ts.expectedUsedResources.Memory.Average, got.Memory.Average, "Memory.Average") - assert.NotNil(t, got.Memory.MinActual, "nil Memory.MinActual") - assert.NotNil(t, got.Memory.MaxActual, "nil Memory.MaxActual") - assert.NotNil(t, got.Memory.AvgActual, "nil Memory.AvgActual") - assert.Equal(t, *ts.expectedUsedResources.Memory.MinActual, *got.Memory.MinActual, "Memory.MinActual") - assert.Equal(t, *ts.expectedUsedResources.Memory.MaxActual, *got.Memory.MaxActual, "Memory.MaxActual") - assert.Equal(t, *ts.expectedUsedResources.Memory.AvgActual, *got.Memory.AvgActual, "Memory.AvgActual") - assert.NotEmpty(t, got.From, "From") - assert.NotEmpty(t, got.To, "To") + assertExpected(t, ts, got) }) } } + +func assertExpected(t *testing.T, ts scenario, got *applicationModels.UsedResources) { + assert.ElementsMatch(t, ts.expectedWarnings, got.Warnings, "Warnings") + assert.Equal(t, ts.expectedUsedResources.CPU.Min, got.CPU.Min, "CPU.Min") + assert.Equal(t, ts.expectedUsedResources.CPU.Max, got.CPU.Max, "CPU.Max") + assert.Equal(t, ts.expectedUsedResources.CPU.Average, got.CPU.Average, "CPU.Average") + assert.NotNil(t, got.CPU.MinActual, "nil CPU.MinActual") + assert.NotNil(t, got.CPU.MaxActual, "nil CPU.MaxActual") + assert.NotNil(t, *got.CPU.AvgActual, "nil CPU.AvgActual") + assert.Equal(t, *ts.expectedUsedResources.CPU.MinActual, *got.CPU.MinActual, "CPU.MinActual") + assert.Equal(t, *ts.expectedUsedResources.CPU.MaxActual, *got.CPU.MaxActual, "CPU.MaxActual") + assert.Equal(t, *ts.expectedUsedResources.CPU.AvgActual, *got.CPU.AvgActual, "CPU.AvgActual") + assert.Equal(t, ts.expectedUsedResources.Memory.Min, got.Memory.Min, "Memory.Min") + assert.Equal(t, ts.expectedUsedResources.Memory.Max, got.Memory.Max, "Memory.Max") + assert.Equal(t, ts.expectedUsedResources.Memory.Average, got.Memory.Average, "Memory.Average") + assert.NotNil(t, got.Memory.MinActual, "nil Memory.MinActual") + assert.NotNil(t, got.Memory.MaxActual, "nil Memory.MaxActual") + assert.NotNil(t, got.Memory.AvgActual, "nil Memory.AvgActual") + assert.Equal(t, *ts.expectedUsedResources.Memory.MinActual, *got.Memory.MinActual, "Memory.MinActual") + assert.Equal(t, *ts.expectedUsedResources.Memory.MaxActual, *got.Memory.MaxActual, "Memory.MaxActual") + assert.Equal(t, *ts.expectedUsedResources.Memory.AvgActual, *got.Memory.AvgActual, "Memory.AvgActual") + assert.NotEmpty(t, got.From, "From") + assert.NotEmpty(t, got.To, "To") +} + +func getClientReturnsMetrics() map[QueryName]model.Value { + return map[QueryName]model.Value{ + cpuMax: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.008123134}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.126576764}, + }, + cpuAvg: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0023213546}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.047546577}, + }, + cpuMin: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0019874}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.02321456}, + }, + memoryMax: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 123456.3475613}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 234567.34575412}, + }, + memoryAvg: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 90654.81}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 150654.12398771}, + }, + memoryMin: model.Vector{ + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 56731.2324654}, + &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 112234.456789}, + }, + } +} + +func getExpectedUsedResources(warnings ...string) *applicationModels.UsedResources { + return &applicationModels.UsedResources{ + Warnings: warnings, + CPU: &applicationModels.UsedResource{ + Min: "1m", + Max: "126m", + Average: "24m", + MinActual: pointers.Ptr(1.9874), + MaxActual: pointers.Ptr(126.576764), + AvgActual: pointers.Ptr(24.933966), + }, + Memory: &applicationModels.UsedResource{ + Min: "56M", + Max: "234M", + Average: "120M", + MinActual: pointers.Ptr(56.731232), + MaxActual: pointers.Ptr(234.567346), + AvgActual: pointers.Ptr(120.654467), + }, + } +} From 30c2e10c92d2dfba1434841a347671ef75457bed Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 20 Sep 2024 15:26:50 +0200 Subject: [PATCH 21/32] Updated refs --- api/metrics/mock/prometheus_client_mock.go | 53 +++++++++++++++++++++ api/metrics/mock/prometheus_handler_mock.go | 52 ++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 api/metrics/mock/prometheus_client_mock.go create mode 100644 api/metrics/mock/prometheus_handler_mock.go diff --git a/api/metrics/mock/prometheus_client_mock.go b/api/metrics/mock/prometheus_client_mock.go new file mode 100644 index 00000000..b8880f2c --- /dev/null +++ b/api/metrics/mock/prometheus_client_mock.go @@ -0,0 +1,53 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./api/metrics/prometheus_client.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + metrics "github.com/equinor/radix-api/api/metrics" + gomock "github.com/golang/mock/gomock" + model "github.com/prometheus/common/model" +) + +// MockPrometheusClient is a mock of PrometheusClient interface. +type MockPrometheusClient struct { + ctrl *gomock.Controller + recorder *MockPrometheusClientMockRecorder +} + +// MockPrometheusClientMockRecorder is the mock recorder for MockPrometheusClient. +type MockPrometheusClientMockRecorder struct { + mock *MockPrometheusClient +} + +// NewMockPrometheusClient creates a new mock instance. +func NewMockPrometheusClient(ctrl *gomock.Controller) *MockPrometheusClient { + mock := &MockPrometheusClient{ctrl: ctrl} + mock.recorder = &MockPrometheusClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPrometheusClient) EXPECT() *MockPrometheusClientMockRecorder { + return m.recorder +} + +// GetMetrics mocks base method. +func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[metrics.QueryName]model.Value, []string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMetrics", ctx, appName, envName, componentName, duration, since) + ret0, _ := ret[0].(map[metrics.QueryName]model.Value) + ret1, _ := ret[1].([]string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetMetrics indicates an expected call of GetMetrics. +func (mr *MockPrometheusClientMockRecorder) GetMetrics(ctx, appName, envName, componentName, duration, since interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetrics", reflect.TypeOf((*MockPrometheusClient)(nil).GetMetrics), ctx, appName, envName, componentName, duration, since) +} diff --git a/api/metrics/mock/prometheus_handler_mock.go b/api/metrics/mock/prometheus_handler_mock.go new file mode 100644 index 00000000..a6183aa3 --- /dev/null +++ b/api/metrics/mock/prometheus_handler_mock.go @@ -0,0 +1,52 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./api/metrics/prometheus_handler.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + models "github.com/equinor/radix-api/api/applications/models" + versioned "github.com/equinor/radix-operator/pkg/client/clientset/versioned" + gomock "github.com/golang/mock/gomock" +) + +// MockPrometheusHandler is a mock of PrometheusHandler interface. +type MockPrometheusHandler struct { + ctrl *gomock.Controller + recorder *MockPrometheusHandlerMockRecorder +} + +// MockPrometheusHandlerMockRecorder is the mock recorder for MockPrometheusHandler. +type MockPrometheusHandlerMockRecorder struct { + mock *MockPrometheusHandler +} + +// NewMockPrometheusHandler creates a new mock instance. +func NewMockPrometheusHandler(ctrl *gomock.Controller) *MockPrometheusHandler { + mock := &MockPrometheusHandler{ctrl: ctrl} + mock.recorder = &MockPrometheusHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPrometheusHandler) EXPECT() *MockPrometheusHandlerMockRecorder { + return m.recorder +} + +// GetUsedResources mocks base method. +func (m *MockPrometheusHandler) GetUsedResources(ctx context.Context, radixClient versioned.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*models.UsedResources, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsedResources", ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) + ret0, _ := ret[0].(*models.UsedResources) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUsedResources indicates an expected call of GetUsedResources. +func (mr *MockPrometheusHandlerMockRecorder) GetUsedResources(ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsedResources", reflect.TypeOf((*MockPrometheusHandler)(nil).GetUsedResources), ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) +} diff --git a/go.mod b/go.mod index a3e2871c..53837daf 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cert-manager/cert-manager v1.15.0 github.com/equinor/radix-common v1.9.5 github.com/equinor/radix-job-scheduler v1.11.0 - github.com/equinor/radix-operator v1.59.1 + github.com/equinor/radix-operator v1.59.3 github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 github.com/golang-jwt/jwt/v5 v5.2.1 diff --git a/go.sum b/go.sum index 24bc2d7b..327f2e63 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/equinor/radix-common v1.9.5 h1:p1xldkYUoavwIMguoxxOyVkOXLPA6K8qMsgzez github.com/equinor/radix-common v1.9.5/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.11.0 h1:8wCmXOVl/1cto8q2WJQEE06Cw68/QmfoifYVR49vzkY= github.com/equinor/radix-job-scheduler v1.11.0/go.mod h1:yPXn3kDcMY0Z3kBkosjuefsdY1x2g0NlBeybMmHz5hc= -github.com/equinor/radix-operator v1.59.1 h1:wzb2tF4MWtGDWmyYuIv3oh17G5Bx8ztW9O+WgnI3QFc= -github.com/equinor/radix-operator v1.59.1/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8= +github.com/equinor/radix-operator v1.59.3 h1:/jCB8Av1VPSTKmkVIStXjlcGqxnSgYRtpBnD426l7Cs= +github.com/equinor/radix-operator v1.59.3/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= From d185ed77e23ffa27836dfaf1f96fffe62b02a38f Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 20 Sep 2024 15:35:53 +0200 Subject: [PATCH 22/32] Renamed var --- .vscode/launch.json | 2 +- README.md | 2 +- api/applications/applications_handler_config.go | 4 ++-- radixconfig.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7528b136..92558aa9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,7 +22,7 @@ "REQUIRE_APP_CONFIGURATION_ITEM": "true", "REQUIRE_APP_AD_GROUPS": "true", "RADIX_ENVIRONMENT":"qa", - "RADIX_PROMETHEUS_URL":"http://localhost:9091", + "PROMETHEUS_URL":"http://localhost:9091", "RADIX_APP":"radix-api", "LOG_LEVEL":"info", "LOG_PRETTY":"true" diff --git a/README.md b/README.md index 01f5140a..5e41a250 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ The following env vars are needed. Useful default values in brackets. - `RADIX_CONTAINER_REGISTRY` - (`radixdev.azurecr.io`) - `PIPELINE_IMG_TAG` - (`master-latest`) - `TEKTON_IMG_TAG` - (`release-latest`) -- `RADIX_PROMETHEUS_URL` - `http://localhost:9091` use this to get Prometheus metrics running the following command (the local port 9090 is used by the API server `/metrics` endpoint, in-cluster URL is http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090): +- `PROMETHEUS_URL` - `http://localhost:9091` use this to get Prometheus metrics running the following command (the local port 9090 is used by the API server `/metrics` endpoint, in-cluster URL is http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090): ``` kubectl -n monitor port-forward svc/prometheus-operator-prometheus 9091:9090 ``` diff --git a/api/applications/applications_handler_config.go b/api/applications/applications_handler_config.go index e8777ee5..ae190e54 100644 --- a/api/applications/applications_handler_config.go +++ b/api/applications/applications_handler_config.go @@ -32,7 +32,7 @@ type ApplicationHandlerConfig struct { AppName string `cfg:"radix_app" flag:"radix-app"` EnvironmentName string `cfg:"radix_environment" flag:"radix-environment"` DNSZone string `cfg:"radix_dns_zone" flag:"radix-dns-zone"` - PrometheusUrl string `cfg:"radix_prometheus_url" flag:"radix-prometheus-url"` + PrometheusUrl string `cfg:"prometheus_url" flag:"prometheus-url"` } func ApplicationHandlerConfigFlagSet() *pflag.FlagSet { @@ -43,6 +43,6 @@ func ApplicationHandlerConfigFlagSet() *pflag.FlagSet { flagset.String("radix-app", "", "Application name") flagset.String("radix-environment", "", "Environment name") flagset.String("radix-dns-zone", "", "Radix DNS zone") - flagset.String("radix-prometheus-url", "", "Prometheus URL") + flagset.String("prometheus-url", "", "Prometheus URL") return flagset } diff --git a/radixconfig.yaml b/radixconfig.yaml index cdcfb99f..91269a0a 100644 --- a/radixconfig.yaml +++ b/radixconfig.yaml @@ -34,7 +34,7 @@ spec: USE_PROFILER: "false" LOG_LEVEL: info LOG_PRETTY: "false" - RADIX_PROMETHEUS_URL: http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090 + PROMETHEUS_URL: http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090 environmentConfig: - environment: qa horizontalScaling: From 72656e5b3416db0d9f10b27e8a030144a9d49ba0 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 24 Sep 2024 10:55:22 +0200 Subject: [PATCH 23/32] Send only raw values --- .../applications_controller_test.go | 24 +++++----- api/applications/models/used_resources.go | 36 ++++---------- api/metrics/prometheus_handler.go | 34 +++++-------- api/metrics/prometheus_handler_test.go | 48 +++++++++---------- swaggerui/html/swagger.json | 32 +++---------- 5 files changed, 63 insertions(+), 111 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index fd72b92f..b37bfb45 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -2015,20 +2015,20 @@ func getTestUsedResources() *applicationModels.UsedResources { From: radixutils.FormatTimestamp(time.Now().Add(time.Minute * -10)), To: radixutils.FormatTimestamp(time.Now()), CPU: &applicationModels.UsedResource{ - Min: "1m", - Max: "10m", - Average: "5m", - MinActual: pointers.Ptr(1.1), - MaxActual: pointers.Ptr(10.12), - AvgActual: pointers.Ptr(5.56), + Min: "1m", + Max: "10m", + Average: "5m", + Min: pointers.Ptr(1.1), + Max: pointers.Ptr(10.12), + Avg: pointers.Ptr(5.56), }, Memory: &applicationModels.UsedResource{ - Min: "100M", - Max: "1000M", - Average: "500M", - MinActual: pointers.Ptr(100.1), - MaxActual: pointers.Ptr(1000.12), - AvgActual: pointers.Ptr(500.56), + Min: "100M", + Max: "1000M", + Average: "500M", + Min: pointers.Ptr(100.1), + Max: pointers.Ptr(1000.12), + Avg: pointers.Ptr(500.56), }, Warnings: []string{"warning1", "warning2"}, } diff --git a/api/applications/models/used_resources.go b/api/applications/models/used_resources.go index 2a19e560..934dcf01 100644 --- a/api/applications/models/used_resources.go +++ b/api/applications/models/used_resources.go @@ -15,16 +15,14 @@ type UsedResources struct { // example: 2006-01-02T15:04:05Z To string `json:"to"` - // CPU used + // CPU used, in cores // // required: false - // example: 120m CPU *UsedResource `json:"cpu,omitempty"` - // CPU used + // Memory used, in bytes // // required: false - // example: 120m Memory *UsedResource `json:"memory,omitempty"` // Warning messages @@ -36,39 +34,21 @@ type UsedResources struct { // UsedResource holds information about used resource // swagger:model UsedResource type UsedResource struct { - // Min resource used - // - // required: false - // example: 120m - Min string `json:"min,omitempty"` - - // Max resource used - // - // required: false - // example: 120m - Max string `json:"max,omitempty"` - - // Average resource used - // - // required: false - // example: 120m - Average string `json:"average,omitempty"` - - // MinActual actual precise resource used + // Min actual precise resource used // // required: false // example: 0.00012 - MinActual *float64 `json:"minActual,omitempty"` + Min *float64 `json:"min,omitempty"` - // MaxActual actual precise resource used + // Max actual precise resource used // // required: false // example: 0.00037 - MaxActual *float64 `json:"maxActual,omitempty"` + Max *float64 `json:"max,omitempty"` - // AvgActual actual precise resource used + // Avg actual precise resource used // // required: false // example: 0.00012 - AvgActual *float64 `json:"avgActual,omitempty"` + Avg *float64 `json:"avg,omitempty"` } diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index 34937e95..2302c78c 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -3,7 +3,6 @@ package metrics import ( "context" "fmt" - "math" "regexp" "time" @@ -16,7 +15,6 @@ import ( "github.com/prometheus/common/model" prometheusModel "github.com/prometheus/common/model" "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -81,13 +79,13 @@ func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient func getUsedResourcesByMetrics(ctx context.Context, results map[QueryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration, ignoreZero bool) *applicationModels.UsedResources { usedCpuResource := applicationModels.UsedResource{} - usedCpuResource.Min, usedCpuResource.MinActual = getCpuMetricValue(ctx, results, cpuMin, ignoreZero) - usedCpuResource.Max, usedCpuResource.MaxActual = getCpuMetricValue(ctx, results, cpuMax, ignoreZero) - usedCpuResource.Average, usedCpuResource.AvgActual = getCpuMetricValue(ctx, results, cpuAvg, ignoreZero) + usedCpuResource.Min = getCpuMetricValue(ctx, results, cpuMin, ignoreZero) + usedCpuResource.Max = getCpuMetricValue(ctx, results, cpuMax, ignoreZero) + usedCpuResource.Avg = getCpuMetricValue(ctx, results, cpuAvg, ignoreZero) usedMemoryResource := applicationModels.UsedResource{} - usedMemoryResource.Min, usedMemoryResource.MinActual = getMemoryMetricValue(ctx, results, memoryMin, ignoreZero) - usedMemoryResource.Max, usedMemoryResource.MaxActual = getMemoryMetricValue(ctx, results, memoryMax, ignoreZero) - usedMemoryResource.Average, usedMemoryResource.AvgActual = getMemoryMetricValue(ctx, results, memoryAvg, ignoreZero) + usedMemoryResource.Min = getMemoryMetricValue(ctx, results, memoryMin, ignoreZero) + usedMemoryResource.Max = getMemoryMetricValue(ctx, results, memoryMax, ignoreZero) + usedMemoryResource.Avg = getMemoryMetricValue(ctx, results, memoryAvg, ignoreZero) now := time.Now() return &applicationModels.UsedResources{ From: radixutils.FormatTimestamp(now.Add(-queryDuration)), @@ -108,26 +106,18 @@ func parseQueryDuration(duration string, defaultValue string) (time.Duration, st return time.Duration(parsedDuration), duration, err } -func roundActualValue(num float64) float64 { - return math.Round(num*1e6) / 1e6 -} - -func getCpuMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (string, *float64) { +func getCpuMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) *float64 { if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { - valueInMillicores := value * 1000.0 - quantity := resource.NewMilliQuantity(int64(valueInMillicores), resource.BinarySI) - return quantity.String(), pointers.Ptr(roundActualValue(valueInMillicores)) + return pointers.Ptr(value) } - return "", nil + return nil } -func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (string, *float64) { +func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) *float64 { if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { - valueInMegabytes := value / 1000.0 - quantity := resource.NewScaledQuantity(int64(valueInMegabytes), resource.Mega) - return quantity.String(), pointers.Ptr(roundActualValue(valueInMegabytes)) + return pointers.Ptr(value) } - return "", nil + return nil } func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (float64, bool) { diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index 6decc1a0..3f906db7 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -114,21 +114,21 @@ func assertExpected(t *testing.T, ts scenario, got *applicationModels.UsedResour assert.Equal(t, ts.expectedUsedResources.CPU.Min, got.CPU.Min, "CPU.Min") assert.Equal(t, ts.expectedUsedResources.CPU.Max, got.CPU.Max, "CPU.Max") assert.Equal(t, ts.expectedUsedResources.CPU.Average, got.CPU.Average, "CPU.Average") - assert.NotNil(t, got.CPU.MinActual, "nil CPU.MinActual") - assert.NotNil(t, got.CPU.MaxActual, "nil CPU.MaxActual") - assert.NotNil(t, *got.CPU.AvgActual, "nil CPU.AvgActual") - assert.Equal(t, *ts.expectedUsedResources.CPU.MinActual, *got.CPU.MinActual, "CPU.MinActual") - assert.Equal(t, *ts.expectedUsedResources.CPU.MaxActual, *got.CPU.MaxActual, "CPU.MaxActual") - assert.Equal(t, *ts.expectedUsedResources.CPU.AvgActual, *got.CPU.AvgActual, "CPU.AvgActual") + assert.NotNil(t, got.CPU.Min, "nil CPU.Min") + assert.NotNil(t, got.CPU.Max, "nil CPU.Max") + assert.NotNil(t, *got.CPU.Avg, "nil CPU.Avg") + assert.Equal(t, *ts.expectedUsedResources.CPU.Min, *got.CPU.Min, "CPU.Min") + assert.Equal(t, *ts.expectedUsedResources.CPU.Max, *got.CPU.Max, "CPU.Max") + assert.Equal(t, *ts.expectedUsedResources.CPU.Avg, *got.CPU.Avg, "CPU.Avg") assert.Equal(t, ts.expectedUsedResources.Memory.Min, got.Memory.Min, "Memory.Min") assert.Equal(t, ts.expectedUsedResources.Memory.Max, got.Memory.Max, "Memory.Max") assert.Equal(t, ts.expectedUsedResources.Memory.Average, got.Memory.Average, "Memory.Average") - assert.NotNil(t, got.Memory.MinActual, "nil Memory.MinActual") - assert.NotNil(t, got.Memory.MaxActual, "nil Memory.MaxActual") - assert.NotNil(t, got.Memory.AvgActual, "nil Memory.AvgActual") - assert.Equal(t, *ts.expectedUsedResources.Memory.MinActual, *got.Memory.MinActual, "Memory.MinActual") - assert.Equal(t, *ts.expectedUsedResources.Memory.MaxActual, *got.Memory.MaxActual, "Memory.MaxActual") - assert.Equal(t, *ts.expectedUsedResources.Memory.AvgActual, *got.Memory.AvgActual, "Memory.AvgActual") + assert.NotNil(t, got.Memory.Min, "nil Memory.Min") + assert.NotNil(t, got.Memory.Max, "nil Memory.Max") + assert.NotNil(t, got.Memory.Avg, "nil Memory.Avg") + assert.Equal(t, *ts.expectedUsedResources.Memory.Min, *got.Memory.Min, "Memory.Min") + assert.Equal(t, *ts.expectedUsedResources.Memory.Max, *got.Memory.Max, "Memory.Max") + assert.Equal(t, *ts.expectedUsedResources.Memory.Avg, *got.Memory.Avg, "Memory.Avg") assert.NotEmpty(t, got.From, "From") assert.NotEmpty(t, got.To, "To") } @@ -166,20 +166,20 @@ func getExpectedUsedResources(warnings ...string) *applicationModels.UsedResourc return &applicationModels.UsedResources{ Warnings: warnings, CPU: &applicationModels.UsedResource{ - Min: "1m", - Max: "126m", - Average: "24m", - MinActual: pointers.Ptr(1.9874), - MaxActual: pointers.Ptr(126.576764), - AvgActual: pointers.Ptr(24.933966), + Min: "1m", + Max: "126m", + Average: "24m", + Min: pointers.Ptr(1.9874), + Max: pointers.Ptr(126.576764), + Avg: pointers.Ptr(24.933966), }, Memory: &applicationModels.UsedResource{ - Min: "56M", - Max: "234M", - Average: "120M", - MinActual: pointers.Ptr(56.731232), - MaxActual: pointers.Ptr(234.567346), - AvgActual: pointers.Ptr(120.654467), + Min: "56M", + Max: "234M", + Average: "120M", + Min: pointers.Ptr(56.731232), + Max: pointers.Ptr(234.567346), + Avg: pointers.Ptr(120.654467), }, } } diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index b84351cf..35b800ee 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -8231,43 +8231,25 @@ "description": "UsedResource holds information about used resource", "type": "object", "properties": { - "average": { - "description": "Average resource used", - "type": "string", - "x-go-name": "Average", - "example": "120m" - }, - "avgActual": { - "description": "AvgActual actual precise resource used", + "avg": { + "description": "Avg actual precise resource used", "type": "number", "format": "double", - "x-go-name": "AvgActual", + "x-go-name": "Avg", "example": 0.00012 }, "max": { - "description": "Max resource used", - "type": "string", - "x-go-name": "Max", - "example": "120m" - }, - "maxActual": { - "description": "MaxActual actual precise resource used", + "description": "Max actual precise resource used", "type": "number", "format": "double", - "x-go-name": "MaxActual", + "x-go-name": "Max", "example": 0.00037 }, "min": { - "description": "Min resource used", - "type": "string", - "x-go-name": "Min", - "example": "120m" - }, - "minActual": { - "description": "MinActual actual precise resource used", + "description": "Min actual precise resource used", "type": "number", "format": "double", - "x-go-name": "MinActual", + "x-go-name": "Min", "example": 0.00012 } }, From 25c56f20a206a62672388b918ef3693a6d46ce84 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 24 Sep 2024 11:01:28 +0200 Subject: [PATCH 24/32] Always remove zero values --- api/applications/applications_controller.go | 13 +-------- api/metrics/mock/prometheus_handler_mock.go | 8 +++--- api/metrics/prometheus_handler.go | 32 ++++++++++----------- api/metrics/prometheus_handler_test.go | 4 +-- swaggerui/html/swagger.json | 7 ----- 5 files changed, 22 insertions(+), 42 deletions(-) diff --git a/api/applications/applications_controller.go b/api/applications/applications_controller.go index 92468fb0..4afef764 100644 --- a/api/applications/applications_controller.go +++ b/api/applications/applications_controller.go @@ -1032,12 +1032,6 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht // description: End time-point of the period in the past, default is now. Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks // type: string // required: false - // - name: ignorezero - // in: query - // description: Ignore metrics with zero value if true, default is false - // type: string - // format: boolean - // required: false // - name: Impersonate-User // in: header // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) @@ -1060,13 +1054,8 @@ func (ac *applicationController) GetUsedResources(accounts models.Accounts, w ht componentName := r.FormValue("component") duration := r.FormValue("duration") since := r.FormValue("since") - ignoreZeroArg := r.FormValue("ignorezero") - var ignoreZero = false - if strings.TrimSpace(ignoreZeroArg) != "" { - ignoreZero, _ = strconv.ParseBool(ignoreZeroArg) - } - usedResources, err := ac.prometheusHandler.GetUsedResources(r.Context(), accounts.UserAccount.RadixClient, appName, envName, componentName, duration, since, ignoreZero) + usedResources, err := ac.prometheusHandler.GetUsedResources(r.Context(), accounts.UserAccount.RadixClient, appName, envName, componentName, duration, since) if err != nil { ac.ErrorResponse(w, r, err) return diff --git a/api/metrics/mock/prometheus_handler_mock.go b/api/metrics/mock/prometheus_handler_mock.go index a6183aa3..255cbe2b 100644 --- a/api/metrics/mock/prometheus_handler_mock.go +++ b/api/metrics/mock/prometheus_handler_mock.go @@ -37,16 +37,16 @@ func (m *MockPrometheusHandler) EXPECT() *MockPrometheusHandlerMockRecorder { } // GetUsedResources mocks base method. -func (m *MockPrometheusHandler) GetUsedResources(ctx context.Context, radixClient versioned.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*models.UsedResources, error) { +func (m *MockPrometheusHandler) GetUsedResources(ctx context.Context, radixClient versioned.Interface, appName, envName, componentName, duration, since string) (*models.UsedResources, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUsedResources", ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) + ret := m.ctrl.Call(m, "GetUsedResources", ctx, radixClient, appName, envName, componentName, duration, since) ret0, _ := ret[0].(*models.UsedResources) ret1, _ := ret[1].(error) return ret0, ret1 } // GetUsedResources indicates an expected call of GetUsedResources. -func (mr *MockPrometheusHandlerMockRecorder) GetUsedResources(ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero interface{}) *gomock.Call { +func (mr *MockPrometheusHandlerMockRecorder) GetUsedResources(ctx, radixClient, appName, envName, componentName, duration, since interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsedResources", reflect.TypeOf((*MockPrometheusHandler)(nil).GetUsedResources), ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsedResources", reflect.TypeOf((*MockPrometheusHandler)(nil).GetUsedResources), ctx, radixClient, appName, envName, componentName, duration, since) } diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index 2302c78c..cd164382 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -35,7 +35,7 @@ const ( // PrometheusHandler Interface for Prometheus handler type PrometheusHandler interface { - GetUsedResources(ctx context.Context, radixClient radixclient.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) + GetUsedResources(ctx context.Context, radixClient radixclient.Interface, appName, envName, componentName, duration, since string) (*applicationModels.UsedResources, error) } type handler struct { @@ -50,7 +50,7 @@ func NewPrometheusHandler(client PrometheusClient) PrometheusHandler { } // GetUsedResources Get used resources for the application -func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*applicationModels.UsedResources, error) { +func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient.Interface, appName, envName, componentName, duration, since string) (*applicationModels.UsedResources, error) { _, err := radixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) if err != nil { return nil, err @@ -71,21 +71,21 @@ func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient if err != nil { return nil, err } - resources := getUsedResourcesByMetrics(ctx, results, durationValue, sinceValue, ignoreZero) + resources := getUsedResourcesByMetrics(ctx, results, durationValue, sinceValue) resources.Warnings = warnings log.Ctx(ctx).Debug().Msgf("Got used resources for application %s", appName) return resources, nil } -func getUsedResourcesByMetrics(ctx context.Context, results map[QueryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration, ignoreZero bool) *applicationModels.UsedResources { +func getUsedResourcesByMetrics(ctx context.Context, results map[QueryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration) *applicationModels.UsedResources { usedCpuResource := applicationModels.UsedResource{} - usedCpuResource.Min = getCpuMetricValue(ctx, results, cpuMin, ignoreZero) - usedCpuResource.Max = getCpuMetricValue(ctx, results, cpuMax, ignoreZero) - usedCpuResource.Avg = getCpuMetricValue(ctx, results, cpuAvg, ignoreZero) + usedCpuResource.Min = getCpuMetricValue(ctx, results, cpuMin) + usedCpuResource.Max = getCpuMetricValue(ctx, results, cpuMax) + usedCpuResource.Avg = getCpuMetricValue(ctx, results, cpuAvg) usedMemoryResource := applicationModels.UsedResource{} - usedMemoryResource.Min = getMemoryMetricValue(ctx, results, memoryMin, ignoreZero) - usedMemoryResource.Max = getMemoryMetricValue(ctx, results, memoryMax, ignoreZero) - usedMemoryResource.Avg = getMemoryMetricValue(ctx, results, memoryAvg, ignoreZero) + usedMemoryResource.Min = getMemoryMetricValue(ctx, results, memoryMin) + usedMemoryResource.Max = getMemoryMetricValue(ctx, results, memoryMax) + usedMemoryResource.Avg = getMemoryMetricValue(ctx, results, memoryAvg) now := time.Now() return &applicationModels.UsedResources{ From: radixutils.FormatTimestamp(now.Add(-queryDuration)), @@ -106,21 +106,21 @@ func parseQueryDuration(duration string, defaultValue string) (time.Duration, st return time.Duration(parsedDuration), duration, err } -func getCpuMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) *float64 { - if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { +func getCpuMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) *float64 { + if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { return pointers.Ptr(value) } return nil } -func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) *float64 { - if value, ok := getMetricsValue(ctx, queryResults, queryName, ignoreZero); ok { +func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) *float64 { + if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { return pointers.Ptr(value) } return nil } -func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName, ignoreZero bool) (float64, bool) { +func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { return 0, false @@ -131,7 +131,7 @@ func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusM return 0, false } values := slice.Reduce(groupedMetrics, make([]float64, 0), func(acc []float64, sample *model.Sample) []float64 { - if ignoreZero && sample.Value <= 0 { + if sample.Value <= 0 { return acc } return append(acc, float64(sample.Value)) diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index 3f906db7..e28c346d 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -22,7 +22,6 @@ type args struct { componentName string duration string since string - ignoreZero bool } type scenario struct { @@ -69,7 +68,6 @@ func Test_handler_GetUsedResources(t *testing.T) { componentName: "component1", duration: defaultDuration, since: "2d", - ignoreZero: true, }, clientReturnsMetrics: getClientReturnsMetrics(), expectedUsedResources: getExpectedUsedResources(), @@ -97,7 +95,7 @@ func Test_handler_GetUsedResources(t *testing.T) { prometheusHandler := &handler{ client: mockPrometheusClient, } - got, err := prometheusHandler.GetUsedResources(context.Background(), radixClient, appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since, ts.args.ignoreZero) + got, err := prometheusHandler.GetUsedResources(context.Background(), radixClient, appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since) if ts.expectedError != nil { assert.ErrorIs(t, err, ts.expectedError, "Missing or not matching GetUsedResources() error") return diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 35b800ee..4569fadf 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -5060,13 +5060,6 @@ "name": "since", "in": "query" }, - { - "type": "string", - "format": "boolean", - "description": "Ignore metrics with zero value if true, default is false", - "name": "ignorezero", - "in": "query" - }, { "type": "string", "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", From c642e64e4136ed160f03f7ee5f3811f0449bba15 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 24 Sep 2024 15:47:35 +0200 Subject: [PATCH 25/32] Updated objects --- api/applications/models/used_resources.go | 16 ++++++++-------- api/metrics/prometheus_handler_test.go | 22 ++++++++-------------- swaggerui/html/swagger.json | 10 +++++----- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/api/applications/models/used_resources.go b/api/applications/models/used_resources.go index 934dcf01..4c89eeed 100644 --- a/api/applications/models/used_resources.go +++ b/api/applications/models/used_resources.go @@ -12,7 +12,7 @@ type UsedResources struct { // To timestamp // // required: true - // example: 2006-01-02T15:04:05Z + // example: 2006-01-03T15:04:05Z To string `json:"to"` // CPU used, in cores @@ -34,21 +34,21 @@ type UsedResources struct { // UsedResource holds information about used resource // swagger:model UsedResource type UsedResource struct { - // Min actual precise resource used + // Min resource used // // required: false // example: 0.00012 Min *float64 `json:"min,omitempty"` - // Max actual precise resource used + // Avg Average resource used // // required: false - // example: 0.00037 - Max *float64 `json:"max,omitempty"` + // example: 0.00023 + Avg *float64 `json:"avg,omitempty"` - // Avg actual precise resource used + // Max resource used // // required: false - // example: 0.00012 - Avg *float64 `json:"avg,omitempty"` + // example: 0.00037 + Max *float64 `json:"max,omitempty"` } diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index e28c346d..1c3ad42d 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -111,7 +111,7 @@ func assertExpected(t *testing.T, ts scenario, got *applicationModels.UsedResour assert.ElementsMatch(t, ts.expectedWarnings, got.Warnings, "Warnings") assert.Equal(t, ts.expectedUsedResources.CPU.Min, got.CPU.Min, "CPU.Min") assert.Equal(t, ts.expectedUsedResources.CPU.Max, got.CPU.Max, "CPU.Max") - assert.Equal(t, ts.expectedUsedResources.CPU.Average, got.CPU.Average, "CPU.Average") + assert.Equal(t, ts.expectedUsedResources.CPU.Avg, got.CPU.Avg, "CPU.Avg") assert.NotNil(t, got.CPU.Min, "nil CPU.Min") assert.NotNil(t, got.CPU.Max, "nil CPU.Max") assert.NotNil(t, *got.CPU.Avg, "nil CPU.Avg") @@ -120,7 +120,7 @@ func assertExpected(t *testing.T, ts scenario, got *applicationModels.UsedResour assert.Equal(t, *ts.expectedUsedResources.CPU.Avg, *got.CPU.Avg, "CPU.Avg") assert.Equal(t, ts.expectedUsedResources.Memory.Min, got.Memory.Min, "Memory.Min") assert.Equal(t, ts.expectedUsedResources.Memory.Max, got.Memory.Max, "Memory.Max") - assert.Equal(t, ts.expectedUsedResources.Memory.Average, got.Memory.Average, "Memory.Average") + assert.Equal(t, ts.expectedUsedResources.Memory.Avg, got.Memory.Avg, "Memory.Avg") assert.NotNil(t, got.Memory.Min, "nil Memory.Min") assert.NotNil(t, got.Memory.Max, "nil Memory.Max") assert.NotNil(t, got.Memory.Avg, "nil Memory.Avg") @@ -164,20 +164,14 @@ func getExpectedUsedResources(warnings ...string) *applicationModels.UsedResourc return &applicationModels.UsedResources{ Warnings: warnings, CPU: &applicationModels.UsedResource{ - Min: "1m", - Max: "126m", - Average: "24m", - Min: pointers.Ptr(1.9874), - Max: pointers.Ptr(126.576764), - Avg: pointers.Ptr(24.933966), + Min: pointers.Ptr(1.9874), + Max: pointers.Ptr(126.576764), + Avg: pointers.Ptr(24.933966), }, Memory: &applicationModels.UsedResource{ - Min: "56M", - Max: "234M", - Average: "120M", - Min: pointers.Ptr(56.731232), - Max: pointers.Ptr(234.567346), - Avg: pointers.Ptr(120.654467), + Min: pointers.Ptr(56.731232), + Max: pointers.Ptr(234.567346), + Avg: pointers.Ptr(120.654467), }, } } diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 4569fadf..9c377495 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -8225,21 +8225,21 @@ "type": "object", "properties": { "avg": { - "description": "Avg actual precise resource used", + "description": "Avg Average resource used", "type": "number", "format": "double", "x-go-name": "Avg", - "example": 0.00012 + "example": 0.00023 }, "max": { - "description": "Max actual precise resource used", + "description": "Max resource used", "type": "number", "format": "double", "x-go-name": "Max", "example": 0.00037 }, "min": { - "description": "Min actual precise resource used", + "description": "Min resource used", "type": "number", "format": "double", "x-go-name": "Min", @@ -8272,7 +8272,7 @@ "description": "To timestamp", "type": "string", "x-go-name": "To", - "example": "2006-01-02T15:04:05Z" + "example": "2006-01-03T15:04:05Z" }, "warnings": { "description": "Warning messages", From 6b9bd1f399bd1da7ed7df3cf8ede2fdf8762e869 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 25 Sep 2024 15:46:23 +0200 Subject: [PATCH 26/32] Tuned queries --- api/metrics/prometheus_handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index cd164382..0ac81a55 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -171,8 +171,8 @@ func getPrometheusQueries(appName, envName, componentName, duration, since strin fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) - cpuUsageQuery := fmt.Sprintf(`sum(rate(container_cpu_usage_seconds_total{namespace!="%s-app" %s %s}[5m] %s )) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, offsetFilter, duration) - memoryUsageQuery := fmt.Sprintf(`sum(rate(container_memory_usage_bytes{namespace!="%s-app" %s %s}[5m] %s )) by (namespace,container)[%s:]`, appName, environmentFilter, componentFilter, offsetFilter, duration) + cpuUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_cpu_usage_seconds_total{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) + memoryUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_memory_usage_bytes{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) queries := map[QueryName]string{ cpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), cpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), From 1d5486dab693c7f6acf24e6ba6b89082c55cd17e Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 25 Sep 2024 17:02:57 +0200 Subject: [PATCH 27/32] Corrected summary logic --- api/metrics/prometheus_handler.go | 41 +++++-------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index 0ac81a55..59afbb09 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -9,7 +9,6 @@ import ( applicationModels "github.com/equinor/radix-api/api/applications/models" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" - "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/utils" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "github.com/prometheus/common/model" @@ -123,46 +122,18 @@ func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]promet func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { - return 0, false + return 0.0, false } groupedMetrics, ok := queryResult.(model.Vector) if !ok { log.Ctx(ctx).Error().Msgf("Failed to convert metrics query %s result to Vector", queryName) return 0, false } - values := slice.Reduce(groupedMetrics, make([]float64, 0), func(acc []float64, sample *model.Sample) []float64 { - if sample.Value <= 0 { - return acc - } - return append(acc, float64(sample.Value)) - }) - if len(values) == 0 { - return 0, false - } - switch queryName { - case cpuMax, memoryMax: - maxVal := slice.Reduce(values, values[0], func(maxValue, sample float64) float64 { - if maxValue < sample { - return sample - } - return maxValue - }) - return maxVal, true - case cpuMin, memoryMin: - minVal := slice.Reduce(values, values[0], func(minValue, sample float64) float64 { - if minValue > sample { - return sample - } - return minValue - }) - return minVal, true - case cpuAvg, memoryAvg: - avgVal := slice.Reduce(values, 0, func(sum, sample float64) float64 { - return sum + sample - }) / float64(len(values)) - return avgVal, true + sum := 0.0 + for _, sample := range groupedMetrics { + sum += float64(sample.Value) } - return 0, false + return sum, true } func getPrometheusQueries(appName, envName, componentName, duration, since string) map[QueryName]string { @@ -171,7 +142,7 @@ func getPrometheusQueries(appName, envName, componentName, duration, since strin fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) - cpuUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_cpu_usage_seconds_total{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) + cpuUsageQuery := fmt.Sprintf(`sum by (namespace, container) (rate(container_cpu_usage_seconds_total{container!="", namespace!="%s-app" %s %s} [1h])) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) memoryUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_memory_usage_bytes{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) queries := map[QueryName]string{ cpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), From 4b1b3ad19261e0256cc37ef440aa2078edf9a0cc Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 27 Sep 2024 14:28:07 +0200 Subject: [PATCH 28/32] Updated versions --- go.mod | 2 -- go.sum | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 35c6d674..e3b25aa0 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/mock v1.6.0 - github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/kedacore/keda/v2 v2.15.1 github.com/marstr/guid v1.1.0 @@ -88,7 +87,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 008033ba..a026954f 100644 --- a/go.sum +++ b/go.sum @@ -233,6 +233,7 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -288,6 +289,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= @@ -316,8 +318,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= -github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= From e7273d9d6afa065357c79a9c67211d2b491089f8 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 30 Sep 2024 08:44:50 +0200 Subject: [PATCH 29/32] Fixed tests --- api/metrics/prometheus_handler_test.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index 1c3ad42d..c3020efb 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -109,18 +109,12 @@ func Test_handler_GetUsedResources(t *testing.T) { func assertExpected(t *testing.T, ts scenario, got *applicationModels.UsedResources) { assert.ElementsMatch(t, ts.expectedWarnings, got.Warnings, "Warnings") - assert.Equal(t, ts.expectedUsedResources.CPU.Min, got.CPU.Min, "CPU.Min") - assert.Equal(t, ts.expectedUsedResources.CPU.Max, got.CPU.Max, "CPU.Max") - assert.Equal(t, ts.expectedUsedResources.CPU.Avg, got.CPU.Avg, "CPU.Avg") assert.NotNil(t, got.CPU.Min, "nil CPU.Min") assert.NotNil(t, got.CPU.Max, "nil CPU.Max") assert.NotNil(t, *got.CPU.Avg, "nil CPU.Avg") assert.Equal(t, *ts.expectedUsedResources.CPU.Min, *got.CPU.Min, "CPU.Min") assert.Equal(t, *ts.expectedUsedResources.CPU.Max, *got.CPU.Max, "CPU.Max") assert.Equal(t, *ts.expectedUsedResources.CPU.Avg, *got.CPU.Avg, "CPU.Avg") - assert.Equal(t, ts.expectedUsedResources.Memory.Min, got.Memory.Min, "Memory.Min") - assert.Equal(t, ts.expectedUsedResources.Memory.Max, got.Memory.Max, "Memory.Max") - assert.Equal(t, ts.expectedUsedResources.Memory.Avg, got.Memory.Avg, "Memory.Avg") assert.NotNil(t, got.Memory.Min, "nil Memory.Min") assert.NotNil(t, got.Memory.Max, "nil Memory.Max") assert.NotNil(t, got.Memory.Avg, "nil Memory.Avg") @@ -164,14 +158,14 @@ func getExpectedUsedResources(warnings ...string) *applicationModels.UsedResourc return &applicationModels.UsedResources{ Warnings: warnings, CPU: &applicationModels.UsedResource{ - Min: pointers.Ptr(1.9874), - Max: pointers.Ptr(126.576764), - Avg: pointers.Ptr(24.933966), + Min: pointers.Ptr(0.02520196), + Avg: pointers.Ptr(0.0498679316), + Max: pointers.Ptr(0.134699898), }, Memory: &applicationModels.UsedResource{ - Min: pointers.Ptr(56.731232), - Max: pointers.Ptr(234.567346), - Avg: pointers.Ptr(120.654467), + Min: pointers.Ptr(168965.6892544), + Avg: pointers.Ptr(241308.93398770998), + Max: pointers.Ptr(358023.69331542), }, } } From ac2bbc7f0b9bc84f8c0a07df3335d45ac14bc6ac Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 30 Sep 2024 09:42:48 +0200 Subject: [PATCH 30/32] Fixed tests --- .../applications_controller_test.go | 43 +++++---------- api/metrics/internal/prometheus_defaults | 0 api/metrics/internal/prometheus_defaults.go | 13 +++++ api/metrics/mock/prometheus_client_mock.go | 6 +-- api/metrics/prometheus_client.go | 7 +-- api/metrics/prometheus_client_mock.go | 52 ------------------- api/metrics/prometheus_handler.go | 52 ++++++++----------- api/metrics/prometheus_handler_mock.go | 52 ------------------- api/metrics/prometheus_handler_test.go | 22 ++++---- 9 files changed, 68 insertions(+), 179 deletions(-) create mode 100644 api/metrics/internal/prometheus_defaults create mode 100644 api/metrics/internal/prometheus_defaults.go delete mode 100644 api/metrics/prometheus_client_mock.go delete mode 100644 api/metrics/prometheus_handler_mock.go diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 144163a8..b09a864d 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -17,7 +17,7 @@ import ( applicationModels "github.com/equinor/radix-api/api/applications/models" environmentModels "github.com/equinor/radix-api/api/environments/models" jobModels "github.com/equinor/radix-api/api/jobs/models" - "github.com/equinor/radix-api/api/metrics" + metricsMock "github.com/equinor/radix-api/api/metrics/mock" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/models" @@ -35,6 +35,7 @@ import ( builders "github.com/equinor/radix-operator/pkg/apis/utils" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/golang/mock/gomock" + "github.com/google/uuid" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" "github.com/stretchr/testify/assert" @@ -92,13 +93,13 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient } -func createPrometheusHandlerMock(t *testing.T, radixclient *radixfake.Clientset, mockHandler *func(handler *metrics.MockPrometheusHandler)) *metrics.MockPrometheusHandler { +func createPrometheusHandlerMock(t *testing.T, radixclient *radixfake.Clientset, mockHandler *func(handler *metricsMock.MockPrometheusHandler)) *metricsMock.MockPrometheusHandler { ctrl := gomock.NewController(t) - mockPrometheusHandler := metrics.NewMockPrometheusHandler(ctrl) + mockPrometheusHandler := metricsMock.NewMockPrometheusHandler(ctrl) if mockHandler != nil { (*mockHandler)(mockPrometheusHandler) } else { - mockPrometheusHandler.EXPECT().GetUsedResources(gomock.Any(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) + mockPrometheusHandler.EXPECT().GetUsedResources(gomock.Any(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) } return mockPrometheusHandler } @@ -1944,7 +1945,6 @@ func Test_GetUsedResources(t *testing.T) { component string duration string since string - ignoreZero bool } type scenario struct { @@ -1964,22 +1964,13 @@ func Test_GetUsedResources(t *testing.T) { }, { name: "Get used resources with arguments", - queryString: "?environment=prod&component=component1&duration=10d&since=2w&ignorezero=true", + queryString: "?environment=prod&component=component1&duration=10d&since=2w", expectedUsedResources: getTestUsedResources(), expectedArgs: expectedArgs{ environment: envName1, component: componentName1, duration: "10d", since: "2w", - ignoreZero: true, - }, - }, - { - name: "Invalid boolean query parameter falls back to false", - expectedUsedResources: getTestUsedResources(), - queryString: "?ignorezero=abc", - expectedArgs: expectedArgs{ - ignoreZero: false, }, }, { @@ -1996,9 +1987,9 @@ func Test_GetUsedResources(t *testing.T) { _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) require.NoError(t, err) - mockHandlerModifier := func(handler *metrics.MockPrometheusHandler) { + mockHandlerModifier := func(handler *metricsMock.MockPrometheusHandler) { args := ts.expectedArgs - handler.EXPECT().GetUsedResources(gomock.Any(), radixClient, appName1, args.environment, args.component, args.duration, args.since, args.ignoreZero). + handler.EXPECT().GetUsedResources(gomock.Any(), radixClient, appName1, args.environment, args.component, args.duration, args.since). Times(1). Return(ts.expectedUsedResources, ts.expectedUsedResourcesError) } @@ -2032,20 +2023,14 @@ func getTestUsedResources() *applicationModels.UsedResources { From: radixutils.FormatTimestamp(time.Now().Add(time.Minute * -10)), To: radixutils.FormatTimestamp(time.Now()), CPU: &applicationModels.UsedResource{ - Min: "1m", - Max: "10m", - Average: "5m", - Min: pointers.Ptr(1.1), - Max: pointers.Ptr(10.12), - Avg: pointers.Ptr(5.56), + Min: pointers.Ptr(1.1), + Max: pointers.Ptr(10.12), + Avg: pointers.Ptr(5.56), }, Memory: &applicationModels.UsedResource{ - Min: "100M", - Max: "1000M", - Average: "500M", - Min: pointers.Ptr(100.1), - Max: pointers.Ptr(1000.12), - Avg: pointers.Ptr(500.56), + Min: pointers.Ptr(100.1), + Max: pointers.Ptr(1000.12), + Avg: pointers.Ptr(500.56), }, Warnings: []string{"warning1", "warning2"}, } diff --git a/api/metrics/internal/prometheus_defaults b/api/metrics/internal/prometheus_defaults new file mode 100644 index 00000000..e69de29b diff --git a/api/metrics/internal/prometheus_defaults.go b/api/metrics/internal/prometheus_defaults.go new file mode 100644 index 00000000..f284370b --- /dev/null +++ b/api/metrics/internal/prometheus_defaults.go @@ -0,0 +1,13 @@ +package internal + +// QueryName Prometheus query name +type QueryName string + +const ( + CpuMax QueryName = "CpuMax" + CpuMin QueryName = "CpuMin" + CpuAvg QueryName = "CpuAvg" + MemoryMax QueryName = "MemoryMax" + MemoryMin QueryName = "MemoryMin" + MemoryAvg QueryName = "MemoryAvg" +) diff --git a/api/metrics/mock/prometheus_client_mock.go b/api/metrics/mock/prometheus_client_mock.go index b8880f2c..c42ab46e 100644 --- a/api/metrics/mock/prometheus_client_mock.go +++ b/api/metrics/mock/prometheus_client_mock.go @@ -8,7 +8,7 @@ import ( context "context" reflect "reflect" - metrics "github.com/equinor/radix-api/api/metrics" + internal "github.com/equinor/radix-api/api/metrics/internal" gomock "github.com/golang/mock/gomock" model "github.com/prometheus/common/model" ) @@ -37,10 +37,10 @@ func (m *MockPrometheusClient) EXPECT() *MockPrometheusClientMockRecorder { } // GetMetrics mocks base method. -func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[metrics.QueryName]model.Value, []string, error) { +func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[internal.QueryName]model.Value, []string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMetrics", ctx, appName, envName, componentName, duration, since) - ret0, _ := ret[0].(map[metrics.QueryName]model.Value) + ret0, _ := ret[0].(map[internal.QueryName]model.Value) ret1, _ := ret[1].([]string) ret2, _ := ret[2].(error) return ret0, ret1, ret2 diff --git a/api/metrics/prometheus_client.go b/api/metrics/prometheus_client.go index ad8e07a8..1c40efb6 100644 --- a/api/metrics/prometheus_client.go +++ b/api/metrics/prometheus_client.go @@ -5,6 +5,7 @@ import ( "errors" "time" + "github.com/equinor/radix-api/api/metrics/internal" prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" @@ -15,7 +16,7 @@ import ( // PrometheusClient Interface for Prometheus client type PrometheusClient interface { // GetMetrics Get metrics for the application - GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[QueryName]prometheusModel.Value, []string, error) + GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[internal.QueryName]prometheusModel.Value, []string, error) } // NewPrometheusClient Constructor for Prometheus client @@ -35,8 +36,8 @@ type client struct { } // GetMetrics Get metrics for the application -func (c *client) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[QueryName]prometheusModel.Value, []string, error) { - results := make(map[QueryName]model.Value) +func (c *client) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[internal.QueryName]prometheusModel.Value, []string, error) { + results := make(map[internal.QueryName]model.Value) now := time.Now() var warnings []string for metricName, query := range getPrometheusQueries(appName, envName, componentName, duration, since) { diff --git a/api/metrics/prometheus_client_mock.go b/api/metrics/prometheus_client_mock.go deleted file mode 100644 index 843c384b..00000000 --- a/api/metrics/prometheus_client_mock.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./api/metrics/prometheus_client.go - -// Package mock is a generated GoMock package. -package metrics - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - model "github.com/prometheus/common/model" -) - -// MockPrometheusClient is a mock of PrometheusClient interface. -type MockPrometheusClient struct { - ctrl *gomock.Controller - recorder *MockPrometheusClientMockRecorder -} - -// MockPrometheusClientMockRecorder is the mock recorder for MockPrometheusClient. -type MockPrometheusClientMockRecorder struct { - mock *MockPrometheusClient -} - -// NewMockPrometheusClient creates a new mock instance. -func NewMockPrometheusClient(ctrl *gomock.Controller) *MockPrometheusClient { - mock := &MockPrometheusClient{ctrl: ctrl} - mock.recorder = &MockPrometheusClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPrometheusClient) EXPECT() *MockPrometheusClientMockRecorder { - return m.recorder -} - -// GetMetrics mocks base method. -func (m *MockPrometheusClient) GetMetrics(ctx context.Context, appName, envName, componentName, duration, since string) (map[QueryName]model.Value, []string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMetrics", ctx, appName, envName, componentName, duration, since) - ret0, _ := ret[0].(map[QueryName]model.Value) - ret1, _ := ret[1].([]string) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetMetrics indicates an expected call of GetMetrics. -func (mr *MockPrometheusClientMockRecorder) GetMetrics(ctx, appName, envName, componentName, duration, since interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetrics", reflect.TypeOf((*MockPrometheusClient)(nil).GetMetrics), ctx, appName, envName, componentName, duration, since) -} diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index 59afbb09..53ff1a9c 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -7,6 +7,7 @@ import ( "time" applicationModels "github.com/equinor/radix-api/api/applications/models" + "github.com/equinor/radix-api/api/metrics/internal" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -17,19 +18,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// QueryName Prometheus query name -type QueryName string - const ( - cpuMax QueryName = "cpuMax" - cpuMin QueryName = "cpuMin" - cpuAvg QueryName = "cpuAvg" - memoryMax QueryName = "memoryMax" - memoryMin QueryName = "memoryMin" - memoryAvg QueryName = "memoryAvg" - durationExpression = `^[0-9]{1,5}[mhdw]$` - defaultDuration = "30d" - defaultOffset = "" + durationExpression = `^[0-9]{1,5}[mhdw]$` + defaultDuration = "30d" + defaultOffset = "" ) // PrometheusHandler Interface for Prometheus handler @@ -76,15 +68,15 @@ func (pc *handler) GetUsedResources(ctx context.Context, radixClient radixclient return resources, nil } -func getUsedResourcesByMetrics(ctx context.Context, results map[QueryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration) *applicationModels.UsedResources { +func getUsedResourcesByMetrics(ctx context.Context, results map[internal.QueryName]prometheusModel.Value, queryDuration time.Duration, querySince time.Duration) *applicationModels.UsedResources { usedCpuResource := applicationModels.UsedResource{} - usedCpuResource.Min = getCpuMetricValue(ctx, results, cpuMin) - usedCpuResource.Max = getCpuMetricValue(ctx, results, cpuMax) - usedCpuResource.Avg = getCpuMetricValue(ctx, results, cpuAvg) + usedCpuResource.Min = getCpuMetricValue(ctx, results, internal.CpuMin) + usedCpuResource.Max = getCpuMetricValue(ctx, results, internal.CpuMax) + usedCpuResource.Avg = getCpuMetricValue(ctx, results, internal.CpuAvg) usedMemoryResource := applicationModels.UsedResource{} - usedMemoryResource.Min = getMemoryMetricValue(ctx, results, memoryMin) - usedMemoryResource.Max = getMemoryMetricValue(ctx, results, memoryMax) - usedMemoryResource.Avg = getMemoryMetricValue(ctx, results, memoryAvg) + usedMemoryResource.Min = getMemoryMetricValue(ctx, results, internal.MemoryMin) + usedMemoryResource.Max = getMemoryMetricValue(ctx, results, internal.MemoryMax) + usedMemoryResource.Avg = getMemoryMetricValue(ctx, results, internal.MemoryAvg) now := time.Now() return &applicationModels.UsedResources{ From: radixutils.FormatTimestamp(now.Add(-queryDuration)), @@ -105,21 +97,21 @@ func parseQueryDuration(duration string, defaultValue string) (time.Duration, st return time.Duration(parsedDuration), duration, err } -func getCpuMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) *float64 { +func getCpuMetricValue(ctx context.Context, queryResults map[internal.QueryName]prometheusModel.Value, queryName internal.QueryName) *float64 { if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { return pointers.Ptr(value) } return nil } -func getMemoryMetricValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) *float64 { +func getMemoryMetricValue(ctx context.Context, queryResults map[internal.QueryName]prometheusModel.Value, queryName internal.QueryName) *float64 { if value, ok := getMetricsValue(ctx, queryResults, queryName); ok { return pointers.Ptr(value) } return nil } -func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusModel.Value, queryName QueryName) (float64, bool) { +func getMetricsValue(ctx context.Context, queryResults map[internal.QueryName]prometheusModel.Value, queryName internal.QueryName) (float64, bool) { queryResult, ok := queryResults[queryName] if !ok { return 0.0, false @@ -136,7 +128,7 @@ func getMetricsValue(ctx context.Context, queryResults map[QueryName]prometheusM return sum, true } -func getPrometheusQueries(appName, envName, componentName, duration, since string) map[QueryName]string { +func getPrometheusQueries(appName, envName, componentName, duration, since string) map[internal.QueryName]string { environmentFilter := radixutils.TernaryString(envName == "", fmt.Sprintf(`,namespace=~"%s-.*"`, appName), fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) @@ -144,13 +136,13 @@ func getPrometheusQueries(appName, envName, componentName, duration, since strin offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) cpuUsageQuery := fmt.Sprintf(`sum by (namespace, container) (rate(container_cpu_usage_seconds_total{container!="", namespace!="%s-app" %s %s} [1h])) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) memoryUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_memory_usage_bytes{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) - queries := map[QueryName]string{ - cpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), - cpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), - cpuAvg: fmt.Sprintf("avg_over_time(%s)", cpuUsageQuery), - memoryMax: fmt.Sprintf("max_over_time(%s)", memoryUsageQuery), - memoryMin: fmt.Sprintf("min_over_time(%s)", memoryUsageQuery), - memoryAvg: fmt.Sprintf("avg_over_time(%s)", memoryUsageQuery), + queries := map[internal.QueryName]string{ + internal.CpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), + internal.CpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), + internal.CpuAvg: fmt.Sprintf("avg_over_time(%s)", cpuUsageQuery), + internal.MemoryMax: fmt.Sprintf("max_over_time(%s)", memoryUsageQuery), + internal.MemoryMin: fmt.Sprintf("min_over_time(%s)", memoryUsageQuery), + internal.MemoryAvg: fmt.Sprintf("avg_over_time(%s)", memoryUsageQuery), } return queries } diff --git a/api/metrics/prometheus_handler_mock.go b/api/metrics/prometheus_handler_mock.go deleted file mode 100644 index 8abc102b..00000000 --- a/api/metrics/prometheus_handler_mock.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./api/metrics/prometheus_handler.go - -// Package mock is a generated GoMock package. -package metrics - -import ( - context "context" - reflect "reflect" - - models "github.com/equinor/radix-api/api/applications/models" - versioned "github.com/equinor/radix-operator/pkg/client/clientset/versioned" - gomock "github.com/golang/mock/gomock" -) - -// MockPrometheusHandler is a mock of PrometheusHandler interface. -type MockPrometheusHandler struct { - ctrl *gomock.Controller - recorder *MockPrometheusHandlerMockRecorder -} - -// MockPrometheusHandlerMockRecorder is the mock recorder for MockPrometheusHandler. -type MockPrometheusHandlerMockRecorder struct { - mock *MockPrometheusHandler -} - -// NewMockPrometheusHandler creates a new mock instance. -func NewMockPrometheusHandler(ctrl *gomock.Controller) *MockPrometheusHandler { - mock := &MockPrometheusHandler{ctrl: ctrl} - mock.recorder = &MockPrometheusHandlerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPrometheusHandler) EXPECT() *MockPrometheusHandlerMockRecorder { - return m.recorder -} - -// GetUsedResources mocks base method. -func (m *MockPrometheusHandler) GetUsedResources(ctx context.Context, radixClient versioned.Interface, appName, envName, componentName, duration, since string, ignoreZero bool) (*models.UsedResources, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUsedResources", ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) - ret0, _ := ret[0].(*models.UsedResources) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUsedResources indicates an expected call of GetUsedResources. -func (mr *MockPrometheusHandlerMockRecorder) GetUsedResources(ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsedResources", reflect.TypeOf((*MockPrometheusHandler)(nil).GetUsedResources), ctx, radixClient, appName, envName, componentName, duration, since, ignoreZero) -} diff --git a/api/metrics/prometheus_handler_test.go b/api/metrics/prometheus_handler_test.go index c3020efb..beb8abdd 100644 --- a/api/metrics/prometheus_handler_test.go +++ b/api/metrics/prometheus_handler_test.go @@ -6,6 +6,8 @@ import ( "testing" applicationModels "github.com/equinor/radix-api/api/applications/models" + "github.com/equinor/radix-api/api/metrics/internal" + "github.com/equinor/radix-api/api/metrics/mock" "github.com/equinor/radix-common/utils/pointers" commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" @@ -27,7 +29,7 @@ type args struct { type scenario struct { name string args args - clientReturnsMetrics map[QueryName]model.Value + clientReturnsMetrics map[internal.QueryName]model.Value expectedUsedResources *applicationModels.UsedResources expectedWarnings []string expectedError error @@ -88,7 +90,7 @@ func Test_handler_GetUsedResources(t *testing.T) { _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName(appName1)) require.NoError(t, err) ctrl := gomock.NewController(t) - mockPrometheusClient := NewMockPrometheusClient(ctrl) + mockPrometheusClient := mock.NewMockPrometheusClient(ctrl) mockPrometheusClient.EXPECT().GetMetrics(gomock.Any(), appName1, ts.args.envName, ts.args.componentName, ts.args.duration, ts.args.since). Return(ts.clientReturnsMetrics, ts.expectedWarnings, ts.expectedError) @@ -125,29 +127,29 @@ func assertExpected(t *testing.T, ts scenario, got *applicationModels.UsedResour assert.NotEmpty(t, got.To, "To") } -func getClientReturnsMetrics() map[QueryName]model.Value { - return map[QueryName]model.Value{ - cpuMax: model.Vector{ +func getClientReturnsMetrics() map[internal.QueryName]model.Value { + return map[internal.QueryName]model.Value{ + internal.CpuMax: model.Vector{ &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.008123134}, &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.126576764}, }, - cpuAvg: model.Vector{ + internal.CpuAvg: model.Vector{ &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0023213546}, &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.047546577}, }, - cpuMin: model.Vector{ + internal.CpuMin: model.Vector{ &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 0.0019874}, &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 0.02321456}, }, - memoryMax: model.Vector{ + internal.MemoryMax: model.Vector{ &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 123456.3475613}, &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 234567.34575412}, }, - memoryAvg: model.Vector{ + internal.MemoryAvg: model.Vector{ &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 90654.81}, &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 150654.12398771}, }, - memoryMin: model.Vector{ + internal.MemoryMin: model.Vector{ &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-dev"}, Value: 56731.2324654}, &model.Sample{Metric: map[model.LabelName]model.LabelValue{metricsKeyContainer: "server", metricsKeyNamespace: "app-prod"}, Value: 112234.456789}, }, From 70e8c213b97f0d13ec66633ec05c70ca8ad15474 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 1 Oct 2024 12:28:05 +0200 Subject: [PATCH 31/32] Delete file --- api/metrics/internal/prometheus_defaults | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 api/metrics/internal/prometheus_defaults diff --git a/api/metrics/internal/prometheus_defaults b/api/metrics/internal/prometheus_defaults deleted file mode 100644 index e69de29b..00000000 From 8e4ae6b1541418540f1db7932e2f14453b21f044 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 1 Oct 2024 12:38:50 +0200 Subject: [PATCH 32/32] Moved the method --- api/metrics/prometheus_client.go | 22 ++++++++++++++++++++++ api/metrics/prometheus_handler.go | 21 --------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/api/metrics/prometheus_client.go b/api/metrics/prometheus_client.go index 1c40efb6..718a15e5 100644 --- a/api/metrics/prometheus_client.go +++ b/api/metrics/prometheus_client.go @@ -3,9 +3,12 @@ package metrics import ( "context" "errors" + "fmt" "time" "github.com/equinor/radix-api/api/metrics/internal" + radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-operator/pkg/apis/utils" prometheusApi "github.com/prometheus/client_golang/api" prometheusV1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" @@ -54,3 +57,22 @@ func (c *client) GetMetrics(ctx context.Context, appName, envName, componentName } return results, warnings, nil } + +func getPrometheusQueries(appName, envName, componentName, duration, since string) map[internal.QueryName]string { + environmentFilter := radixutils.TernaryString(envName == "", + fmt.Sprintf(`,namespace=~"%s-.*"`, appName), + fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) + componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) + offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) + cpuUsageQuery := fmt.Sprintf(`sum by (namespace, container) (rate(container_cpu_usage_seconds_total{container!="", namespace!="%s-app" %s %s} [1h])) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) + memoryUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_memory_usage_bytes{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) + queries := map[internal.QueryName]string{ + internal.CpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), + internal.CpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), + internal.CpuAvg: fmt.Sprintf("avg_over_time(%s)", cpuUsageQuery), + internal.MemoryMax: fmt.Sprintf("max_over_time(%s)", memoryUsageQuery), + internal.MemoryMin: fmt.Sprintf("min_over_time(%s)", memoryUsageQuery), + internal.MemoryAvg: fmt.Sprintf("avg_over_time(%s)", memoryUsageQuery), + } + return queries +} diff --git a/api/metrics/prometheus_handler.go b/api/metrics/prometheus_handler.go index 53ff1a9c..09bcd992 100644 --- a/api/metrics/prometheus_handler.go +++ b/api/metrics/prometheus_handler.go @@ -2,7 +2,6 @@ package metrics import ( "context" - "fmt" "regexp" "time" @@ -10,7 +9,6 @@ import ( "github.com/equinor/radix-api/api/metrics/internal" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" - "github.com/equinor/radix-operator/pkg/apis/utils" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "github.com/prometheus/common/model" prometheusModel "github.com/prometheus/common/model" @@ -127,22 +125,3 @@ func getMetricsValue(ctx context.Context, queryResults map[internal.QueryName]pr } return sum, true } - -func getPrometheusQueries(appName, envName, componentName, duration, since string) map[internal.QueryName]string { - environmentFilter := radixutils.TernaryString(envName == "", - fmt.Sprintf(`,namespace=~"%s-.*"`, appName), - fmt.Sprintf(`,namespace="%s"`, utils.GetEnvironmentNamespace(appName, envName))) - componentFilter := radixutils.TernaryString(envName == "", "", fmt.Sprintf(`,container="%s"`, componentName)) - offsetFilter := radixutils.TernaryString(since == "", "", fmt.Sprintf(` offset %s `, since)) - cpuUsageQuery := fmt.Sprintf(`sum by (namespace, container) (rate(container_cpu_usage_seconds_total{container!="", namespace!="%s-app" %s %s} [1h])) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) - memoryUsageQuery := fmt.Sprintf(`sum by (namespace, container) (container_memory_usage_bytes{container!="", namespace!="%s-app" %s %s} > 0) [%s:] %s`, appName, environmentFilter, componentFilter, duration, offsetFilter) - queries := map[internal.QueryName]string{ - internal.CpuMax: fmt.Sprintf("max_over_time(%s)", cpuUsageQuery), - internal.CpuMin: fmt.Sprintf("min_over_time(%s)", cpuUsageQuery), - internal.CpuAvg: fmt.Sprintf("avg_over_time(%s)", cpuUsageQuery), - internal.MemoryMax: fmt.Sprintf("max_over_time(%s)", memoryUsageQuery), - internal.MemoryMin: fmt.Sprintf("min_over_time(%s)", memoryUsageQuery), - internal.MemoryAvg: fmt.Sprintf("avg_over_time(%s)", memoryUsageQuery), - } - return queries -}