From e49e5a04741900c91d167987bef5db524b624f48 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Wed, 5 Nov 2025 18:17:29 +0100 Subject: [PATCH] finalize timed node state backend code, concat functions --- api/schema.graphqls | 7 +- internal/graph/generated/generated.go | 137 ++++++++------------------ internal/graph/model/models_gen.go | 7 +- internal/graph/schema.resolvers.go | 40 ++++---- internal/repository/node.go | 46 +++++---- 5 files changed, 96 insertions(+), 141 deletions(-) diff --git a/api/schema.graphqls b/api/schema.graphqls index 4ee573c..410bdd5 100644 --- a/api/schema.graphqls +++ b/api/schema.graphqls @@ -28,9 +28,8 @@ type NodeStates { type NodeStatesTimed { state: String! - type: String! - count: Int! - time: Int! + counts: [Int!]! + times: [Int!]! } type Job { @@ -317,7 +316,7 @@ type Query { node(id: ID!): Node nodes(filter: [NodeFilter!], order: OrderByInput): NodeStateResultList! nodeStates(filter: [NodeFilter!]): [NodeStates!]! - nodeStatesTimed(filter: [NodeFilter!]): [NodeStatesTimed!]! + nodeStatesTimed(filter: [NodeFilter!], type: String!): [NodeStatesTimed!]! job(id: ID!): Job jobMetrics( diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index e1baf4c..e5f59e4 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -302,10 +302,9 @@ type ComplexityRoot struct { } NodeStatesTimed struct { - Count func(childComplexity int) int - State func(childComplexity int) int - Time func(childComplexity int) int - Type func(childComplexity int) int + Counts func(childComplexity int) int + State func(childComplexity int) int + Times func(childComplexity int) int } NodesResultList struct { @@ -332,7 +331,7 @@ type ComplexityRoot struct { NodeMetrics func(childComplexity int, cluster string, nodes []string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time) int NodeMetricsList func(childComplexity int, cluster string, subCluster string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) int NodeStates func(childComplexity int, filter []*model.NodeFilter) int - NodeStatesTimed func(childComplexity int, filter []*model.NodeFilter) int + NodeStatesTimed func(childComplexity int, filter []*model.NodeFilter, typeArg string) int Nodes func(childComplexity int, filter []*model.NodeFilter, order *model.OrderByInput) int RooflineHeatmap func(childComplexity int, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) int ScopedJobStats func(childComplexity int, id string, metrics []string, scopes []schema.MetricScope) int @@ -473,7 +472,7 @@ type QueryResolver interface { Node(ctx context.Context, id string) (*schema.Node, error) Nodes(ctx context.Context, filter []*model.NodeFilter, order *model.OrderByInput) (*model.NodeStateResultList, error) NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error) - NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStatesTimed, error) + NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter, typeArg string) ([]*model.NodeStatesTimed, error) Job(ctx context.Context, id string) (*schema.Job, error) JobMetrics(ctx context.Context, id string, metrics []string, scopes []schema.MetricScope, resolution *int) ([]*model.JobMetricWithName, error) JobStats(ctx context.Context, id string, metrics []string) ([]*model.NamedStats, error) @@ -1617,12 +1616,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.NodeStates.State(childComplexity), true - case "NodeStatesTimed.count": - if e.complexity.NodeStatesTimed.Count == nil { + case "NodeStatesTimed.counts": + if e.complexity.NodeStatesTimed.Counts == nil { break } - return e.complexity.NodeStatesTimed.Count(childComplexity), true + return e.complexity.NodeStatesTimed.Counts(childComplexity), true case "NodeStatesTimed.state": if e.complexity.NodeStatesTimed.State == nil { @@ -1631,19 +1630,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.NodeStatesTimed.State(childComplexity), true - case "NodeStatesTimed.time": - if e.complexity.NodeStatesTimed.Time == nil { + case "NodeStatesTimed.times": + if e.complexity.NodeStatesTimed.Times == nil { break } - return e.complexity.NodeStatesTimed.Time(childComplexity), true - - case "NodeStatesTimed.type": - if e.complexity.NodeStatesTimed.Type == nil { - break - } - - return e.complexity.NodeStatesTimed.Type(childComplexity), true + return e.complexity.NodeStatesTimed.Times(childComplexity), true case "NodesResultList.count": if e.complexity.NodesResultList.Count == nil { @@ -1855,7 +1847,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Query.NodeStatesTimed(childComplexity, args["filter"].([]*model.NodeFilter)), true + return e.complexity.Query.NodeStatesTimed(childComplexity, args["filter"].([]*model.NodeFilter), args["type"].(string)), true case "Query.nodes": if e.complexity.Query.Nodes == nil { @@ -2441,9 +2433,8 @@ type NodeStates { type NodeStatesTimed { state: String! - type: String! - count: Int! - time: Int! + counts: [Int!]! + times: [Int!]! } type Job { @@ -2730,7 +2721,7 @@ type Query { node(id: ID!): Node nodes(filter: [NodeFilter!], order: OrderByInput): NodeStateResultList! nodeStates(filter: [NodeFilter!]): [NodeStates!]! - nodeStatesTimed(filter: [NodeFilter!]): [NodeStatesTimed!]! + nodeStatesTimed(filter: [NodeFilter!], type: String!): [NodeStatesTimed!]! job(id: ID!): Job jobMetrics( @@ -3315,6 +3306,11 @@ func (ec *executionContext) field_Query_nodeStatesTimed_args(ctx context.Context return nil, err } args["filter"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "type", ec.unmarshalNString2string) + if err != nil { + return nil, err + } + args["type"] = arg1 return args, nil } @@ -10630,8 +10626,8 @@ func (ec *executionContext) fieldContext_NodeStatesTimed_state(_ context.Context return fc, nil } -func (ec *executionContext) _NodeStatesTimed_type(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_NodeStatesTimed_type(ctx, field) +func (ec *executionContext) _NodeStatesTimed_counts(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_NodeStatesTimed_counts(ctx, field) if err != nil { return graphql.Null } @@ -10644,7 +10640,7 @@ func (ec *executionContext) _NodeStatesTimed_type(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return obj.Type, nil + return obj.Counts, nil }) if err != nil { ec.Error(ctx, err) @@ -10656,56 +10652,12 @@ func (ec *executionContext) _NodeStatesTimed_type(ctx context.Context, field gra } return graphql.Null } - res := resTmp.(string) + res := resTmp.([]int) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNInt2ᚕintᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_NodeStatesTimed_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "NodeStatesTimed", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _NodeStatesTimed_count(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_NodeStatesTimed_count(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.Count, 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.(int) - fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_NodeStatesTimed_count(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_NodeStatesTimed_counts(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "NodeStatesTimed", Field: field, @@ -10718,8 +10670,8 @@ func (ec *executionContext) fieldContext_NodeStatesTimed_count(_ context.Context return fc, nil } -func (ec *executionContext) _NodeStatesTimed_time(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_NodeStatesTimed_time(ctx, field) +func (ec *executionContext) _NodeStatesTimed_times(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_NodeStatesTimed_times(ctx, field) if err != nil { return graphql.Null } @@ -10732,7 +10684,7 @@ func (ec *executionContext) _NodeStatesTimed_time(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return obj.Time, nil + return obj.Times, nil }) if err != nil { ec.Error(ctx, err) @@ -10744,12 +10696,12 @@ func (ec *executionContext) _NodeStatesTimed_time(ctx context.Context, field gra } return graphql.Null } - res := resTmp.(int) + res := resTmp.([]int) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNInt2ᚕintᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_NodeStatesTimed_time(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_NodeStatesTimed_times(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "NodeStatesTimed", Field: field, @@ -11514,7 +11466,7 @@ func (ec *executionContext) _Query_nodeStatesTimed(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().NodeStatesTimed(rctx, fc.Args["filter"].([]*model.NodeFilter)) + return ec.resolvers.Query().NodeStatesTimed(rctx, fc.Args["filter"].([]*model.NodeFilter), fc.Args["type"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -11541,12 +11493,10 @@ func (ec *executionContext) fieldContext_Query_nodeStatesTimed(ctx context.Conte switch field.Name { case "state": return ec.fieldContext_NodeStatesTimed_state(ctx, field) - case "type": - return ec.fieldContext_NodeStatesTimed_type(ctx, field) - case "count": - return ec.fieldContext_NodeStatesTimed_count(ctx, field) - case "time": - return ec.fieldContext_NodeStatesTimed_time(ctx, field) + case "counts": + return ec.fieldContext_NodeStatesTimed_counts(ctx, field) + case "times": + return ec.fieldContext_NodeStatesTimed_times(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type NodeStatesTimed", field.Name) }, @@ -19555,18 +19505,13 @@ func (ec *executionContext) _NodeStatesTimed(ctx context.Context, sel ast.Select if out.Values[i] == graphql.Null { out.Invalids++ } - case "type": - out.Values[i] = ec._NodeStatesTimed_type(ctx, field, obj) + case "counts": + out.Values[i] = ec._NodeStatesTimed_counts(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "count": - out.Values[i] = ec._NodeStatesTimed_count(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "time": - out.Values[i] = ec._NodeStatesTimed_time(ctx, field, obj) + case "times": + out.Values[i] = ec._NodeStatesTimed_times(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index 7b64464..cd9bc87 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -196,10 +196,9 @@ type NodeStates struct { } type NodeStatesTimed struct { - State string `json:"state"` - Type string `json:"type"` - Count int `json:"count"` - Time int `json:"time"` + State string `json:"state"` + Counts []int `json:"counts"` + Times []int `json:"times"` } type NodesResultList struct { diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index 15bc6df..9f91397 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -387,13 +387,13 @@ func (r *queryResolver) Nodes(ctx context.Context, filter []*model.NodeFilter, o func (r *queryResolver) NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error) { repo := repository.GetNodeRepository() - stateCounts, serr := repo.CountNodeStates(ctx, filter) + stateCounts, serr := repo.CountStates(ctx, filter, "node_state") if serr != nil { cclog.Warnf("Error while counting nodeStates: %s", serr.Error()) return nil, serr } - healthCounts, herr := repo.CountHealthStates(ctx, filter) + healthCounts, herr := repo.CountStates(ctx, filter, "health_state") if herr != nil { cclog.Warnf("Error while counting healthStates: %s", herr.Error()) return nil, herr @@ -406,26 +406,28 @@ func (r *queryResolver) NodeStates(ctx context.Context, filter []*model.NodeFilt } // NodeStatesTimed is the resolver for the nodeStatesTimed field. -func (r *queryResolver) NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStatesTimed, error) { - panic(fmt.Errorf("not implemented: NodeStatesTimed - NodeStatesTimed")) - // repo := repository.GetNodeRepository() +func (r *queryResolver) NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter, typeArg string) ([]*model.NodeStatesTimed, error) { + repo := repository.GetNodeRepository() - // stateCounts, serr := repo.CountNodeStates(ctx, filter) - // if serr != nil { - // cclog.Warnf("Error while counting nodeStates: %s", serr.Error()) - // return nil, serr - // } + if typeArg == "node" { + stateCounts, serr := repo.CountStatesTimed(ctx, filter, "node_state") + if serr != nil { + cclog.Warnf("Error while counting nodeStates in time: %s", serr.Error()) + return nil, serr + } + return stateCounts, nil + } - // healthCounts, herr := repo.CountHealthStates(ctx, filter) - // if herr != nil { - // cclog.Warnf("Error while counting healthStates: %s", herr.Error()) - // return nil, herr - // } + if typeArg == "health" { + healthCounts, herr := repo.CountStatesTimed(ctx, filter, "health_state") + if herr != nil { + cclog.Warnf("Error while counting healthStates in time: %s", herr.Error()) + return nil, herr + } + return healthCounts, nil + } - // allCounts := make([]*model.NodeStates, 0) - // allCounts = append(stateCounts, healthCounts...) - - // return allCounts, nil + return nil, errors.New("Unknown Node State Query Type") } // Job is the resolver for the job field. diff --git a/internal/repository/node.go b/internal/repository/node.go index c3152f4..3115b9d 100644 --- a/internal/repository/node.go +++ b/internal/repository/node.go @@ -357,8 +357,8 @@ func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) { return nodeList, nil } -func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { - query, qerr := AccessCheck(ctx, sq.Select("hostname", "node_state", "MAX(time_stamp) as time").From("node")) +func (r *NodeRepository) CountStates(ctx context.Context, filters []*model.NodeFilter, column string) ([]*model.NodeStates, error) { + query, qerr := AccessCheck(ctx, sq.Select("hostname", column, "MAX(time_stamp) as time").From("node")) if qerr != nil { return nil, qerr } @@ -395,16 +395,16 @@ func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.N stateMap := map[string]int{} for rows.Next() { - var hostname, node_state string + var hostname, state string var timestamp int64 - if err := rows.Scan(&hostname, &node_state, ×tamp); err != nil { + if err := rows.Scan(&hostname, &state, ×tamp); err != nil { rows.Close() cclog.Warn("Error while scanning rows (NodeStates)") return nil, err } - stateMap[node_state] += 1 + stateMap[state] += 1 } nodes := make([]*model.NodeStates, 0) @@ -416,8 +416,8 @@ func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.N return nodes, nil } -func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { - query, qerr := AccessCheck(ctx, sq.Select("hostname", "health_state", "MAX(time_stamp) as time").From("node")) +func (r *NodeRepository) CountStatesTimed(ctx context.Context, filters []*model.NodeFilter, column string) ([]*model.NodeStatesTimed, error) { + query, qerr := AccessCheck(ctx, sq.Select(column, "time_stamp", "count(*) as count").From("node")) // "cluster"? if qerr != nil { return nil, qerr } @@ -425,6 +425,11 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model query = query.Join("node_state ON node_state.node_id = node.id") for _, f := range filters { + // Required + if f.TimeStart != nil { + query = query.Where("time_stamp > ?", f.TimeStart) + } + // Optional if f.Hostname != nil { query = buildStringCondition("hostname", f.Hostname, query) } @@ -443,7 +448,7 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model } // Add Group and Order - query = query.GroupBy("hostname").OrderBy("hostname DESC") + query = query.GroupBy(column + ", time_stamp").OrderBy("time_stamp ASC") rows, err := query.RunWith(r.stmtCache).Query() if err != nil { @@ -452,27 +457,32 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model return nil, err } - stateMap := map[string]int{} + rawData := make(map[string][][]int) for rows.Next() { - var hostname, health_state string - var timestamp int64 + var state string + var time, count int - if err := rows.Scan(&hostname, &health_state, ×tamp); err != nil { + if err := rows.Scan(&state, &time, &count); err != nil { rows.Close() cclog.Warn("Error while scanning rows (NodeStates)") return nil, err } - stateMap[health_state] += 1 + if rawData[state] == nil { + rawData[state] = [][]int{make([]int, 0), make([]int, 0)} + } + + rawData[state][0] = append(rawData[state][0], time) + rawData[state][1] = append(rawData[state][1], count) } - nodes := make([]*model.NodeStates, 0) - for state, counts := range stateMap { - node := model.NodeStates{State: state, Count: counts} - nodes = append(nodes, &node) + timedStates := make([]*model.NodeStatesTimed, 0) + for state, data := range rawData { + entry := model.NodeStatesTimed{State: state, Times: data[0], Counts: data[1]} + timedStates = append(timedStates, &entry) } - return nodes, nil + return timedStates, nil } func AccessCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) {