diff --git a/api_test.go b/api_test.go index 7fd3fa3..11a2454 100644 --- a/api_test.go +++ b/api_test.go @@ -30,9 +30,14 @@ func setup(t *testing.T) *api.RestApi { const testclusterJson = `{ "name": "testcluster", - "partitions": [ + "subClusters": [ { - "name": "default", + "name": "sc0", + "nodes": "host120,host121,host122" + }, + { + "name": "sc1", + "nodes": "host123,host124,host125", "processorType": "Intel Core i7-4770", "socketsPerNode": 1, "coresPerSocket": 4, @@ -141,7 +146,7 @@ func TestRestApi(t *testing.T) { Timestep: 60, Series: []schema.Series{ { - Hostname: "testhost", + Hostname: "host123", Statistics: &schema.MetricStatistics{Min: 0.1, Avg: 0.2, Max: 0.3}, Data: []schema.Float{0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.3, 0.3, 0.3}, }, @@ -173,7 +178,7 @@ func TestRestApi(t *testing.T) { "tags": [{ "type": "testTagType", "name": "testTagName" }], "resources": [ { - "hostname": "testhost", + "hostname": "host123", "hwthreads": [0, 1, 2, 3, 4, 5, 6, 7] } ], @@ -211,6 +216,7 @@ func TestRestApi(t *testing.T) { job.User != "testuser" || job.Project != "testproj" || job.Cluster != "testcluster" || + job.SubCluster != "sc1" || job.Partition != "default" || job.ArrayJobId != 0 || job.NumNodes != 1 || @@ -219,7 +225,7 @@ func TestRestApi(t *testing.T) { job.Exclusive != 1 || job.MonitoringStatus != 1 || job.SMT != 1 || - !reflect.DeepEqual(job.Resources, []*schema.Resource{{Hostname: "testhost", HWThreads: []int{0, 1, 2, 3, 4, 5, 6, 7}}}) || + !reflect.DeepEqual(job.Resources, []*schema.Resource{{Hostname: "host123", HWThreads: []int{0, 1, 2, 3, 4, 5, 6, 7}}}) || job.StartTime.Unix() != 123456789 { t.Fatalf("unexpected job properties: %#v", job) } diff --git a/config/config.go b/config/config.go index 816ad8f..ef49a12 100644 --- a/config/config.go +++ b/config/config.go @@ -20,10 +20,14 @@ import ( var db *sqlx.DB var lookupConfigStmt *sqlx.Stmt + var lock sync.RWMutex var uiDefaults map[string]interface{} + var cache *lrucache.Cache = lrucache.New(1024) + var Clusters []*model.Cluster +var nodeLists map[string]map[string]NodeList func Init(usersdb *sqlx.DB, authEnabled bool, uiConfig map[string]interface{}, jobArchive string) error { db = usersdb @@ -34,6 +38,7 @@ func Init(usersdb *sqlx.DB, authEnabled bool, uiConfig map[string]interface{}, j } Clusters = []*model.Cluster{} + nodeLists = map[string]map[string]NodeList{} for _, de := range entries { raw, err := os.ReadFile(filepath.Join(jobArchive, de.Name(), "cluster.json")) if err != nil { @@ -53,8 +58,8 @@ func Init(usersdb *sqlx.DB, authEnabled bool, uiConfig map[string]interface{}, j return err } - if len(cluster.Name) == 0 || len(cluster.MetricConfig) == 0 || len(cluster.Partitions) == 0 { - return errors.New("cluster.name, cluster.metricConfig and cluster.Partitions should not be empty") + if len(cluster.Name) == 0 || len(cluster.MetricConfig) == 0 || len(cluster.SubClusters) == 0 { + return errors.New("cluster.name, cluster.metricConfig and cluster.SubClusters should not be empty") } for _, mc := range cluster.MetricConfig { @@ -83,6 +88,19 @@ func Init(usersdb *sqlx.DB, authEnabled bool, uiConfig map[string]interface{}, j } Clusters = append(Clusters, &cluster) + + nodeLists[cluster.Name] = make(map[string]NodeList) + for _, sc := range cluster.SubClusters { + if sc.Nodes == "" { + continue + } + + nl, err := ParseNodeList(sc.Nodes) + if err != nil { + return fmt.Errorf("in %s/cluster.json: %w", cluster.Name, err) + } + nodeLists[cluster.Name][sc.Name] = nl + } } if authEnabled { @@ -188,7 +206,7 @@ func UpdateConfig(key, value string, ctx context.Context) error { return nil } -func GetClusterConfig(cluster string) *model.Cluster { +func GetCluster(cluster string) *model.Cluster { for _, c := range Clusters { if c.Name == cluster { return c @@ -197,11 +215,11 @@ func GetClusterConfig(cluster string) *model.Cluster { return nil } -func GetPartition(cluster, partition string) *model.Partition { +func GetSubCluster(cluster, subcluster string) *model.SubCluster { for _, c := range Clusters { if c.Name == cluster { - for _, p := range c.Partitions { - if p.Name == partition { + for _, p := range c.SubClusters { + if p.Name == subcluster { return p } } @@ -222,3 +240,40 @@ func GetMetricConfig(cluster, metric string) *model.MetricConfig { } return nil } + +// AssignSubCluster sets the `job.subcluster` property of the job based +// on its cluster and resources. +func AssignSubCluster(job *schema.BaseJob) error { + cluster := GetCluster(job.Cluster) + if cluster == nil { + return fmt.Errorf("unkown cluster: %#v", job.Cluster) + } + + if job.SubCluster != "" { + for _, sc := range cluster.SubClusters { + if sc.Name == job.SubCluster { + return nil + } + } + return fmt.Errorf("already assigned subcluster %#v unkown (cluster: %#v)", job.SubCluster, job.Cluster) + } + + if len(job.Resources) == 0 { + return fmt.Errorf("job without any resources/hosts") + } + + host0 := job.Resources[0].Hostname + for sc, nl := range nodeLists[job.Cluster] { + if nl != nil && nl.Contains(host0) { + job.SubCluster = sc + return nil + } + } + + if cluster.SubClusters[0].Nodes == "" { + job.SubCluster = cluster.SubClusters[0].Name + return nil + } + + return fmt.Errorf("no subcluster found for cluster %#v and host %#v", job.Cluster, host0) +} diff --git a/config/nodelist.go b/config/nodelist.go new file mode 100644 index 0000000..800e1ba --- /dev/null +++ b/config/nodelist.go @@ -0,0 +1,136 @@ +package config + +import ( + "fmt" + "strconv" + "strings" + + "github.com/ClusterCockpit/cc-backend/log" +) + +type NLExprString string + +func (nle NLExprString) consume(input string) (next string, ok bool) { + str := string(nle) + if strings.HasPrefix(input, str) { + return strings.TrimPrefix(input, str), true + } + return "", false +} + +type NLExprIntRange struct { + start, end int64 + zeroPadded bool + digits int +} + +func (nle NLExprIntRange) consume(input string) (next string, ok bool) { + if !nle.zeroPadded || nle.digits < 1 { + log.Error("node list: only zero-padded ranges are allowed") + return "", false + } + + if len(input) < nle.digits { + return "", false + } + + numerals, rest := input[:nle.digits], input[nle.digits:] + for len(numerals) > 1 && numerals[0] == '0' { + numerals = numerals[1:] + } + + x, err := strconv.ParseInt(numerals, 10, 32) + if err != nil { + return "", false + } + + if nle.start <= x && x <= nle.end { + return rest, true + } + + return "", false +} + +type NodeList [][]interface { + consume(input string) (next string, ok bool) +} + +func (nl *NodeList) Contains(name string) bool { + var ok bool + for _, term := range *nl { + str := name + for _, expr := range term { + str, ok = expr.consume(str) + if !ok { + break + } + } + + if ok && str == "" { + return true + } + } + + return false +} + +func ParseNodeList(raw string) (NodeList, error) { + nl := NodeList{} + + isLetter := func(r byte) bool { return ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') } + isDigit := func(r byte) bool { return '0' <= r && r <= '9' } + + for _, rawterm := range strings.Split(raw, ",") { + exprs := []interface { + consume(input string) (next string, ok bool) + }{} + for i := 0; i < len(rawterm); i++ { + c := rawterm[i] + if isLetter(c) || isDigit(c) { + j := i + for j < len(rawterm) && (isLetter(rawterm[j]) || isDigit(rawterm[j])) { + j++ + } + exprs = append(exprs, NLExprString(rawterm[i:j])) + i = j - 1 + } else if c == '[' { + end := strings.Index(rawterm[i:], "]") + if end == -1 { + return nil, fmt.Errorf("node list: unclosed '['") + } + + minus := strings.Index(rawterm[i:i+end], "-") + if minus == -1 { + return nil, fmt.Errorf("node list: no '-' found inside '[...]'") + } + + s1, s2 := rawterm[i+1:i+minus], rawterm[i+minus+1:i+end] + if len(s1) != len(s2) || len(s1) == 0 { + return nil, fmt.Errorf("node list: %#v and %#v are not of equal length or of length zero", s1, s2) + } + + x1, err := strconv.ParseInt(s1, 10, 32) + if err != nil { + return nil, fmt.Errorf("node list: %w", err) + } + x2, err := strconv.ParseInt(s2, 10, 32) + if err != nil { + return nil, fmt.Errorf("node list: %w", err) + } + + exprs = append(exprs, NLExprIntRange{ + start: x1, + end: x2, + digits: len(s1), + zeroPadded: true, + }) + i += end + } else { + return nil, fmt.Errorf("node list: invalid character: %#v", rune(c)) + } + } + nl = append(nl, exprs) + } + + return nl, nil +} diff --git a/config/nodelist_test.go b/config/nodelist_test.go new file mode 100644 index 0000000..6768d59 --- /dev/null +++ b/config/nodelist_test.go @@ -0,0 +1,37 @@ +package config + +import ( + "testing" +) + +func TestNodeList(t *testing.T) { + nl, err := ParseNodeList("hallo,wel123t,emmy[01-99],fritz[005-500],woody[100-200]") + if err != nil { + t.Fatal(err) + } + + // fmt.Printf("terms\n") + // for i, term := range nl.terms { + // fmt.Printf("term %d: %#v\n", i, term) + // } + + if nl.Contains("hello") || nl.Contains("woody") { + t.Fail() + } + + if nl.Contains("fritz1") || nl.Contains("fritz9") || nl.Contains("fritz004") || nl.Contains("woody201") { + t.Fail() + } + + if !nl.Contains("hallo") || !nl.Contains("wel123t") { + t.Fail() + } + + if !nl.Contains("emmy01") || !nl.Contains("emmy42") || !nl.Contains("emmy99") { + t.Fail() + } + + if !nl.Contains("woody100") || !nl.Contains("woody199") { + t.Fail() + } +} diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 3ed5804..323229c 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -37,6 +37,7 @@ type Config struct { } type ResolverRoot interface { + Cluster() ClusterResolver Job() JobResolver Mutation() MutationResolver Query() QueryResolver @@ -56,7 +57,7 @@ type ComplexityRoot struct { FilterRanges func(childComplexity int) int MetricConfig func(childComplexity int) int Name func(childComplexity int) int - Partitions func(childComplexity int) int + SubClusters func(childComplexity int) int } Count struct { @@ -169,18 +170,6 @@ type ComplexityRoot struct { Metrics func(childComplexity int) int } - Partition struct { - CoresPerSocket func(childComplexity int) int - FlopRateScalar func(childComplexity int) int - FlopRateSimd func(childComplexity int) int - MemoryBandwidth func(childComplexity int) int - Name func(childComplexity int) int - ProcessorType func(childComplexity int) int - SocketsPerNode func(childComplexity int) int - ThreadsPerCore func(childComplexity int) int - Topology func(childComplexity int) int - } - Query struct { Clusters func(childComplexity int) int Job func(childComplexity int, id string) int @@ -214,6 +203,19 @@ type ComplexityRoot struct { Min func(childComplexity int) int } + SubCluster struct { + CoresPerSocket func(childComplexity int) int + FlopRateScalar func(childComplexity int) int + FlopRateSimd func(childComplexity int) int + MemoryBandwidth func(childComplexity int) int + Name func(childComplexity int) int + Nodes func(childComplexity int) int + ProcessorType func(childComplexity int) int + SocketsPerNode func(childComplexity int) int + ThreadsPerCore func(childComplexity int) int + Topology func(childComplexity int) int + } + Tag struct { ID func(childComplexity int) int Name func(childComplexity int) int @@ -235,6 +237,9 @@ type ComplexityRoot struct { } } +type ClusterResolver interface { + SubClusters(ctx context.Context, obj *model.Cluster) ([]*model.SubCluster, error) +} type JobResolver interface { MetaData(ctx context.Context, obj *schema.Job) (interface{}, error) Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag, error) @@ -316,12 +321,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Cluster.Name(childComplexity), true - case "Cluster.partitions": - if e.complexity.Cluster.Partitions == nil { + case "Cluster.subClusters": + if e.complexity.Cluster.SubClusters == nil { break } - return e.complexity.Cluster.Partitions(childComplexity), true + return e.complexity.Cluster.SubClusters(childComplexity), true case "Count.count": if e.complexity.Count.Count == nil { @@ -824,69 +829,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.NodeMetrics.Metrics(childComplexity), true - case "Partition.coresPerSocket": - if e.complexity.Partition.CoresPerSocket == nil { - break - } - - return e.complexity.Partition.CoresPerSocket(childComplexity), true - - case "Partition.flopRateScalar": - if e.complexity.Partition.FlopRateScalar == nil { - break - } - - return e.complexity.Partition.FlopRateScalar(childComplexity), true - - case "Partition.flopRateSimd": - if e.complexity.Partition.FlopRateSimd == nil { - break - } - - return e.complexity.Partition.FlopRateSimd(childComplexity), true - - case "Partition.memoryBandwidth": - if e.complexity.Partition.MemoryBandwidth == nil { - break - } - - return e.complexity.Partition.MemoryBandwidth(childComplexity), true - - case "Partition.name": - if e.complexity.Partition.Name == nil { - break - } - - return e.complexity.Partition.Name(childComplexity), true - - case "Partition.processorType": - if e.complexity.Partition.ProcessorType == nil { - break - } - - return e.complexity.Partition.ProcessorType(childComplexity), true - - case "Partition.socketsPerNode": - if e.complexity.Partition.SocketsPerNode == nil { - break - } - - return e.complexity.Partition.SocketsPerNode(childComplexity), true - - case "Partition.threadsPerCore": - if e.complexity.Partition.ThreadsPerCore == nil { - break - } - - return e.complexity.Partition.ThreadsPerCore(childComplexity), true - - case "Partition.topology": - if e.complexity.Partition.Topology == nil { - break - } - - return e.complexity.Partition.Topology(childComplexity), true - case "Query.clusters": if e.complexity.Query.Clusters == nil { break @@ -1074,6 +1016,76 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.StatsSeries.Min(childComplexity), true + case "SubCluster.coresPerSocket": + if e.complexity.SubCluster.CoresPerSocket == nil { + break + } + + return e.complexity.SubCluster.CoresPerSocket(childComplexity), true + + case "SubCluster.flopRateScalar": + if e.complexity.SubCluster.FlopRateScalar == nil { + break + } + + return e.complexity.SubCluster.FlopRateScalar(childComplexity), true + + case "SubCluster.flopRateSimd": + if e.complexity.SubCluster.FlopRateSimd == nil { + break + } + + return e.complexity.SubCluster.FlopRateSimd(childComplexity), true + + case "SubCluster.memoryBandwidth": + if e.complexity.SubCluster.MemoryBandwidth == nil { + break + } + + return e.complexity.SubCluster.MemoryBandwidth(childComplexity), true + + case "SubCluster.name": + if e.complexity.SubCluster.Name == nil { + break + } + + return e.complexity.SubCluster.Name(childComplexity), true + + case "SubCluster.nodes": + if e.complexity.SubCluster.Nodes == nil { + break + } + + return e.complexity.SubCluster.Nodes(childComplexity), true + + case "SubCluster.processorType": + if e.complexity.SubCluster.ProcessorType == nil { + break + } + + return e.complexity.SubCluster.ProcessorType(childComplexity), true + + case "SubCluster.socketsPerNode": + if e.complexity.SubCluster.SocketsPerNode == nil { + break + } + + return e.complexity.SubCluster.SocketsPerNode(childComplexity), true + + case "SubCluster.threadsPerCore": + if e.complexity.SubCluster.ThreadsPerCore == nil { + break + } + + return e.complexity.SubCluster.ThreadsPerCore(childComplexity), true + + case "SubCluster.topology": + if e.complexity.SubCluster.Topology == nil { + break + } + + return e.complexity.SubCluster.Topology(childComplexity), true + case "Tag.id": if e.complexity.Tag.ID == nil { break @@ -1250,11 +1262,12 @@ type Cluster { name: String! metricConfig: [MetricConfig!]! filterRanges: FilterRanges! - partitions: [Partition!]! + subClusters: [SubCluster!]! } -type Partition { +type SubCluster { name: String! + nodes: String! processorType: String! socketsPerNode: Int! coresPerSocket: Int! @@ -2141,7 +2154,7 @@ func (ec *executionContext) _Cluster_filterRanges(ctx context.Context, field gra return ec.marshalNFilterRanges2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐFilterRanges(ctx, field.Selections, res) } -func (ec *executionContext) _Cluster_partitions(ctx context.Context, field graphql.CollectedField, obj *model.Cluster) (ret graphql.Marshaler) { +func (ec *executionContext) _Cluster_subClusters(ctx context.Context, field graphql.CollectedField, obj *model.Cluster) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2152,14 +2165,14 @@ func (ec *executionContext) _Cluster_partitions(ctx context.Context, field graph Object: "Cluster", Field: field, Args: nil, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, } 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.Partitions, nil + return ec.resolvers.Cluster().SubClusters(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2171,9 +2184,9 @@ func (ec *executionContext) _Cluster_partitions(ctx context.Context, field graph } return graphql.Null } - res := resTmp.([]*model.Partition) + res := resTmp.([]*model.SubCluster) fc.Result = res - return ec.marshalNPartition2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐPartitionᚄ(ctx, field.Selections, res) + return ec.marshalNSubCluster2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubClusterᚄ(ctx, field.Selections, res) } func (ec *executionContext) _Count_name(ctx context.Context, field graphql.CollectedField, obj *model.Count) (ret graphql.Marshaler) { @@ -4570,321 +4583,6 @@ func (ec *executionContext) _NodeMetrics_metrics(ctx context.Context, field grap 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) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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) _Partition_processorType(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.ProcessorType, 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) _Partition_socketsPerNode(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.SocketsPerNode, 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) _Partition_coresPerSocket(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.CoresPerSocket, 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) _Partition_threadsPerCore(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.ThreadsPerCore, 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) _Partition_flopRateScalar(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.FlopRateScalar, 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) _Partition_flopRateSimd(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.FlopRateSimd, 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) _Partition_memoryBandwidth(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.MemoryBandwidth, 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) _Partition_topology(ctx context.Context, field graphql.CollectedField, obj *model.Partition) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Partition", - 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.Topology, 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.(*model.Topology) - fc.Result = res - return ec.marshalNTopology2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐTopology(ctx, field.Selections, res) -} - func (ec *executionContext) _Query_clusters(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5729,6 +5427,356 @@ func (ec *executionContext) _StatsSeries_max(ctx context.Context, field graphql. return ec.marshalNNullableFloat2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋschemaᚐFloatᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _SubCluster_name(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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) _SubCluster_nodes(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.Nodes, 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) _SubCluster_processorType(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.ProcessorType, 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) _SubCluster_socketsPerNode(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.SocketsPerNode, 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) _SubCluster_coresPerSocket(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.CoresPerSocket, 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) _SubCluster_threadsPerCore(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.ThreadsPerCore, 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) _SubCluster_flopRateScalar(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.FlopRateScalar, 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) _SubCluster_flopRateSimd(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.FlopRateSimd, 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) _SubCluster_memoryBandwidth(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.MemoryBandwidth, 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) _SubCluster_topology(ctx context.Context, field graphql.CollectedField, obj *model.SubCluster) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubCluster", + 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.Topology, 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.(*model.Topology) + fc.Result = res + return ec.marshalNTopology2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐTopology(ctx, field.Selections, res) +} + func (ec *executionContext) _Tag_id(ctx context.Context, field graphql.CollectedField, obj *schema.Tag) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -7586,23 +7634,32 @@ func (ec *executionContext) _Cluster(ctx context.Context, sel ast.SelectionSet, case "name": out.Values[i] = ec._Cluster_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "metricConfig": out.Values[i] = ec._Cluster_metricConfig(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "filterRanges": out.Values[i] = ec._Cluster_filterRanges(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ - } - case "partitions": - out.Values[i] = ec._Cluster_partitions(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } + case "subClusters": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Cluster_subClusters(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8263,73 +8320,6 @@ func (ec *executionContext) _NodeMetrics(ctx context.Context, sel ast.SelectionS return out } -var partitionImplementors = []string{"Partition"} - -func (ec *executionContext) _Partition(ctx context.Context, sel ast.SelectionSet, obj *model.Partition) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, partitionImplementors) - - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("Partition") - case "name": - out.Values[i] = ec._Partition_name(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "processorType": - out.Values[i] = ec._Partition_processorType(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "socketsPerNode": - out.Values[i] = ec._Partition_socketsPerNode(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "coresPerSocket": - out.Values[i] = ec._Partition_coresPerSocket(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "threadsPerCore": - out.Values[i] = ec._Partition_threadsPerCore(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "flopRateScalar": - out.Values[i] = ec._Partition_flopRateScalar(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "flopRateSimd": - out.Values[i] = ec._Partition_flopRateSimd(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "memoryBandwidth": - out.Values[i] = ec._Partition_memoryBandwidth(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "topology": - out.Values[i] = ec._Partition_topology(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 queryImplementors = []string{"Query"} func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -8603,6 +8593,78 @@ func (ec *executionContext) _StatsSeries(ctx context.Context, sel ast.SelectionS return out } +var subClusterImplementors = []string{"SubCluster"} + +func (ec *executionContext) _SubCluster(ctx context.Context, sel ast.SelectionSet, obj *model.SubCluster) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, subClusterImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SubCluster") + case "name": + out.Values[i] = ec._SubCluster_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "nodes": + out.Values[i] = ec._SubCluster_nodes(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "processorType": + out.Values[i] = ec._SubCluster_processorType(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "socketsPerNode": + out.Values[i] = ec._SubCluster_socketsPerNode(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "coresPerSocket": + out.Values[i] = ec._SubCluster_coresPerSocket(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "threadsPerCore": + out.Values[i] = ec._SubCluster_threadsPerCore(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "flopRateScalar": + out.Values[i] = ec._SubCluster_flopRateScalar(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "flopRateSimd": + out.Values[i] = ec._SubCluster_flopRateSimd(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "memoryBandwidth": + out.Values[i] = ec._SubCluster_memoryBandwidth(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "topology": + out.Values[i] = ec._SubCluster_topology(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 tagImplementors = []string{"Tag"} func (ec *executionContext) _Tag(ctx context.Context, sel ast.SelectionSet, obj *schema.Tag) graphql.Marshaler { @@ -9760,53 +9822,6 @@ func (ec *executionContext) marshalNNullableFloat2ᚕgithubᚗcomᚋClusterCockp return ret } -func (ec *executionContext) marshalNPartition2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐPartitionᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Partition) 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.marshalNPartition2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐPartition(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - return ret -} - -func (ec *executionContext) marshalNPartition2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐPartition(ctx context.Context, sel ast.SelectionSet, v *model.Partition) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - return ec._Partition(ctx, sel, v) -} - func (ec *executionContext) marshalNResource2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋschemaᚐResourceᚄ(ctx context.Context, sel ast.SelectionSet, v []*schema.Resource) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -9913,6 +9928,53 @@ func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel return ret } +func (ec *executionContext) marshalNSubCluster2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubClusterᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.SubCluster) 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.marshalNSubCluster2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubCluster(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNSubCluster2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubCluster(ctx context.Context, sel ast.SelectionSet, v *model.SubCluster) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._SubCluster(ctx, sel, v) +} + func (ec *executionContext) marshalNTag2githubᚗcomᚋClusterCockpitᚋccᚑbackendᚋschemaᚐTag(ctx context.Context, sel ast.SelectionSet, v schema.Tag) graphql.Marshaler { return ec._Tag(ctx, sel, &v) } diff --git a/graph/model/models.go b/graph/model/models.go index e67098e..7a2d936 100644 --- a/graph/model/models.go +++ b/graph/model/models.go @@ -6,7 +6,7 @@ type Cluster struct { Name string `json:"name"` MetricConfig []*MetricConfig `json:"metricConfig"` FilterRanges *FilterRanges `json:"filterRanges"` - Partitions []*Partition `json:"partitions"` + SubClusters []*SubCluster `json:"subClusters"` // NOT part of the API: MetricDataRepository *MetricDataRepository `json:"metricDataRepository"` diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index 174f679..95f58b0 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -122,8 +122,16 @@ type PageRequest struct { Page int `json:"page"` } -type Partition struct { +type StringInput struct { + Eq *string `json:"eq"` + Contains *string `json:"contains"` + StartsWith *string `json:"startsWith"` + EndsWith *string `json:"endsWith"` +} + +type SubCluster struct { Name string `json:"name"` + Nodes string `json:"nodes"` ProcessorType string `json:"processorType"` SocketsPerNode int `json:"socketsPerNode"` CoresPerSocket int `json:"coresPerSocket"` @@ -134,13 +142,6 @@ type Partition struct { Topology *Topology `json:"topology"` } -type StringInput struct { - Eq *string `json:"eq"` - Contains *string `json:"contains"` - StartsWith *string `json:"startsWith"` - EndsWith *string `json:"endsWith"` -} - type TimeRange struct { From *time.Time `json:"from"` To *time.Time `json:"to"` diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 0b85a34..25d44a8 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -33,11 +33,12 @@ type Cluster { name: String! metricConfig: [MetricConfig!]! filterRanges: FilterRanges! - partitions: [Partition!]! + subClusters: [SubCluster!]! } -type Partition { +type SubCluster { name: String! + nodes: String! processorType: String! socketsPerNode: Int! coresPerSocket: Int! diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index 3fa95b0..8fec60c 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -18,6 +18,10 @@ import ( "github.com/ClusterCockpit/cc-backend/schema" ) +func (r *clusterResolver) SubClusters(ctx context.Context, obj *model.Cluster) ([]*model.SubCluster, error) { + panic(fmt.Errorf("not implemented")) +} + func (r *jobResolver) MetaData(ctx context.Context, obj *schema.Job) (interface{}, error) { return r.Repo.FetchMetadata(obj) } @@ -204,7 +208,7 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, partiti } if metrics == nil { - for _, mc := range config.GetClusterConfig(cluster).MetricConfig { + for _, mc := range config.GetCluster(cluster).MetricConfig { metrics = append(metrics, mc.Name) } } @@ -236,6 +240,9 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, partiti return nodeMetrics, nil } +// Cluster returns generated.ClusterResolver implementation. +func (r *Resolver) Cluster() generated.ClusterResolver { return &clusterResolver{r} } + // Job returns generated.JobResolver implementation. func (r *Resolver) Job() generated.JobResolver { return &jobResolver{r} } @@ -245,6 +252,7 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } +type clusterResolver struct{ *Resolver } type jobResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } diff --git a/graph/stats.go b/graph/stats.go index da21995..fb24bab 100644 --- a/graph/stats.go +++ b/graph/stats.go @@ -32,8 +32,8 @@ func (r *queryResolver) jobsStatistics(ctx context.Context, filter []*model.JobF // `socketsPerNode` and `coresPerSocket` can differ from cluster to cluster, so we need to explicitly loop over those. for _, cluster := range config.Clusters { - for _, partition := range cluster.Partitions { - corehoursCol := fmt.Sprintf("CAST(ROUND(SUM(job.duration * job.num_nodes * %d * %d) / 3600) as int)", partition.SocketsPerNode, partition.CoresPerSocket) + for _, subcluster := range cluster.SubClusters { + corehoursCol := fmt.Sprintf("CAST(ROUND(SUM(job.duration * job.num_nodes * %d * %d) / 3600) as int)", subcluster.SocketsPerNode, subcluster.CoresPerSocket) var query sq.SelectBuilder if groupBy == nil { query = sq.Select( @@ -54,7 +54,7 @@ func (r *queryResolver) jobsStatistics(ctx context.Context, filter []*model.JobF query = query. Where("job.cluster = ?", cluster.Name). - Where("job.partition = ?", partition.Name) + Where("job.subcluster = ?", subcluster.Name) query = repository.SecurityCheck(ctx, query) for _, f := range filter { diff --git a/metricdata/archive.go b/metricdata/archive.go index e2aff03..e3cae79 100644 --- a/metricdata/archive.go +++ b/metricdata/archive.go @@ -157,7 +157,7 @@ func GetStatistics(job *schema.Job) (map[string]schema.JobStatistics, error) { // Writes a running job to the job-archive func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.JobMeta, error) { allMetrics := make([]string, 0) - metricConfigs := config.GetClusterConfig(job.Cluster).MetricConfig + metricConfigs := config.GetCluster(job.Cluster).MetricConfig for _, mc := range metricConfigs { allMetrics = append(allMetrics, mc.Name) } diff --git a/metricdata/cc-metric-store.go b/metricdata/cc-metric-store.go index 78d8750..c77d43d 100644 --- a/metricdata/cc-metric-store.go +++ b/metricdata/cc-metric-store.go @@ -227,7 +227,7 @@ var ( func (ccms *CCMetricStore) buildQueries(job *schema.Job, metrics []string, scopes []schema.MetricScope) ([]ApiQuery, []schema.MetricScope, error) { queries := make([]ApiQuery, 0, len(metrics)*len(scopes)*len(job.Resources)) - topology := config.GetPartition(job.Cluster, job.Partition).Topology + topology := config.GetSubCluster(job.Cluster, job.SubCluster).Topology assignedScope := []schema.MetricScope{} for _, metric := range metrics { diff --git a/metricdata/metricdata.go b/metricdata/metricdata.go index d4d9817..8f4122a 100644 --- a/metricdata/metricdata.go +++ b/metricdata/metricdata.go @@ -79,7 +79,7 @@ func LoadData(job *schema.Job, metrics []string, scopes []schema.MetricScope, ct } if metrics == nil { - cluster := config.GetClusterConfig(job.Cluster) + cluster := config.GetCluster(job.Cluster) for _, mc := range cluster.MetricConfig { metrics = append(metrics, mc.Name) } @@ -167,7 +167,7 @@ func LoadNodeData(cluster, partition string, metrics, nodes []string, scopes []s } if metrics == nil { - for _, m := range config.GetClusterConfig(cluster).MetricConfig { + for _, m := range config.GetCluster(cluster).MetricConfig { metrics = append(metrics, m.Name) } } diff --git a/repository/import.go b/repository/import.go index 94fba51..a18c189 100644 --- a/repository/import.go +++ b/repository/import.go @@ -122,12 +122,13 @@ func (r *JobRepository) ImportJob(jobMeta *schema.JobMeta, jobData *schema.JobDa return nil } +// This function also sets the subcluster if necessary! func SanityChecks(job *schema.BaseJob) error { - if c := config.GetClusterConfig(job.Cluster); c == nil { + if c := config.GetCluster(job.Cluster); c == nil { return fmt.Errorf("no such cluster: %#v", job.Cluster) } - if p := config.GetPartition(job.Cluster, job.Partition); p == nil { - return fmt.Errorf("no such partition: %#v (on cluster %#v)", job.Partition, job.Cluster) + if err := config.AssignSubCluster(job); err != nil { + return err } if !job.State.Valid() { return fmt.Errorf("not a valid job state: %#v", job.State) diff --git a/repository/job.go b/repository/job.go index 732ef6f..567b512 100644 --- a/repository/job.go +++ b/repository/job.go @@ -31,17 +31,17 @@ func (r *JobRepository) Init() error { } var jobColumns []string = []string{ - "job.id", "job.job_id", "job.user", "job.project", "job.cluster", "job.start_time", "job.partition", "job.array_job_id", + "job.id", "job.job_id", "job.user", "job.project", "job.cluster", "job.subcluster", "job.start_time", "job.partition", "job.array_job_id", "job.num_nodes", "job.num_hwthreads", "job.num_acc", "job.exclusive", "job.monitoring_status", "job.smt", "job.job_state", - "job.duration", "job.resources", // "job.meta_data", + "job.duration", "job.walltime", "job.resources", // "job.meta_data", } func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) { job := &schema.Job{} if err := row.Scan( - &job.ID, &job.JobID, &job.User, &job.Project, &job.Cluster, &job.StartTimeUnix, &job.Partition, &job.ArrayJobId, + &job.ID, &job.JobID, &job.User, &job.Project, &job.Cluster, &job.SubCluster, &job.StartTimeUnix, &job.Partition, &job.ArrayJobId, &job.NumNodes, &job.NumHWThreads, &job.NumAcc, &job.Exclusive, &job.MonitoringStatus, &job.SMT, &job.State, - &job.Duration, &job.RawResources /*&job.MetaData*/); err != nil { + &job.Duration, &job.Walltime, &job.RawResources /*&job.MetaData*/); err != nil { return nil, err }