mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-31 16:05:06 +01:00 
			
		
		
		
	start working on non-node-scoped metrics in node/system view
This commit is contained in:
		| @@ -156,13 +156,8 @@ type ComplexityRoot struct { | ||||
| 		UpdateConfiguration func(childComplexity int, name string, value string) int | ||||
| 	} | ||||
|  | ||||
| 	NodeMetric struct { | ||||
| 		Data func(childComplexity int) int | ||||
| 		Name func(childComplexity int) int | ||||
| 	} | ||||
|  | ||||
| 	NodeMetrics struct { | ||||
| 		ID      func(childComplexity int) int | ||||
| 		Host    func(childComplexity int) int | ||||
| 		Metrics func(childComplexity int) int | ||||
| 	} | ||||
|  | ||||
| @@ -185,7 +180,7 @@ type ComplexityRoot struct { | ||||
| 		Jobs            func(childComplexity int, filter []*model.JobFilter, page *model.PageRequest, order *model.OrderByInput) int | ||||
| 		JobsFootprints  func(childComplexity int, filter []*model.JobFilter, metrics []string) int | ||||
| 		JobsStatistics  func(childComplexity int, filter []*model.JobFilter, groupBy *model.Aggregate) int | ||||
| 		NodeMetrics     func(childComplexity int, cluster string, nodes []string, metrics []string, from time.Time, to time.Time) int | ||||
| 		NodeMetrics     func(childComplexity int, cluster string, partition string, nodes []string, scopes []string, metrics []string, from time.Time, to time.Time) int | ||||
| 		RooflineHeatmap func(childComplexity int, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) int | ||||
| 		Tags            func(childComplexity int) int | ||||
| 	} | ||||
| @@ -250,7 +245,7 @@ type QueryResolver interface { | ||||
| 	Jobs(ctx context.Context, filter []*model.JobFilter, page *model.PageRequest, order *model.OrderByInput) (*model.JobResultList, error) | ||||
| 	JobsStatistics(ctx context.Context, filter []*model.JobFilter, groupBy *model.Aggregate) ([]*model.JobsStatistics, error) | ||||
| 	RooflineHeatmap(ctx context.Context, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) ([][]float64, error) | ||||
| 	NodeMetrics(ctx context.Context, cluster string, nodes []string, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error) | ||||
| 	NodeMetrics(ctx context.Context, cluster string, partition string, nodes []string, scopes []string, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error) | ||||
| } | ||||
|  | ||||
| type executableSchema struct { | ||||
| @@ -769,26 +764,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in | ||||
|  | ||||
| 		return e.complexity.Mutation.UpdateConfiguration(childComplexity, args["name"].(string), args["value"].(string)), true | ||||
|  | ||||
| 	case "NodeMetric.data": | ||||
| 		if e.complexity.NodeMetric.Data == nil { | ||||
| 	case "NodeMetrics.host": | ||||
| 		if e.complexity.NodeMetrics.Host == nil { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		return e.complexity.NodeMetric.Data(childComplexity), true | ||||
|  | ||||
| 	case "NodeMetric.name": | ||||
| 		if e.complexity.NodeMetric.Name == nil { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		return e.complexity.NodeMetric.Name(childComplexity), true | ||||
|  | ||||
| 	case "NodeMetrics.id": | ||||
| 		if e.complexity.NodeMetrics.ID == nil { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		return e.complexity.NodeMetrics.ID(childComplexity), true | ||||
| 		return e.complexity.NodeMetrics.Host(childComplexity), true | ||||
|  | ||||
| 	case "NodeMetrics.metrics": | ||||
| 		if e.complexity.NodeMetrics.Metrics == nil { | ||||
| @@ -937,7 +918,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in | ||||
| 			return 0, false | ||||
| 		} | ||||
|  | ||||
| 		return e.complexity.Query.NodeMetrics(childComplexity, args["cluster"].(string), args["nodes"].([]string), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time)), true | ||||
| 		return e.complexity.Query.NodeMetrics(childComplexity, args["cluster"].(string), args["partition"].(string), args["nodes"].([]string), args["scopes"].([]string), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time)), true | ||||
|  | ||||
| 	case "Query.rooflineHeatmap": | ||||
| 		if e.complexity.Query.RooflineHeatmap == nil { | ||||
| @@ -1299,19 +1280,14 @@ type MetricFootprints { | ||||
|  | ||||
| enum Aggregate { USER, PROJECT, CLUSTER } | ||||
|  | ||||
| type NodeMetric { | ||||
|   name: String! | ||||
|   data: [NullableFloat!]! | ||||
| } | ||||
|  | ||||
| type NodeMetrics { | ||||
|   id:      String! | ||||
|   metrics: [NodeMetric!]! | ||||
|   host:    String! | ||||
|   metrics: [JobMetricWithName!]! | ||||
| } | ||||
|  | ||||
| type Query { | ||||
|   clusters:     [Cluster!]!   # List of all clusters | ||||
|   tags:         [Tag!]!    # List of all tags | ||||
|   tags:         [Tag!]!       # List of all tags | ||||
|  | ||||
|   job(id: ID!): Job | ||||
|   jobMetrics(id: ID!, metrics: [String!], scopes: [MetricScope!]): [JobMetricWithName!]! | ||||
| @@ -1322,7 +1298,7 @@ type Query { | ||||
|  | ||||
|   rooflineHeatmap(filter: [JobFilter!]!, rows: Int!, cols: Int!, minX: Float!, minY: Float!, maxX: Float!, maxY: Float!): [[Float!]!]! | ||||
|  | ||||
|   nodeMetrics(cluster: ID!, nodes: [String!], metrics: [String!], from: Time!, to: Time!): [NodeMetrics!]! | ||||
|   nodeMetrics(cluster: String!, partition: String!, nodes: [String!], scopes: [String!], metrics: [String!], from: Time!, to: Time!): [NodeMetrics!]! | ||||
| } | ||||
|  | ||||
| type Mutation { | ||||
| @@ -1681,48 +1657,66 @@ func (ec *executionContext) field_Query_nodeMetrics_args(ctx context.Context, ra | ||||
| 	var arg0 string | ||||
| 	if tmp, ok := rawArgs["cluster"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("cluster")) | ||||
| 		arg0, err = ec.unmarshalNID2string(ctx, tmp) | ||||
| 		arg0, err = ec.unmarshalNString2string(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["cluster"] = arg0 | ||||
| 	var arg1 []string | ||||
| 	if tmp, ok := rawArgs["nodes"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nodes")) | ||||
| 		arg1, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) | ||||
| 	var arg1 string | ||||
| 	if tmp, ok := rawArgs["partition"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("partition")) | ||||
| 		arg1, err = ec.unmarshalNString2string(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["nodes"] = arg1 | ||||
| 	args["partition"] = arg1 | ||||
| 	var arg2 []string | ||||
| 	if tmp, ok := rawArgs["metrics"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("metrics")) | ||||
| 	if tmp, ok := rawArgs["nodes"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nodes")) | ||||
| 		arg2, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["metrics"] = arg2 | ||||
| 	var arg3 time.Time | ||||
| 	args["nodes"] = arg2 | ||||
| 	var arg3 []string | ||||
| 	if tmp, ok := rawArgs["scopes"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scopes")) | ||||
| 		arg3, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["scopes"] = arg3 | ||||
| 	var arg4 []string | ||||
| 	if tmp, ok := rawArgs["metrics"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("metrics")) | ||||
| 		arg4, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["metrics"] = arg4 | ||||
| 	var arg5 time.Time | ||||
| 	if tmp, ok := rawArgs["from"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("from")) | ||||
| 		arg3, err = ec.unmarshalNTime2timeᚐTime(ctx, tmp) | ||||
| 		arg5, err = ec.unmarshalNTime2timeᚐTime(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["from"] = arg3 | ||||
| 	var arg4 time.Time | ||||
| 	args["from"] = arg5 | ||||
| 	var arg6 time.Time | ||||
| 	if tmp, ok := rawArgs["to"]; ok { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("to")) | ||||
| 		arg4, err = ec.unmarshalNTime2timeᚐTime(ctx, tmp) | ||||
| 		arg6, err = ec.unmarshalNTime2timeᚐTime(ctx, tmp) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	args["to"] = arg4 | ||||
| 	args["to"] = arg6 | ||||
| 	return args, nil | ||||
| } | ||||
|  | ||||
| @@ -4230,77 +4224,7 @@ func (ec *executionContext) _Mutation_updateConfiguration(ctx context.Context, f | ||||
| 	return ec.marshalOString2ᚖstring(ctx, field.Selections, res) | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) _NodeMetric_name(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetric) (ret graphql.Marshaler) { | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			ec.Error(ctx, ec.Recover(ctx, r)) | ||||
| 			ret = graphql.Null | ||||
| 		} | ||||
| 	}() | ||||
| 	fc := &graphql.FieldContext{ | ||||
| 		Object:     "NodeMetric", | ||||
| 		Field:      field, | ||||
| 		Args:       nil, | ||||
| 		IsMethod:   false, | ||||
| 		IsResolver: false, | ||||
| 	} | ||||
|  | ||||
| 	ctx = graphql.WithFieldContext(ctx, fc) | ||||
| 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { | ||||
| 		ctx = rctx // use context from middleware stack in children | ||||
| 		return obj.Name, 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.(string) | ||||
| 	fc.Result = res | ||||
| 	return ec.marshalNString2string(ctx, field.Selections, res) | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) _NodeMetric_data(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetric) (ret graphql.Marshaler) { | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			ec.Error(ctx, ec.Recover(ctx, r)) | ||||
| 			ret = graphql.Null | ||||
| 		} | ||||
| 	}() | ||||
| 	fc := &graphql.FieldContext{ | ||||
| 		Object:     "NodeMetric", | ||||
| 		Field:      field, | ||||
| 		Args:       nil, | ||||
| 		IsMethod:   false, | ||||
| 		IsResolver: false, | ||||
| 	} | ||||
|  | ||||
| 	ctx = graphql.WithFieldContext(ctx, fc) | ||||
| 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { | ||||
| 		ctx = rctx // use context from middleware stack in children | ||||
| 		return obj.Data, 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.([]schema.Float) | ||||
| 	fc.Result = res | ||||
| 	return ec.marshalNNullableFloat2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋschemaᚐFloatᚄ(ctx, field.Selections, res) | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) _NodeMetrics_id(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) { | ||||
| func (ec *executionContext) _NodeMetrics_host(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) { | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			ec.Error(ctx, ec.Recover(ctx, r)) | ||||
| @@ -4318,7 +4242,7 @@ func (ec *executionContext) _NodeMetrics_id(ctx context.Context, field graphql.C | ||||
| 	ctx = graphql.WithFieldContext(ctx, fc) | ||||
| 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { | ||||
| 		ctx = rctx // use context from middleware stack in children | ||||
| 		return obj.ID, nil | ||||
| 		return obj.Host, nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ec.Error(ctx, err) | ||||
| @@ -4365,9 +4289,9 @@ func (ec *executionContext) _NodeMetrics_metrics(ctx context.Context, field grap | ||||
| 		} | ||||
| 		return graphql.Null | ||||
| 	} | ||||
| 	res := resTmp.([]*model.NodeMetric) | ||||
| 	res := resTmp.([]*model.JobMetricWithName) | ||||
| 	fc.Result = res | ||||
| 	return ec.marshalNNodeMetric2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐNodeMetricᚄ(ctx, field.Selections, res) | ||||
| 	return ec.marshalNJobMetricWithName2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐJobMetricWithNameᚄ(ctx, field.Selections, res) | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) _Partition_name(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { | ||||
| @@ -5029,7 +4953,7 @@ func (ec *executionContext) _Query_nodeMetrics(ctx context.Context, field graphq | ||||
| 	fc.Args = args | ||||
| 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { | ||||
| 		ctx = rctx // use context from middleware stack in children | ||||
| 		return ec.resolvers.Query().NodeMetrics(rctx, args["cluster"].(string), args["nodes"].([]string), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time)) | ||||
| 		return ec.resolvers.Query().NodeMetrics(rctx, args["cluster"].(string), args["partition"].(string), args["nodes"].([]string), args["scopes"].([]string), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time)) | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ec.Error(ctx, err) | ||||
| @@ -7928,38 +7852,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| var nodeMetricImplementors = []string{"NodeMetric"} | ||||
|  | ||||
| func (ec *executionContext) _NodeMetric(ctx context.Context, sel ast.SelectionSet, obj *model.NodeMetric) graphql.Marshaler { | ||||
| 	fields := graphql.CollectFields(ec.OperationContext, sel, nodeMetricImplementors) | ||||
|  | ||||
| 	out := graphql.NewFieldSet(fields) | ||||
| 	var invalids uint32 | ||||
| 	for i, field := range fields { | ||||
| 		switch field.Name { | ||||
| 		case "__typename": | ||||
| 			out.Values[i] = graphql.MarshalString("NodeMetric") | ||||
| 		case "name": | ||||
| 			out.Values[i] = ec._NodeMetric_name(ctx, field, obj) | ||||
| 			if out.Values[i] == graphql.Null { | ||||
| 				invalids++ | ||||
| 			} | ||||
| 		case "data": | ||||
| 			out.Values[i] = ec._NodeMetric_data(ctx, field, obj) | ||||
| 			if out.Values[i] == graphql.Null { | ||||
| 				invalids++ | ||||
| 			} | ||||
| 		default: | ||||
| 			panic("unknown field " + strconv.Quote(field.Name)) | ||||
| 		} | ||||
| 	} | ||||
| 	out.Dispatch() | ||||
| 	if invalids > 0 { | ||||
| 		return graphql.Null | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| var nodeMetricsImplementors = []string{"NodeMetrics"} | ||||
|  | ||||
| func (ec *executionContext) _NodeMetrics(ctx context.Context, sel ast.SelectionSet, obj *model.NodeMetrics) graphql.Marshaler { | ||||
| @@ -7971,8 +7863,8 @@ func (ec *executionContext) _NodeMetrics(ctx context.Context, sel ast.SelectionS | ||||
| 		switch field.Name { | ||||
| 		case "__typename": | ||||
| 			out.Values[i] = graphql.MarshalString("NodeMetrics") | ||||
| 		case "id": | ||||
| 			out.Values[i] = ec._NodeMetrics_id(ctx, field, obj) | ||||
| 		case "host": | ||||
| 			out.Values[i] = ec._NodeMetrics_host(ctx, field, obj) | ||||
| 			if out.Values[i] == graphql.Null { | ||||
| 				invalids++ | ||||
| 			} | ||||
| @@ -9310,53 +9202,6 @@ func (ec *executionContext) marshalNMetricScope2githubᚗcomᚋClusterCockpitᚋ | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) marshalNNodeMetric2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐNodeMetricᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.NodeMetric) graphql.Marshaler { | ||||
| 	ret := make(graphql.Array, len(v)) | ||||
| 	var wg sync.WaitGroup | ||||
| 	isLen1 := len(v) == 1 | ||||
| 	if !isLen1 { | ||||
| 		wg.Add(len(v)) | ||||
| 	} | ||||
| 	for i := range v { | ||||
| 		i := i | ||||
| 		fc := &graphql.FieldContext{ | ||||
| 			Index:  &i, | ||||
| 			Result: &v[i], | ||||
| 		} | ||||
| 		ctx := graphql.WithFieldContext(ctx, fc) | ||||
| 		f := func(i int) { | ||||
| 			defer func() { | ||||
| 				if r := recover(); r != nil { | ||||
| 					ec.Error(ctx, ec.Recover(ctx, r)) | ||||
| 					ret = nil | ||||
| 				} | ||||
| 			}() | ||||
| 			if !isLen1 { | ||||
| 				defer wg.Done() | ||||
| 			} | ||||
| 			ret[i] = ec.marshalNNodeMetric2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐNodeMetric(ctx, sel, v[i]) | ||||
| 		} | ||||
| 		if isLen1 { | ||||
| 			f(i) | ||||
| 		} else { | ||||
| 			go f(i) | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| 	wg.Wait() | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) marshalNNodeMetric2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐNodeMetric(ctx context.Context, sel ast.SelectionSet, v *model.NodeMetric) graphql.Marshaler { | ||||
| 	if v == nil { | ||||
| 		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { | ||||
| 			ec.Errorf(ctx, "must not be null") | ||||
| 		} | ||||
| 		return graphql.Null | ||||
| 	} | ||||
| 	return ec._NodeMetric(ctx, sel, v) | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) marshalNNodeMetrics2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐNodeMetricsᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.NodeMetrics) graphql.Marshaler { | ||||
| 	ret := make(graphql.Array, len(v)) | ||||
| 	var wg sync.WaitGroup | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package model | ||||
|  | ||||
| import "strconv" | ||||
|  | ||||
| type Cluster struct { | ||||
| 	Name         string          `json:"name"` | ||||
| 	MetricConfig []*MetricConfig `json:"metricConfig"` | ||||
| @@ -76,3 +78,15 @@ func (topo *Topology) GetCoresFromHWThreads(hwthreads []int) (cores []int, exclu | ||||
|  | ||||
| 	return cores, exclusive | ||||
| } | ||||
|  | ||||
| func (topo *Topology) GetAcceleratorIDs() ([]int, error) { | ||||
| 	accels := make([]int, 0) | ||||
| 	for _, accel := range topo.Accelerators { | ||||
| 		id, err := strconv.Atoi(accel.ID) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		accels = append(accels, id) | ||||
| 	} | ||||
| 	return accels, nil | ||||
| } | ||||
|   | ||||
| @@ -101,14 +101,9 @@ type MetricFootprints struct { | ||||
| 	Footprints []schema.Float `json:"footprints"` | ||||
| } | ||||
|  | ||||
| type NodeMetric struct { | ||||
| 	Name string         `json:"name"` | ||||
| 	Data []schema.Float `json:"data"` | ||||
| } | ||||
|  | ||||
| type NodeMetrics struct { | ||||
| 	ID      string        `json:"id"` | ||||
| 	Metrics []*NodeMetric `json:"metrics"` | ||||
| 	Host    string               `json:"host"` | ||||
| 	Metrics []*JobMetricWithName `json:"metrics"` | ||||
| } | ||||
|  | ||||
| type OrderByInput struct { | ||||
|   | ||||
| @@ -121,19 +121,14 @@ type MetricFootprints { | ||||
|  | ||||
| enum Aggregate { USER, PROJECT, CLUSTER } | ||||
|  | ||||
| type NodeMetric { | ||||
|   name: String! | ||||
|   data: [NullableFloat!]! | ||||
| } | ||||
|  | ||||
| type NodeMetrics { | ||||
|   id:      String! | ||||
|   metrics: [NodeMetric!]! | ||||
|   host:    String! | ||||
|   metrics: [JobMetricWithName!]! | ||||
| } | ||||
|  | ||||
| type Query { | ||||
|   clusters:     [Cluster!]!   # List of all clusters | ||||
|   tags:         [Tag!]!    # List of all tags | ||||
|   tags:         [Tag!]!       # List of all tags | ||||
|  | ||||
|   job(id: ID!): Job | ||||
|   jobMetrics(id: ID!, metrics: [String!], scopes: [MetricScope!]): [JobMetricWithName!]! | ||||
| @@ -144,7 +139,7 @@ type Query { | ||||
|  | ||||
|   rooflineHeatmap(filter: [JobFilter!]!, rows: Int!, cols: Int!, minX: Float!, minY: Float!, maxX: Float!, maxY: Float!): [[Float!]!]! | ||||
|  | ||||
|   nodeMetrics(cluster: ID!, nodes: [String!], metrics: [String!], from: Time!, to: Time!): [NodeMetrics!]! | ||||
|   nodeMetrics(cluster: String!, partition: String!, nodes: [String!], scopes: [String!], metrics: [String!], from: Time!, to: Time!): [NodeMetrics!]! | ||||
| } | ||||
|  | ||||
| type Mutation { | ||||
|   | ||||
| @@ -207,34 +207,13 @@ func (r *queryResolver) RooflineHeatmap(ctx context.Context, filter []*model.Job | ||||
| 	return r.rooflineHeatmap(ctx, filter, rows, cols, minX, minY, maxX, maxY) | ||||
| } | ||||
|  | ||||
| func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, nodes []string, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error) { | ||||
| func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, partition string, nodes []string, scopes []string, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error) { | ||||
| 	user := auth.GetUser(ctx) | ||||
| 	if user != nil && !user.HasRole(auth.RoleAdmin) { | ||||
| 		return nil, errors.New("you need to be an administrator for this query") | ||||
| 	} | ||||
|  | ||||
| 	data, err := metricdata.LoadNodeData(cluster, metrics, nodes, from.Unix(), to.Unix(), ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	res := make([]*model.NodeMetrics, 0, len(data)) | ||||
| 	for node, metrics := range data { | ||||
| 		nodeMetrics := make([]*model.NodeMetric, 0, len(metrics)) | ||||
| 		for metric, data := range metrics { | ||||
| 			nodeMetrics = append(nodeMetrics, &model.NodeMetric{ | ||||
| 				Name: metric, | ||||
| 				Data: data, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		res = append(res, &model.NodeMetrics{ | ||||
| 			ID:      node, | ||||
| 			Metrics: nodeMetrics, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return res, nil | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // Job returns generated.JobResolver implementation. | ||||
|   | ||||
| @@ -449,11 +449,12 @@ func (ccms *CCMetricStore) LoadStats(job *schema.Job, metrics []string, ctx cont | ||||
| 	return stats, nil | ||||
| } | ||||
|  | ||||
| func (ccms *CCMetricStore) LoadNodeData(clusterId string, metrics, nodes []string, from, to int64, ctx context.Context) (map[string]map[string][]schema.Float, error) { | ||||
| // TODO: Support sub-node-scope metrics! For this, the partition of a node needs to be known! | ||||
| func (ccms *CCMetricStore) LoadNodeData(cluster, partition string, metrics, nodes []string, scopes []schema.MetricScope, from, to time.Time, ctx context.Context) (map[string]map[string][]*schema.JobMetric, error) { | ||||
| 	req := ApiQueryRequest{ | ||||
| 		Cluster:   clusterId, | ||||
| 		From:      from, | ||||
| 		To:        to, | ||||
| 		Cluster:   cluster, | ||||
| 		From:      from.Unix(), | ||||
| 		To:        to.Unix(), | ||||
| 		WithStats: false, | ||||
| 		WithData:  true, | ||||
| 	} | ||||
| @@ -476,21 +477,34 @@ func (ccms *CCMetricStore) LoadNodeData(clusterId string, metrics, nodes []strin | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	data := make(map[string]map[string][]schema.Float) | ||||
| 	_ = resBody | ||||
| 	data := make(map[string]map[string][]*schema.JobMetric) | ||||
| 	for i, res := range resBody { | ||||
| 		query := req.Queries[i] | ||||
| 		metric := ccms.toLocalName(query.Metric) | ||||
| 		qdata := res[0] | ||||
| 		if qdata.Error != nil { | ||||
| 			return nil, fmt.Errorf("fetching %s for node %s failed: %s", query.Metric, query.Hostname, *qdata.Error) | ||||
| 		} | ||||
|  | ||||
| 		nodedata, ok := data[query.Hostname] | ||||
| 		hostdata, ok := data[query.Hostname] | ||||
| 		if !ok { | ||||
| 			nodedata = make(map[string][]schema.Float) | ||||
| 			data[query.Hostname] = nodedata | ||||
| 			hostdata = make(map[string][]*schema.JobMetric) | ||||
| 			data[query.Hostname] = hostdata | ||||
| 		} | ||||
|  | ||||
| 		nodedata[ccms.toLocalName(query.Metric)] = qdata.Data | ||||
| 		mc := config.GetMetricConfig(cluster, metric) | ||||
| 		hostdata[query.Metric] = append(hostdata[query.Metric], &schema.JobMetric{ | ||||
| 			Unit:     mc.Unit, | ||||
| 			Scope:    schema.MetricScopeNode, | ||||
| 			Timestep: mc.Timestep, | ||||
| 			Series: []schema.Series{ | ||||
| 				{ | ||||
| 					Hostname: query.Hostname, | ||||
| 					Data:     qdata.Data, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return data, nil | ||||
|   | ||||
| @@ -21,8 +21,8 @@ type MetricDataRepository interface { | ||||
| 	// Return a map of metrics to a map of nodes to the metric statistics of the job. node scope assumed for now. | ||||
| 	LoadStats(job *schema.Job, metrics []string, ctx context.Context) (map[string]map[string]schema.MetricStatistics, error) | ||||
|  | ||||
| 	// Return a map of nodes to a map of metrics to the data for the requested time. | ||||
| 	LoadNodeData(clusterId string, metrics, nodes []string, from, to int64, ctx context.Context) (map[string]map[string][]schema.Float, error) | ||||
| 	// Return a map of hosts to a map of metrics at the requested scopes for that node. | ||||
| 	LoadNodeData(cluster, partition string, metrics, nodes []string, scopes []schema.MetricScope, from, to time.Time, ctx context.Context) (map[string]map[string][]*schema.JobMetric, error) | ||||
| } | ||||
|  | ||||
| var metricDataRepos map[string]MetricDataRepository = map[string]MetricDataRepository{} | ||||
| @@ -152,26 +152,26 @@ func LoadAverages(job *schema.Job, metrics []string, data [][]schema.Float, ctx | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Used for the node/system view. Returns a map of nodes to a map of metrics (at node scope). | ||||
| func LoadNodeData(clusterId string, metrics, nodes []string, from, to int64, ctx context.Context) (map[string]map[string][]schema.Float, error) { | ||||
| 	repo, ok := metricDataRepos[clusterId] | ||||
| // Used for the node/system view. Returns a map of nodes to a map of metrics. | ||||
| func LoadNodeData(cluster, partition string, metrics, nodes []string, scopes []schema.MetricScope, from, to time.Time, ctx context.Context) (map[string]map[string][]*schema.JobMetric, error) { | ||||
| 	repo, ok := metricDataRepos[cluster] | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("no metric data repository configured for '%s'", clusterId) | ||||
| 		return nil, fmt.Errorf("no metric data repository configured for '%s'", cluster) | ||||
| 	} | ||||
|  | ||||
| 	if metrics == nil { | ||||
| 		for _, m := range config.GetClusterConfig(clusterId).MetricConfig { | ||||
| 		for _, m := range config.GetClusterConfig(cluster).MetricConfig { | ||||
| 			metrics = append(metrics, m.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	data, err := repo.LoadNodeData(clusterId, metrics, nodes, from, to, ctx) | ||||
| 	data, err := repo.LoadNodeData(cluster, partition, metrics, nodes, scopes, from, to, ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if data == nil { | ||||
| 		return nil, fmt.Errorf("the metric data repository for '%s' does not support this query", clusterId) | ||||
| 		return nil, fmt.Errorf("the metric data repository for '%s' does not support this query", cluster) | ||||
| 	} | ||||
|  | ||||
| 	return data, nil | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package metricdata | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ClusterCockpit/cc-backend/schema" | ||||
| ) | ||||
| @@ -31,6 +32,6 @@ func (tmdr *TestMetricDataRepository) LoadStats(job *schema.Job, metrics []strin | ||||
| 	panic("TODO") | ||||
| } | ||||
|  | ||||
| func (tmdr *TestMetricDataRepository) LoadNodeData(clusterId string, metrics, nodes []string, from, to int64, ctx context.Context) (map[string]map[string][]schema.Float, error) { | ||||
| func (tmdr *TestMetricDataRepository) LoadNodeData(cluster, partition string, metrics, nodes []string, scopes []schema.MetricScope, from, to time.Time, ctx context.Context) (map[string]map[string][]*schema.JobMetric, error) { | ||||
| 	panic("TODO") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user