From 93fc6d546994d3036e20b8a9baff7c80ec91d750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Bj=C3=B8rnstad?= Date: Tue, 18 Mar 2025 11:40:56 +0100 Subject: [PATCH 1/2] Add InstanceUtilization type and resolver for application instances utilization --- internal/graph/applications.resolvers.go | 5 + internal/graph/gengql/generated.go | 285 ++++++++++++++++++++- internal/graph/schema/utilization.graphqls | 9 + internal/graph/utilization.resolvers.go | 4 + internal/utilization/model.go | 5 + internal/utilization/queries.go | 49 +++- 6 files changed, 331 insertions(+), 26 deletions(-) diff --git a/internal/graph/applications.resolvers.go b/internal/graph/applications.resolvers.go index 0501de6a2..54eca5d97 100644 --- a/internal/graph/applications.resolvers.go +++ b/internal/graph/applications.resolvers.go @@ -143,6 +143,10 @@ func (r *teamInventoryCountsResolver) Applications(ctx context.Context, obj *tea func (r *Resolver) Application() gengql.ApplicationResolver { return &applicationResolver{r} } +func (r *Resolver) ApplicationInstance() gengql.ApplicationInstanceResolver { + return &applicationInstanceResolver{r} +} + func (r *Resolver) DeleteApplicationPayload() gengql.DeleteApplicationPayloadResolver { return &deleteApplicationPayloadResolver{r} } @@ -159,6 +163,7 @@ func (r *Resolver) TeamInventoryCountApplications() gengql.TeamInventoryCountApp type ( applicationResolver struct{ *Resolver } + applicationInstanceResolver struct{ *Resolver } deleteApplicationPayloadResolver struct{ *Resolver } ingressResolver struct{ *Resolver } restartApplicationPayloadResolver struct{ *Resolver } diff --git a/internal/graph/gengql/generated.go b/internal/graph/gengql/generated.go index 7d93fb3eb..364e8cea5 100644 --- a/internal/graph/gengql/generated.go +++ b/internal/graph/gengql/generated.go @@ -77,6 +77,7 @@ type Config struct { type ResolverRoot interface { Application() ApplicationResolver + ApplicationInstance() ApplicationInstanceResolver BigQueryDataset() BigQueryDatasetResolver Bucket() BucketResolver ContainerImage() ContainerImageResolver @@ -219,12 +220,13 @@ type ComplexityRoot struct { } ApplicationInstance struct { - Created func(childComplexity int) int - ID func(childComplexity int) int - Image func(childComplexity int) int - Name func(childComplexity int) int - Restarts func(childComplexity int) int - Status func(childComplexity int) int + Created func(childComplexity int) int + ID func(childComplexity int) int + Image func(childComplexity int) int + InstanceUtilization func(childComplexity int, resourceType utilization.UtilizationResourceType) int + Name func(childComplexity int) int + Restarts func(childComplexity int) int + Status func(childComplexity int) int } ApplicationInstanceConnection struct { @@ -640,6 +642,10 @@ type ComplexityRoot struct { URL func(childComplexity int) int } + InstanceUtilization struct { + Current func(childComplexity int) int + } + Job struct { AuthIntegrations func(childComplexity int) int BigQueryDatasets func(childComplexity int, orderBy *bigquery.BigQueryDatasetOrder) int @@ -2389,6 +2395,9 @@ type ApplicationResolver interface { Utilization(ctx context.Context, obj *application.Application) (*utilization.WorkloadUtilization, error) ValkeyInstances(ctx context.Context, obj *application.Application, orderBy *valkey.ValkeyInstanceOrder) (*pagination.Connection[*valkey.ValkeyInstance], error) } +type ApplicationInstanceResolver interface { + InstanceUtilization(ctx context.Context, obj *application.ApplicationInstance, resourceType utilization.UtilizationResourceType) (*utilization.InstanceUtilization, error) +} type BigQueryDatasetResolver interface { Team(ctx context.Context, obj *bigquery.BigQueryDataset) (*team.Team, error) Environment(ctx context.Context, obj *bigquery.BigQueryDataset) (*team.TeamEnvironment, error) @@ -3190,6 +3199,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ApplicationInstance.Image(childComplexity), true + case "ApplicationInstance.instanceUtilization": + if e.complexity.ApplicationInstance.InstanceUtilization == nil { + break + } + + args, err := ec.field_ApplicationInstance_instanceUtilization_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.ApplicationInstance.InstanceUtilization(childComplexity, args["resourceType"].(utilization.UtilizationResourceType)), true + case "ApplicationInstance.name": if e.complexity.ApplicationInstance.Name == nil { break @@ -4646,6 +4667,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Ingress.URL(childComplexity), true + case "InstanceUtilization.current": + if e.complexity.InstanceUtilization.Current == nil { + break + } + + return e.complexity.InstanceUtilization.Current(childComplexity), true + case "Job.authIntegrations": if e.complexity.Job.AuthIntegrations == nil { break @@ -18986,6 +19014,10 @@ extend type Team { serviceUtilization: TeamServiceUtilization! } +extend type ApplicationInstance { + instanceUtilization(resourceType: UtilizationResourceType!): InstanceUtilization! +} + extend type Query { teamsUtilization(resourceType: UtilizationResourceType!): [TeamUtilizationData!]! } @@ -19062,6 +19094,11 @@ type TeamUtilizationData { "The environment for the utilization data." teamEnvironment: TeamEnvironment! } + +type InstanceUtilization { + "Get the current usage for the requested resource type." + current: Float! +} `, BuiltIn: false}, {Name: "../schema/valkey.graphqls", Input: `extend type Team { "Valkey instances owned by the team." @@ -19936,6 +19973,34 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_ApplicationInstance_instanceUtilization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_ApplicationInstance_instanceUtilization_argsResourceType(ctx, rawArgs) + if err != nil { + return nil, err + } + args["resourceType"] = arg0 + return args, nil +} +func (ec *executionContext) field_ApplicationInstance_instanceUtilization_argsResourceType( + ctx context.Context, + rawArgs map[string]any, +) (utilization.UtilizationResourceType, error) { + if _, ok := rawArgs["resourceType"]; !ok { + var zeroVal utilization.UtilizationResourceType + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("resourceType")) + if tmp, ok := rawArgs["resourceType"]; ok { + return ec.unmarshalNUtilizationResourceType2githubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐUtilizationResourceType(ctx, tmp) + } + + var zeroVal utilization.UtilizationResourceType + return zeroVal, nil +} + func (ec *executionContext) field_Application_bigQueryDatasets_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -31008,6 +31073,65 @@ func (ec *executionContext) fieldContext_ApplicationInstance_status(_ context.Co return fc, nil } +func (ec *executionContext) _ApplicationInstance_instanceUtilization(ctx context.Context, field graphql.CollectedField, obj *application.ApplicationInstance) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ApplicationInstance_instanceUtilization(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.ApplicationInstance().InstanceUtilization(rctx, obj, fc.Args["resourceType"].(utilization.UtilizationResourceType)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*utilization.InstanceUtilization) + fc.Result = res + return ec.marshalNInstanceUtilization2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐInstanceUtilization(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ApplicationInstance_instanceUtilization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ApplicationInstance", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "current": + return ec.fieldContext_InstanceUtilization_current(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type InstanceUtilization", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_ApplicationInstance_instanceUtilization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _ApplicationInstanceConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *pagination.Connection[*application.ApplicationInstance]) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ApplicationInstanceConnection_pageInfo(ctx, field) if err != nil { @@ -31119,6 +31243,8 @@ func (ec *executionContext) fieldContext_ApplicationInstanceConnection_nodes(_ c return ec.fieldContext_ApplicationInstance_created(ctx, field) case "status": return ec.fieldContext_ApplicationInstance_status(ctx, field) + case "instanceUtilization": + return ec.fieldContext_ApplicationInstance_instanceUtilization(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ApplicationInstance", field.Name) }, @@ -31271,6 +31397,8 @@ func (ec *executionContext) fieldContext_ApplicationInstanceEdge_node(_ context. return ec.fieldContext_ApplicationInstance_created(ctx, field) case "status": return ec.fieldContext_ApplicationInstance_status(ctx, field) + case "instanceUtilization": + return ec.fieldContext_ApplicationInstance_instanceUtilization(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ApplicationInstance", field.Name) }, @@ -41156,6 +41284,50 @@ func (ec *executionContext) fieldContext_Ingress_type(_ context.Context, field g return fc, nil } +func (ec *executionContext) _InstanceUtilization_current(ctx context.Context, field graphql.CollectedField, obj *utilization.InstanceUtilization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_InstanceUtilization_current(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Current, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_InstanceUtilization_current(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "InstanceUtilization", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Job_id(ctx context.Context, field graphql.CollectedField, obj *job.Job) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Job_id(ctx, field) if err != nil { @@ -99029,33 +99201,69 @@ func (ec *executionContext) _ApplicationInstance(ctx context.Context, sel ast.Se case "id": out.Values[i] = ec._ApplicationInstance_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "name": out.Values[i] = ec._ApplicationInstance_name(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "image": out.Values[i] = ec._ApplicationInstance_image(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "restarts": out.Values[i] = ec._ApplicationInstance_restarts(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "created": out.Values[i] = ec._ApplicationInstance_created(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "status": out.Values[i] = ec._ApplicationInstance_status(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) + } + case "instanceUtilization": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._ApplicationInstance_instanceUtilization(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -103119,6 +103327,45 @@ func (ec *executionContext) _Ingress(ctx context.Context, sel ast.SelectionSet, return out } +var instanceUtilizationImplementors = []string{"InstanceUtilization"} + +func (ec *executionContext) _InstanceUtilization(ctx context.Context, sel ast.SelectionSet, obj *utilization.InstanceUtilization) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, instanceUtilizationImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("InstanceUtilization") + case "current": + out.Values[i] = ec._InstanceUtilization_current(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var jobImplementors = []string{"Job", "Node", "Workload", "SearchNode"} func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj *job.Job) graphql.Marshaler { @@ -124542,6 +124789,20 @@ func (ec *executionContext) marshalNIngressType2githubᚗcomᚋnaisᚋapiᚋinte return v } +func (ec *executionContext) marshalNInstanceUtilization2githubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐInstanceUtilization(ctx context.Context, sel ast.SelectionSet, v utilization.InstanceUtilization) graphql.Marshaler { + return ec._InstanceUtilization(ctx, sel, &v) +} + +func (ec *executionContext) marshalNInstanceUtilization2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐInstanceUtilization(ctx context.Context, sel ast.SelectionSet, v *utilization.InstanceUtilization) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._InstanceUtilization(ctx, sel, v) +} + func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v any) (int, error) { res, err := graphql.UnmarshalInt(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/internal/graph/schema/utilization.graphqls b/internal/graph/schema/utilization.graphqls index c3c22e0c2..2fad152df 100644 --- a/internal/graph/schema/utilization.graphqls +++ b/internal/graph/schema/utilization.graphqls @@ -7,6 +7,10 @@ extend type Team { serviceUtilization: TeamServiceUtilization! } +extend type ApplicationInstance { + instanceUtilization(resourceType: UtilizationResourceType!): InstanceUtilization! +} + extend type Query { teamsUtilization(resourceType: UtilizationResourceType!): [TeamUtilizationData!]! } @@ -83,3 +87,8 @@ type TeamUtilizationData { "The environment for the utilization data." teamEnvironment: TeamEnvironment! } + +type InstanceUtilization { + "Get the current usage for the requested resource type." + current: Float! +} diff --git a/internal/graph/utilization.resolvers.go b/internal/graph/utilization.resolvers.go index e2dbfbc10..81e31b107 100644 --- a/internal/graph/utilization.resolvers.go +++ b/internal/graph/utilization.resolvers.go @@ -19,6 +19,10 @@ func (r *applicationResolver) Utilization(ctx context.Context, obj *application. }, nil } +func (r *applicationInstanceResolver) InstanceUtilization(ctx context.Context, obj *application.ApplicationInstance, resourceType utilization.UtilizationResourceType) (*utilization.InstanceUtilization, error) { + return utilization.ForInstance(ctx, r.unmappedEnvironmentName(obj.EnvironmentName), obj.TeamSlug, obj.ApplicationName, obj.Name, resourceType) +} + func (r *queryResolver) TeamsUtilization(ctx context.Context, resourceType utilization.UtilizationResourceType) ([]*utilization.TeamUtilizationData, error) { return utilization.ForTeams(ctx, resourceType) } diff --git a/internal/utilization/model.go b/internal/utilization/model.go index 91be3a819..39e092df9 100644 --- a/internal/utilization/model.go +++ b/internal/utilization/model.go @@ -117,3 +117,8 @@ type TeamUtilization struct { type TeamServiceUtilization struct { TeamSlug slug.Slug `json:"-"` } + +type InstanceUtilization struct { + // Get the current usage for the requested resource type. + Current float64 `json:"current"` +} diff --git a/internal/utilization/queries.go b/internal/utilization/queries.go index 4adf553d5..c3c3ff873 100644 --- a/internal/utilization/queries.go +++ b/internal/utilization/queries.go @@ -14,20 +14,22 @@ import ( ) const ( - appCPULimit = `kube_pod_container_resource_limits{namespace=%q, container=%q, resource="cpu", unit="core"}` - appCPURequest = `kube_pod_container_resource_requests{namespace=%q, container=%q, resource="cpu",unit="core"}` - appCPUUsage = `rate(container_cpu_usage_seconds_total{namespace=%q, container=%q}[5m])` - appMemoryLimit = `kube_pod_container_resource_limits{namespace=%q, container=%q, resource="memory", unit="byte"}` - appMemoryRequest = `kube_pod_container_resource_requests{namespace=%q, container=%q, resource="memory",unit="byte"}` - appMemoryUsage = `last_over_time(container_memory_working_set_bytes{namespace=%q, container=%q}[5m])` - teamCPURequest = `sum by (container, owner_kind) (kube_pod_container_resource_requests{namespace=%q, container!~%q, resource="cpu",unit="core"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` - teamCPUUsage = `sum by (container, owner_kind) (rate(container_cpu_usage_seconds_total{namespace=%q, container!~%q}[5m]) * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"} )` - teamMemoryRequest = `sum by (container, owner_kind) (kube_pod_container_resource_requests{namespace=%q, container!~%q, resource="memory",unit="byte"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` - teamMemoryUsage = `sum by (container, owner_kind) (container_memory_working_set_bytes{namespace=%q, container!~%q} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` - teamsCPURequest = `sum by (namespace, owner_kind) (kube_pod_container_resource_requests{namespace!~%q, container!~%q, resource="cpu",unit="core"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` - teamsCPUUsage = `sum by (namespace, owner_kind) (rate(container_cpu_usage_seconds_total{namespace!~%q, container!~%q}[5m]) * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` - teamsMemoryRequest = `sum by (namespace, owner_kind) (kube_pod_container_resource_requests{namespace!~%q, container!~%q, resource="memory",unit="byte"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` - teamsMemoryUsage = `sum by (namespace, owner_kind) (container_memory_working_set_bytes{namespace!~%q, container!~%q} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + appCPULimit = `kube_pod_container_resource_limits{namespace=%q, container=%q, resource="cpu", unit="core"}` + appCPURequest = `kube_pod_container_resource_requests{namespace=%q, container=%q, resource="cpu",unit="core"}` + appCPUUsage = `rate(container_cpu_usage_seconds_total{namespace=%q, container=%q}[5m])` + appMemoryLimit = `kube_pod_container_resource_limits{namespace=%q, container=%q, resource="memory", unit="byte"}` + appMemoryRequest = `kube_pod_container_resource_requests{namespace=%q, container=%q, resource="memory",unit="byte"}` + appMemoryUsage = `last_over_time(container_memory_working_set_bytes{namespace=%q, container=%q}[5m])` + instanceCPUUsage = `rate(container_cpu_usage_seconds_total{namespace=%q, container=%q, pod=%q}[5m])` + instanceMemoryUsage = `last_over_time(container_memory_working_set_bytes{namespace=%q, container=%q, pod=%q}[5m])` + teamCPURequest = `sum by (container, owner_kind) (kube_pod_container_resource_requests{namespace=%q, container!~%q, resource="cpu",unit="core"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + teamCPUUsage = `sum by (container, owner_kind) (rate(container_cpu_usage_seconds_total{namespace=%q, container!~%q}[5m]) * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"} )` + teamMemoryRequest = `sum by (container, owner_kind) (kube_pod_container_resource_requests{namespace=%q, container!~%q, resource="memory",unit="byte"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + teamMemoryUsage = `sum by (container, owner_kind) (container_memory_working_set_bytes{namespace=%q, container!~%q} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + teamsCPURequest = `sum by (namespace, owner_kind) (kube_pod_container_resource_requests{namespace!~%q, container!~%q, resource="cpu",unit="core"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + teamsCPUUsage = `sum by (namespace, owner_kind) (rate(container_cpu_usage_seconds_total{namespace!~%q, container!~%q}[5m]) * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + teamsMemoryRequest = `sum by (namespace, owner_kind) (kube_pod_container_resource_requests{namespace!~%q, container!~%q, resource="memory",unit="byte"} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` + teamsMemoryUsage = `sum by (namespace, owner_kind) (container_memory_working_set_bytes{namespace!~%q, container!~%q} * on(pod,namespace) group_left(owner_kind) kube_pod_owner{owner_kind="ReplicaSet"})` ) var ( @@ -35,6 +37,25 @@ var ( ignoredNamespaces = strings.Join([]string{"kube-system", "nais-system", "cnrm-system", "configconnector-operator-system", "linkerd", "gke-mcs", "gke-managed-system", "kyverno", "default", "kube-node-lease", "kube-public"}, "|") ) +func ForInstance(ctx context.Context, env string, teamSlug slug.Slug, workloadName string, instanceName string, resourceType UtilizationResourceType) (*InstanceUtilization, error) { + usageQ := instanceMemoryUsage + + if resourceType == UtilizationResourceTypeCPU { + usageQ = instanceCPUUsage + } + + c := fromContext(ctx).client + + current, err := c.query(ctx, env, fmt.Sprintf(usageQ, teamSlug, workloadName, instanceName)) + if err != nil { + return nil, err + } + + return &InstanceUtilization{ + Current: ensuredVal(current), + }, nil +} + func ForTeams(ctx context.Context, resourceType UtilizationResourceType) ([]*TeamUtilizationData, error) { reqQ := teamsMemoryRequest usageQ := teamsMemoryUsage From 8391c11554369398ed91e9e6ac9e497c97ffb5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Bj=C3=B8rnstad?= Date: Tue, 18 Mar 2025 12:52:49 +0100 Subject: [PATCH 2/2] Rename InstanceUtilization to ApplicationInstanceUtilization for clarity and consistency in utilization queries and resolvers --- internal/graph/gengql/generated.go | 230 ++++++++++----------- internal/graph/schema/utilization.graphqls | 4 +- internal/graph/utilization.resolvers.go | 2 +- internal/utilization/model.go | 2 +- internal/utilization/queries.go | 4 +- 5 files changed, 121 insertions(+), 121 deletions(-) diff --git a/internal/graph/gengql/generated.go b/internal/graph/gengql/generated.go index 364e8cea5..73fab52f9 100644 --- a/internal/graph/gengql/generated.go +++ b/internal/graph/gengql/generated.go @@ -245,6 +245,10 @@ type ComplexityRoot struct { State func(childComplexity int) int } + ApplicationInstanceUtilization struct { + Current func(childComplexity int) int + } + ApplicationManifest struct { Content func(childComplexity int) int } @@ -642,10 +646,6 @@ type ComplexityRoot struct { URL func(childComplexity int) int } - InstanceUtilization struct { - Current func(childComplexity int) int - } - Job struct { AuthIntegrations func(childComplexity int) int BigQueryDatasets func(childComplexity int, orderBy *bigquery.BigQueryDatasetOrder) int @@ -2396,7 +2396,7 @@ type ApplicationResolver interface { ValkeyInstances(ctx context.Context, obj *application.Application, orderBy *valkey.ValkeyInstanceOrder) (*pagination.Connection[*valkey.ValkeyInstance], error) } type ApplicationInstanceResolver interface { - InstanceUtilization(ctx context.Context, obj *application.ApplicationInstance, resourceType utilization.UtilizationResourceType) (*utilization.InstanceUtilization, error) + InstanceUtilization(ctx context.Context, obj *application.ApplicationInstance, resourceType utilization.UtilizationResourceType) (*utilization.ApplicationInstanceUtilization, error) } type BigQueryDatasetResolver interface { Team(ctx context.Context, obj *bigquery.BigQueryDataset) (*team.Team, error) @@ -3281,6 +3281,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ApplicationInstanceStatus.State(childComplexity), true + case "ApplicationInstanceUtilization.current": + if e.complexity.ApplicationInstanceUtilization.Current == nil { + break + } + + return e.complexity.ApplicationInstanceUtilization.Current(childComplexity), true + case "ApplicationManifest.content": if e.complexity.ApplicationManifest.Content == nil { break @@ -4667,13 +4674,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Ingress.URL(childComplexity), true - case "InstanceUtilization.current": - if e.complexity.InstanceUtilization.Current == nil { - break - } - - return e.complexity.InstanceUtilization.Current(childComplexity), true - case "Job.authIntegrations": if e.complexity.Job.AuthIntegrations == nil { break @@ -19015,7 +19015,7 @@ extend type Team { } extend type ApplicationInstance { - instanceUtilization(resourceType: UtilizationResourceType!): InstanceUtilization! + instanceUtilization(resourceType: UtilizationResourceType!): ApplicationInstanceUtilization! } extend type Query { @@ -19095,7 +19095,7 @@ type TeamUtilizationData { teamEnvironment: TeamEnvironment! } -type InstanceUtilization { +type ApplicationInstanceUtilization { "Get the current usage for the requested resource type." current: Float! } @@ -31099,9 +31099,9 @@ func (ec *executionContext) _ApplicationInstance_instanceUtilization(ctx context } return graphql.Null } - res := resTmp.(*utilization.InstanceUtilization) + res := resTmp.(*utilization.ApplicationInstanceUtilization) fc.Result = res - return ec.marshalNInstanceUtilization2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐInstanceUtilization(ctx, field.Selections, res) + return ec.marshalNApplicationInstanceUtilization2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐApplicationInstanceUtilization(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_ApplicationInstance_instanceUtilization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -31113,9 +31113,9 @@ func (ec *executionContext) fieldContext_ApplicationInstance_instanceUtilization Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "current": - return ec.fieldContext_InstanceUtilization_current(ctx, field) + return ec.fieldContext_ApplicationInstanceUtilization_current(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type InstanceUtilization", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ApplicationInstanceUtilization", field.Name) }, } defer func() { @@ -31494,6 +31494,50 @@ func (ec *executionContext) fieldContext_ApplicationInstanceStatus_message(_ con return fc, nil } +func (ec *executionContext) _ApplicationInstanceUtilization_current(ctx context.Context, field graphql.CollectedField, obj *utilization.ApplicationInstanceUtilization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ApplicationInstanceUtilization_current(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Current, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ApplicationInstanceUtilization_current(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ApplicationInstanceUtilization", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _ApplicationManifest_content(ctx context.Context, field graphql.CollectedField, obj *application.ApplicationManifest) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ApplicationManifest_content(ctx, field) if err != nil { @@ -41284,50 +41328,6 @@ func (ec *executionContext) fieldContext_Ingress_type(_ context.Context, field g return fc, nil } -func (ec *executionContext) _InstanceUtilization_current(ctx context.Context, field graphql.CollectedField, obj *utilization.InstanceUtilization) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_InstanceUtilization_current(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return obj.Current, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(float64) - fc.Result = res - return ec.marshalNFloat2float64(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_InstanceUtilization_current(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "InstanceUtilization", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Float does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _Job_id(ctx context.Context, field graphql.CollectedField, obj *job.Job) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Job_id(ctx, field) if err != nil { @@ -99424,6 +99424,45 @@ func (ec *executionContext) _ApplicationInstanceStatus(ctx context.Context, sel return out } +var applicationInstanceUtilizationImplementors = []string{"ApplicationInstanceUtilization"} + +func (ec *executionContext) _ApplicationInstanceUtilization(ctx context.Context, sel ast.SelectionSet, obj *utilization.ApplicationInstanceUtilization) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, applicationInstanceUtilizationImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ApplicationInstanceUtilization") + case "current": + out.Values[i] = ec._ApplicationInstanceUtilization_current(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var applicationManifestImplementors = []string{"ApplicationManifest", "WorkloadManifest"} func (ec *executionContext) _ApplicationManifest(ctx context.Context, sel ast.SelectionSet, obj *application.ApplicationManifest) graphql.Marshaler { @@ -103327,45 +103366,6 @@ func (ec *executionContext) _Ingress(ctx context.Context, sel ast.SelectionSet, return out } -var instanceUtilizationImplementors = []string{"InstanceUtilization"} - -func (ec *executionContext) _InstanceUtilization(ctx context.Context, sel ast.SelectionSet, obj *utilization.InstanceUtilization) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, instanceUtilizationImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("InstanceUtilization") - case "current": - out.Values[i] = ec._InstanceUtilization_current(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - var jobImplementors = []string{"Job", "Node", "Workload", "SearchNode"} func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj *job.Job) graphql.Marshaler { @@ -122847,6 +122847,20 @@ func (ec *executionContext) marshalNApplicationInstanceStatus2ᚖgithubᚗcomᚋ return ec._ApplicationInstanceStatus(ctx, sel, v) } +func (ec *executionContext) marshalNApplicationInstanceUtilization2githubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐApplicationInstanceUtilization(ctx context.Context, sel ast.SelectionSet, v utilization.ApplicationInstanceUtilization) graphql.Marshaler { + return ec._ApplicationInstanceUtilization(ctx, sel, &v) +} + +func (ec *executionContext) marshalNApplicationInstanceUtilization2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐApplicationInstanceUtilization(ctx context.Context, sel ast.SelectionSet, v *utilization.ApplicationInstanceUtilization) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._ApplicationInstanceUtilization(ctx, sel, v) +} + func (ec *executionContext) marshalNApplicationManifest2githubᚗcomᚋnaisᚋapiᚋinternalᚋworkloadᚋapplicationᚐApplicationManifest(ctx context.Context, sel ast.SelectionSet, v application.ApplicationManifest) graphql.Marshaler { return ec._ApplicationManifest(ctx, sel, &v) } @@ -124789,20 +124803,6 @@ func (ec *executionContext) marshalNIngressType2githubᚗcomᚋnaisᚋapiᚋinte return v } -func (ec *executionContext) marshalNInstanceUtilization2githubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐInstanceUtilization(ctx context.Context, sel ast.SelectionSet, v utilization.InstanceUtilization) graphql.Marshaler { - return ec._InstanceUtilization(ctx, sel, &v) -} - -func (ec *executionContext) marshalNInstanceUtilization2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋutilizationᚐInstanceUtilization(ctx context.Context, sel ast.SelectionSet, v *utilization.InstanceUtilization) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "the requested element is null which the schema does not allow") - } - return graphql.Null - } - return ec._InstanceUtilization(ctx, sel, v) -} - func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v any) (int, error) { res, err := graphql.UnmarshalInt(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/internal/graph/schema/utilization.graphqls b/internal/graph/schema/utilization.graphqls index 2fad152df..4230f0968 100644 --- a/internal/graph/schema/utilization.graphqls +++ b/internal/graph/schema/utilization.graphqls @@ -8,7 +8,7 @@ extend type Team { } extend type ApplicationInstance { - instanceUtilization(resourceType: UtilizationResourceType!): InstanceUtilization! + instanceUtilization(resourceType: UtilizationResourceType!): ApplicationInstanceUtilization! } extend type Query { @@ -88,7 +88,7 @@ type TeamUtilizationData { teamEnvironment: TeamEnvironment! } -type InstanceUtilization { +type ApplicationInstanceUtilization { "Get the current usage for the requested resource type." current: Float! } diff --git a/internal/graph/utilization.resolvers.go b/internal/graph/utilization.resolvers.go index 81e31b107..af4335d05 100644 --- a/internal/graph/utilization.resolvers.go +++ b/internal/graph/utilization.resolvers.go @@ -19,7 +19,7 @@ func (r *applicationResolver) Utilization(ctx context.Context, obj *application. }, nil } -func (r *applicationInstanceResolver) InstanceUtilization(ctx context.Context, obj *application.ApplicationInstance, resourceType utilization.UtilizationResourceType) (*utilization.InstanceUtilization, error) { +func (r *applicationInstanceResolver) InstanceUtilization(ctx context.Context, obj *application.ApplicationInstance, resourceType utilization.UtilizationResourceType) (*utilization.ApplicationInstanceUtilization, error) { return utilization.ForInstance(ctx, r.unmappedEnvironmentName(obj.EnvironmentName), obj.TeamSlug, obj.ApplicationName, obj.Name, resourceType) } diff --git a/internal/utilization/model.go b/internal/utilization/model.go index 39e092df9..ee6b92689 100644 --- a/internal/utilization/model.go +++ b/internal/utilization/model.go @@ -118,7 +118,7 @@ type TeamServiceUtilization struct { TeamSlug slug.Slug `json:"-"` } -type InstanceUtilization struct { +type ApplicationInstanceUtilization struct { // Get the current usage for the requested resource type. Current float64 `json:"current"` } diff --git a/internal/utilization/queries.go b/internal/utilization/queries.go index c3c3ff873..3a5c2c48a 100644 --- a/internal/utilization/queries.go +++ b/internal/utilization/queries.go @@ -37,7 +37,7 @@ var ( ignoredNamespaces = strings.Join([]string{"kube-system", "nais-system", "cnrm-system", "configconnector-operator-system", "linkerd", "gke-mcs", "gke-managed-system", "kyverno", "default", "kube-node-lease", "kube-public"}, "|") ) -func ForInstance(ctx context.Context, env string, teamSlug slug.Slug, workloadName string, instanceName string, resourceType UtilizationResourceType) (*InstanceUtilization, error) { +func ForInstance(ctx context.Context, env string, teamSlug slug.Slug, workloadName string, instanceName string, resourceType UtilizationResourceType) (*ApplicationInstanceUtilization, error) { usageQ := instanceMemoryUsage if resourceType == UtilizationResourceTypeCPU { @@ -51,7 +51,7 @@ func ForInstance(ctx context.Context, env string, teamSlug slug.Slug, workloadNa return nil, err } - return &InstanceUtilization{ + return &ApplicationInstanceUtilization{ Current: ensuredVal(current), }, nil }