From 4419df8d1b2d2308bcbc61d10cc266c80215c6e3 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 6 May 2025 18:08:35 +0200 Subject: [PATCH] add cluster and subcluster information to compareplot --- api/schema.graphqls | 2 + internal/graph/generated/generated.go | 120 ++++++++++++++++++ internal/graph/model/models_gen.go | 2 + internal/graph/schema.resolvers.go | 2 + web/frontend/src/generic/JobCompare.svelte | 8 +- .../src/generic/plots/Comparogram.svelte | 3 +- 6 files changed, 135 insertions(+), 2 deletions(-) diff --git a/api/schema.graphqls b/api/schema.graphqls index f5ca392..693ed29 100644 --- a/api/schema.graphqls +++ b/api/schema.graphqls @@ -174,6 +174,8 @@ type JobStats { jobId: Int! startTime: Int! duration: Int! + cluster: String! + subCluster: String! numNodes: Int! numHWThreads: Int numAccelerators: Int diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index d692335..e20fb69 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -171,6 +171,7 @@ type ComplexityRoot struct { } JobStats struct { + Cluster func(childComplexity int) int Duration func(childComplexity int) int JobID func(childComplexity int) int NumAccelerators func(childComplexity int) int @@ -178,6 +179,7 @@ type ComplexityRoot struct { NumNodes func(childComplexity int) int StartTime func(childComplexity int) int Stats func(childComplexity int) int + SubCluster func(childComplexity int) int } JobsStatistics struct { @@ -945,6 +947,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.JobResultList.Offset(childComplexity), true + case "JobStats.cluster": + if e.complexity.JobStats.Cluster == nil { + break + } + + return e.complexity.JobStats.Cluster(childComplexity), true + case "JobStats.duration": if e.complexity.JobStats.Duration == nil { break @@ -994,6 +1003,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.JobStats.Stats(childComplexity), true + case "JobStats.subCluster": + if e.complexity.JobStats.SubCluster == nil { + break + } + + return e.complexity.JobStats.SubCluster(childComplexity), true + case "JobsStatistics.histDuration": if e.complexity.JobsStatistics.HistDuration == nil { break @@ -2323,6 +2339,8 @@ type JobStats { jobId: Int! startTime: Int! duration: Int! + cluster: String! + subCluster: String! numNodes: Int! numHWThreads: Int numAccelerators: Int @@ -7533,6 +7551,94 @@ func (ec *executionContext) fieldContext_JobStats_duration(_ context.Context, fi return fc, nil } +func (ec *executionContext) _JobStats_cluster(ctx context.Context, field graphql.CollectedField, obj *model.JobStats) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_JobStats_cluster(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.Cluster, 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) fieldContext_JobStats_cluster(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "JobStats", + 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) _JobStats_subCluster(ctx context.Context, field graphql.CollectedField, obj *model.JobStats) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_JobStats_subCluster(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.SubCluster, 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) fieldContext_JobStats_subCluster(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "JobStats", + 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) _JobStats_numNodes(ctx context.Context, field graphql.CollectedField, obj *model.JobStats) (ret graphql.Marshaler) { fc, err := ec.fieldContext_JobStats_numNodes(ctx, field) if err != nil { @@ -11460,6 +11566,10 @@ func (ec *executionContext) fieldContext_Query_jobsMetricStats(ctx context.Conte return ec.fieldContext_JobStats_startTime(ctx, field) case "duration": return ec.fieldContext_JobStats_duration(ctx, field) + case "cluster": + return ec.fieldContext_JobStats_cluster(ctx, field) + case "subCluster": + return ec.fieldContext_JobStats_subCluster(ctx, field) case "numNodes": return ec.fieldContext_JobStats_numNodes(ctx, field) case "numHWThreads": @@ -17806,6 +17916,16 @@ func (ec *executionContext) _JobStats(ctx context.Context, sel ast.SelectionSet, if out.Values[i] == graphql.Null { out.Invalids++ } + case "cluster": + out.Values[i] = ec._JobStats_cluster(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "subCluster": + out.Values[i] = ec._JobStats_subCluster(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "numNodes": out.Values[i] = ec._JobStats_numNodes(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index d493844..dbe4462 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -100,6 +100,8 @@ type JobStats struct { JobID int `json:"jobId"` StartTime int `json:"startTime"` Duration int `json:"duration"` + Cluster string `json:"cluster"` + SubCluster string `json:"subCluster"` NumNodes int `json:"numNodes"` NumHWThreads *int `json:"numHWThreads,omitempty"` NumAccelerators *int `json:"numAccelerators,omitempty"` diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index 6176402..64e63db 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -621,6 +621,8 @@ func (r *queryResolver) JobsMetricStats(ctx context.Context, filter []*model.Job JobID: int(job.JobID), StartTime: int(job.StartTime.Unix()), Duration: int(job.Duration), + Cluster: job.Cluster, + SubCluster: job.SubCluster, NumNodes: int(job.NumNodes), NumHWThreads: &numThreadsInt, NumAccelerators: &numAccsInt, diff --git a/web/frontend/src/generic/JobCompare.svelte b/web/frontend/src/generic/JobCompare.svelte index 2ff3337..791ed30 100644 --- a/web/frontend/src/generic/JobCompare.svelte +++ b/web/frontend/src/generic/JobCompare.svelte @@ -41,6 +41,7 @@ let filter = [...filterBuffer]; let comparePlotData = {}; let jobIds = []; + let jobClusters = []; /*uPlot*/ let plotSync = uPlot.sync("compareJobsView"); @@ -54,6 +55,8 @@ jobId startTime duration + cluster + subCluster numNodes numHWThreads numAccelerators @@ -130,8 +133,9 @@ if (jobs) { let plotIndex = 0 jobs.forEach((j) => { - // Collect JobIDs for X-Ticks + // Collect JobIDs & Clusters for X-Ticks and Legend jobIds.push(j.jobId) + jobClusters.push(`${j.cluster} ${j.subCluster}`) // Resources comparePlotData['resources'].data[0].push(plotIndex) comparePlotData['resources'].data[1].push(j.startTime) @@ -203,6 +207,7 @@ title={'Compare Resources'} xlabel="JobIDs" xticks={jobIds} + xinfo={jobClusters} ylabel={'Resource Counts'} data={comparePlotData['resources'].data} {plotSync} @@ -217,6 +222,7 @@ title={`Compare Metric '${m}'`} xlabel="JobIDs" xticks={jobIds} + xinfo={jobClusters} ylabel={m} metric={m} yunit={comparePlotData[m].unit} diff --git a/web/frontend/src/generic/plots/Comparogram.svelte b/web/frontend/src/generic/plots/Comparogram.svelte index c0c924a..a2e8c69 100644 --- a/web/frontend/src/generic/plots/Comparogram.svelte +++ b/web/frontend/src/generic/plots/Comparogram.svelte @@ -24,6 +24,7 @@ export let data = null; export let xlabel = ""; export let xticks = []; + export let xinfo = []; export let ylabel = ""; export let yunit = ""; export let title = ""; @@ -102,7 +103,7 @@ label: "JobID", scale: "x", value: (u, ts, sidx, didx) => { - return xticks[didx]; + return `${xticks[didx]} | ${xinfo[didx]}`; }, }, {