Merge branch 'master' into 214_user_status_histograms

This commit is contained in:
Christoph Kluge 2024-02-08 12:26:07 +01:00 committed by GitHub
commit 21dde870c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 747 additions and 29 deletions

View File

@ -28,6 +28,11 @@ type Job {
resources: [Resource!]!
concurrentJobs: JobLinkResultList
memUsedMax: Float
flopsAnyAvg: Float
memBwAvg: Float
loadAvg: Float
metaData: Any
userData: User
}

View File

@ -90,8 +90,12 @@ type ComplexityRoot struct {
ConcurrentJobs func(childComplexity int) int
Duration func(childComplexity int) int
Exclusive func(childComplexity int) int
FlopsAnyAvg func(childComplexity int) int
ID func(childComplexity int) int
JobID func(childComplexity int) int
LoadAvg func(childComplexity int) int
MemBwAvg func(childComplexity int) int
MemUsedMax func(childComplexity int) int
MetaData func(childComplexity int) int
MonitoringStatus func(childComplexity int) int
NumAcc func(childComplexity int) int
@ -319,6 +323,7 @@ type JobResolver interface {
Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag, error)
ConcurrentJobs(ctx context.Context, obj *schema.Job) (*model.JobLinkResultList, error)
MetaData(ctx context.Context, obj *schema.Job) (interface{}, error)
UserData(ctx context.Context, obj *schema.Job) (*model.User, error)
}
@ -505,6 +510,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Job.Exclusive(childComplexity), true
case "Job.flopsAnyAvg":
if e.complexity.Job.FlopsAnyAvg == nil {
break
}
return e.complexity.Job.FlopsAnyAvg(childComplexity), true
case "Job.id":
if e.complexity.Job.ID == nil {
break
@ -519,6 +531,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Job.JobID(childComplexity), true
case "Job.loadAvg":
if e.complexity.Job.LoadAvg == nil {
break
}
return e.complexity.Job.LoadAvg(childComplexity), true
case "Job.memBwAvg":
if e.complexity.Job.MemBwAvg == nil {
break
}
return e.complexity.Job.MemBwAvg(childComplexity), true
case "Job.memUsedMax":
if e.complexity.Job.MemUsedMax == nil {
break
}
return e.complexity.Job.MemUsedMax(childComplexity), true
case "Job.metaData":
if e.complexity.Job.MetaData == nil {
break
@ -1704,6 +1737,11 @@ type Job {
resources: [Resource!]!
concurrentJobs: JobLinkResultList
memUsedMax: Float
flopsAnyAvg: Float
memBwAvg: Float
loadAvg: Float
metaData: Any
userData: User
}
@ -4153,6 +4191,170 @@ func (ec *executionContext) fieldContext_Job_concurrentJobs(ctx context.Context,
return fc, nil
}
func (ec *executionContext) _Job_memUsedMax(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Job_memUsedMax(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) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.MemUsedMax, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(float64)
fc.Result = res
return ec.marshalOFloat2float64(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Job_memUsedMax(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Job",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Job_flopsAnyAvg(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Job_flopsAnyAvg(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) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.FlopsAnyAvg, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(float64)
fc.Result = res
return ec.marshalOFloat2float64(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Job_flopsAnyAvg(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Job",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Job_memBwAvg(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Job_memBwAvg(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) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.MemBwAvg, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(float64)
fc.Result = res
return ec.marshalOFloat2float64(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Job_memBwAvg(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Job",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Job_loadAvg(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Job_loadAvg(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) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.LoadAvg, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(float64)
fc.Result = res
return ec.marshalOFloat2float64(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Job_loadAvg(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Job",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Job_metaData(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Job_metaData(ctx, field)
if err != nil {
@ -4877,6 +5079,14 @@ func (ec *executionContext) fieldContext_JobResultList_items(ctx context.Context
return ec.fieldContext_Job_resources(ctx, field)
case "concurrentJobs":
return ec.fieldContext_Job_concurrentJobs(ctx, field)
case "memUsedMax":
return ec.fieldContext_Job_memUsedMax(ctx, field)
case "flopsAnyAvg":
return ec.fieldContext_Job_flopsAnyAvg(ctx, field)
case "memBwAvg":
return ec.fieldContext_Job_memBwAvg(ctx, field)
case "loadAvg":
return ec.fieldContext_Job_loadAvg(ctx, field)
case "metaData":
return ec.fieldContext_Job_metaData(ctx, field)
case "userData":
@ -7609,6 +7819,14 @@ func (ec *executionContext) fieldContext_Query_job(ctx context.Context, field gr
return ec.fieldContext_Job_resources(ctx, field)
case "concurrentJobs":
return ec.fieldContext_Job_concurrentJobs(ctx, field)
case "memUsedMax":
return ec.fieldContext_Job_memUsedMax(ctx, field)
case "flopsAnyAvg":
return ec.fieldContext_Job_flopsAnyAvg(ctx, field)
case "memBwAvg":
return ec.fieldContext_Job_memBwAvg(ctx, field)
case "loadAvg":
return ec.fieldContext_Job_loadAvg(ctx, field)
case "metaData":
return ec.fieldContext_Job_metaData(ctx, field)
case "userData":
@ -12963,6 +13181,14 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "memUsedMax":
out.Values[i] = ec._Job_memUsedMax(ctx, field, obj)
case "flopsAnyAvg":
out.Values[i] = ec._Job_flopsAnyAvg(ctx, field, obj)
case "memBwAvg":
out.Values[i] = ec._Job_memBwAvg(ctx, field, obj)
case "loadAvg":
out.Values[i] = ec._Job_loadAvg(ctx, field, obj)
case "metaData":
field := field

View File

@ -60,7 +60,7 @@ func GetJobRepository() *JobRepository {
var jobColumns []string = []string{
"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.walltime", "job.resources", // "job.meta_data",
"job.duration", "job.walltime", "job.resources", "job.mem_used_max", "job.flops_any_avg", "job.mem_bw_avg", "job.load_avg", // "job.meta_data",
}
func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
@ -68,7 +68,7 @@ func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
if err := row.Scan(
&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.Walltime, &job.RawResources /*&job.RawMetaData*/); err != nil {
&job.Duration, &job.Walltime, &job.RawResources, &job.MemUsedMax, &job.FlopsAnyAvg, &job.MemBwAvg, &job.LoadAvg /*&job.RawMetaData*/); err != nil {
log.Warnf("Error while scanning rows (Job): %v", err)
return nil, err
}
@ -483,6 +483,7 @@ func (r *JobRepository) MarkArchived(
case "mem_bw":
stmt = stmt.Set("mem_bw_avg", stats.Avg)
case "load":
stmt = stmt.Set("load_avg", stats.Avg)
case "cpu_load":
stmt = stmt.Set("load_avg", stats.Avg)
case "net_bw":

View File

@ -54,10 +54,10 @@ type Job struct {
BaseJob
StartTimeUnix int64 `json:"-" db:"start_time" example:"1649723812"` // Start epoch time stamp in seconds
StartTime time.Time `json:"startTime"` // Start time as 'time.Time' data type
MemUsedMax float64 `json:"-" db:"mem_used_max"` // MemUsedMax as Float64
FlopsAnyAvg float64 `json:"-" db:"flops_any_avg"` // FlopsAnyAvg as Float64
MemBwAvg float64 `json:"-" db:"mem_bw_avg"` // MemBwAvg as Float64
LoadAvg float64 `json:"-" db:"load_avg"` // LoadAvg as Float64
MemUsedMax float64 `json:"memUsedMax" db:"mem_used_max"` // MemUsedMax as Float64
FlopsAnyAvg float64 `json:"flopsAnyAvg" db:"flops_any_avg"` // FlopsAnyAvg as Float64
MemBwAvg float64 `json:"memBwAvg" db:"mem_bw_avg"` // MemBwAvg as Float64
LoadAvg float64 `json:"loadAvg" db:"load_avg"` // LoadAvg as Float64
NetBwAvg float64 `json:"-" db:"net_bw_avg"` // NetBwAvg as Float64
NetDataVolTotal float64 `json:"-" db:"net_data_vol_total"` // NetDataVolTotal as Float64
FileBwAvg float64 `json:"-" db:"file_bw_avg"` // FileBwAvg as Float64

View File

@ -13,7 +13,9 @@
"@urql/svelte": "^4.0.1",
"chart.js": "^4.3.3",
"date-fns": "^2.30.0",
"date-fns": "^2.30.0",
"graphql": "^16.6.0",
"mathjs": "^12.0.0",
"svelte-chartjs": "^3.1.2",
"sveltestrap": "^5.11.1",
"uplot": "^1.6.24",
@ -102,6 +104,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.20",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
"version": "0.3.20",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
@ -151,6 +156,9 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
@ -168,6 +176,7 @@
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0||^4.0.0"
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@ -176,18 +185,23 @@
}
},
"node_modules/@rollup/plugin-replace": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz",
"integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz",
"integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"magic-string": "^0.30.3"
"magic-string": "^0.30.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@ -206,7 +220,21 @@
"node": ">=12"
}
},
"node_modules/@rollup/plugin-replace/node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@rollup/plugin-terser": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
@ -221,6 +249,7 @@
},
"peerDependencies": {
"rollup": "^2.0.0||^3.0.0||^4.0.0"
"rollup": "^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@ -242,6 +271,7 @@
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@ -259,6 +289,9 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/resolve": {
"version": "1.20.2",
@ -288,6 +321,9 @@
}
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
@ -355,6 +391,18 @@
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true
},
"node_modules/complex.js": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
"integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==",
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
"url": "https://www.patreon.com/infusion"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -370,6 +418,11 @@
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/decimal.js": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@ -379,11 +432,28 @@
"node": ">=0.10.0"
}
},
"node_modules/escape-latex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/fraction.js": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz",
"integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==",
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -412,6 +482,13 @@
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob": {
"version": "8.1.0",
@ -433,6 +510,9 @@
}
},
"node_modules/graphql": {
"version": "16.8.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
"version": "16.8.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
@ -440,6 +520,10 @@
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@ -447,9 +531,11 @@
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
"node": ">= 0.4"
}
},
"node_modules/inflight": {
@ -484,12 +570,16 @@
}
},
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dev": true,
"dependencies": {
"hasown": "^2.0.0"
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -510,11 +600,17 @@
"@types/estree": "*"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
},
"node_modules/magic-string": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
"dev": true,
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.13"
},
@ -522,6 +618,28 @@
"node": ">=12"
}
},
"node_modules/mathjs": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-12.0.0.tgz",
"integrity": "sha512-Oz3swPplNPe7taoP6WrkKhQzhDE2SwvOgLzu8H3EN+hEadw2GjEJUm6Xl+hrioHoB8g2BYb3gfw1glSzhdBKYw==",
"dependencies": {
"@babel/runtime": "^7.23.2",
"complex.js": "^2.1.1",
"decimal.js": "^10.4.3",
"escape-latex": "^1.2.0",
"fraction.js": "4.3.4",
"javascript-natural-sort": "^0.7.1",
"seedrandom": "^3.0.5",
"tiny-emitter": "^2.1.0",
"typed-function": "^4.1.1"
},
"bin": {
"mathjs": "bin/cli.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
@ -574,7 +692,15 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
@ -601,6 +727,9 @@
}
},
"node_modules/rollup": {
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
@ -617,6 +746,9 @@
}
},
"node_modules/rollup-plugin-css-only": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-4.5.2.tgz",
"integrity": "sha512-7rj9+jB17Pz8LNcPgtMUb16JcgD8lxQMK9HcGfAVhMK3na/WXes3oGIo5QsrQQVqtgAU6q6KnQNXJrYunaUIQQ==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-4.5.2.tgz",
"integrity": "sha512-7rj9+jB17Pz8LNcPgtMUb16JcgD8lxQMK9HcGfAVhMK3na/WXes3oGIo5QsrQQVqtgAU6q6KnQNXJrYunaUIQQ==",
@ -629,6 +761,7 @@
},
"peerDependencies": {
"rollup": "<5"
"rollup": "<5"
}
},
"node_modules/rollup-plugin-svelte": {
@ -681,6 +814,11 @@
}
]
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"node_modules/serialize-javascript": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
@ -691,6 +829,9 @@
}
},
"node_modules/smob": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
@ -745,6 +886,9 @@
}
},
"node_modules/sveltestrap": {
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/sveltestrap/-/sveltestrap-5.11.2.tgz",
"integrity": "sha512-fkLqIUh2QHBoom7v6kHI85grLeOqplmvtnTiA5Ck2gchzpVmwXWaWpf8qWhCFxfDuMhJBPlWbJvtSmwpDEowrg==",
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/sveltestrap/-/sveltestrap-5.11.2.tgz",
"integrity": "sha512-fkLqIUh2QHBoom7v6kHI85grLeOqplmvtnTiA5Ck2gchzpVmwXWaWpf8qWhCFxfDuMhJBPlWbJvtSmwpDEowrg==",
@ -773,10 +917,26 @@
"node": ">=10"
}
},
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"node_modules/typed-function": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz",
"integrity": "sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==",
"engines": {
"node": ">= 14"
}
},
"node_modules/uplot": {
"version": "1.6.27",
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.27.tgz",
"integrity": "sha512-78U4ss5YeU65kQkOC/QAKiyII+4uo+TYUJJKvuxRzeSpk/s5sjpY1TL0agkmhHBBShpvLtmbHIEiM7+C5lBULg=="
"version": "1.6.27",
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.27.tgz",
"integrity": "sha512-78U4ss5YeU65kQkOC/QAKiyII+4uo+TYUJJKvuxRzeSpk/s5sjpY1TL0agkmhHBBShpvLtmbHIEiM7+C5lBULg=="
},
"node_modules/wonka": {
"version": "6.3.4",

View File

@ -22,6 +22,7 @@
"chart.js": "^4.3.3",
"date-fns": "^2.30.0",
"graphql": "^16.6.0",
"mathjs": "^12.0.0",
"svelte-chartjs": "^3.1.2",
"sveltestrap": "^5.11.1",
"uplot": "^1.6.24",

View File

@ -27,6 +27,7 @@
import TagManagement from "./TagManagement.svelte";
import MetricSelection from "./MetricSelection.svelte";
import StatsTable from "./StatsTable.svelte";
import JobFootprint from "./JobFootprint.svelte";
import { getContext } from "svelte";
export let dbid;
@ -46,7 +47,8 @@
resources { hostname, hwthreads, accelerators },
metaData,
userData { name, email },
concurrentJobs { items { id, jobId }, count, listQuery }
concurrentJobs { items { id, jobId }, count, listQuery },
flopsAnyAvg, memBwAvg, loadAvg
}
`);
@ -134,7 +136,9 @@
let plots = {},
jobTags,
statsTable;
statsTable,
jobFootprint;
$: document.title = $initq.fetching
? "Loading..."
: $initq.error
@ -202,6 +206,17 @@
<Spinner secondary />
{/if}
</Col>
{#if $jobMetrics.data}
{#key $jobMetrics.data}
<Col>
<JobFootprint
bind:this={jobFootprint}
job={$initq.data.job}
jobMetrics={$jobMetrics.data.jobMetrics}
/>
</Col>
{/key}
{/if}
{#if $jobMetrics.data && $initq.data}
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0}
{#if authlevel > roles.manager}

View File

@ -0,0 +1,230 @@
<script>
import { getContext } from 'svelte'
import {
Card,
CardHeader,
CardTitle,
CardBody,
Progress,
Icon,
Tooltip
} from "sveltestrap";
import { mean, round } from 'mathjs'
export let job
export let jobMetrics
export let view = 'job'
export let width = 'auto'
const clusters = getContext('clusters')
const subclusterConfig = clusters.find((c) => c.name == job.cluster).subClusters.find((sc) => sc.name == job.subCluster)
const footprintMetrics = (job.numAcc !== 0)
? (job.exclusive !== 1)
? ['cpu_load', 'flops_any', 'acc_utilization']
: ['cpu_load', 'flops_any', 'acc_utilization', 'mem_bw']
: (job.exclusive !== 1)
? ['cpu_load', 'flops_any', 'mem_used']
: ['cpu_load', 'flops_any', 'mem_used', 'mem_bw']
const footprintData = footprintMetrics.map((fm) => {
// Mean: Primarily use backend sourced avgs from job.*, secondarily calculate/read from metricdata
let mv = null
if (fm === 'cpu_load' && job.loadAvg !== 0) {
mv = round(job.loadAvg, 2)
} else if (fm === 'flops_any' && job.flopsAnyAvg !== 0) {
mv = round(job.flopsAnyAvg, 2)
} else if (fm === 'mem_bw' && job.memBwAvg !== 0) {
mv = round(job.memBwAvg, 2)
} else { // Calculate from jobMetrics
const jm = jobMetrics.find((jm) => jm.name === fm && jm.scope === 'node')
if (jm?.metric?.statisticsSeries) {
mv = round(mean(jm.metric.statisticsSeries.mean), 2)
} else if (jm?.metric?.series?.length > 1) {
const avgs = jm.metric.series.map(jms => jms.statistics.avg)
mv = round(mean(avgs), 2)
} else {
mv = jm.metric.series[0].statistics.avg
}
}
// Unit
const fmc = getContext('metrics')(job.cluster, fm)
let unit = ''
if (fmc?.unit?.base) unit = fmc.unit.prefix + fmc.unit.base
// Threshold / -Differences
const fmt = findJobThresholds(job, fmc, subclusterConfig)
if (fm === 'flops_any') fmt.peak = round((fmt.peak * 0.85), 0)
// Define basic data
const fmBase = {
name: fm,
unit: unit,
avg: mv,
max: fmt.peak
}
if (evalFootprint(fm, mv, fmt, 'alert')) {
return {
...fmBase,
color: 'danger',
message:`Metric average way ${fm === 'mem_used' ? 'above' : 'below' } expected normal thresholds.`,
impact: 3
}
} else if (evalFootprint(fm, mv, fmt, 'caution')) {
return {
...fmBase,
color: 'warning',
message: `Metric average ${fm === 'mem_used' ? 'above' : 'below' } expected normal thresholds.`,
impact: 2
}
} else if (evalFootprint(fm, mv, fmt, 'normal')) {
return {
...fmBase,
color: 'success',
message: 'Metric average within expected thresholds.',
impact: 1
}
} else if (evalFootprint(fm, mv, fmt, 'peak')) {
return {
...fmBase,
color: 'info',
message: 'Metric average above expected normal thresholds: Check for artifacts recommended.',
impact: 0
}
} else {
return {
...fmBase,
color: 'secondary',
message: 'Metric average above expected peak threshold: Check for artifacts!',
impact: -1
}
}
})
function evalFootprint(metric, mean, thresholds, level) {
// mem_used has inverse logic regarding threshold levels
switch (level) {
case 'peak':
if (metric === 'mem_used') return (mean <= thresholds.peak && mean > thresholds.alert)
else return (mean <= thresholds.peak && mean > thresholds.normal)
case 'alert':
if (metric === 'mem_used') return (mean <= thresholds.alert && mean > thresholds.caution)
else return (mean <= thresholds.alert && mean > 0)
case 'caution':
if (metric === 'mem_used') return (mean <= thresholds.caution && mean > thresholds.normal)
else return (mean <= thresholds.caution && mean > thresholds.alert)
case 'normal':
if (metric === 'mem_used') return (mean <= thresholds.normal && mean > 0)
else return (mean <= thresholds.normal && mean > thresholds.caution)
default:
return false
}
}
</script>
<script context="module">
export function findJobThresholds(job, metricConfig, subClusterConfig) {
if (!job || !metricConfig || !subClusterConfig) {
console.warn('Argument missing for findJobThresholds!')
return null
}
const subclusterThresholds = metricConfig.subClusters.find(sc => sc.name == subClusterConfig.name)
const defaultThresholds = {
peak: subclusterThresholds ? subclusterThresholds.peak : metricConfig.peak,
normal: subclusterThresholds ? subclusterThresholds.normal : metricConfig.normal,
caution: subclusterThresholds ? subclusterThresholds.caution : metricConfig.caution,
alert: subclusterThresholds ? subclusterThresholds.alert : metricConfig.alert
}
if (job.exclusive === 1) { // Exclusive: Use as defined
return defaultThresholds
} else { // Shared: Handle specifically
if (metricConfig.name === 'cpu_load') { // Special: Avg Aggregation BUT scaled based on #hwthreads
return {
peak: job.numHWThreads,
normal: job.numHWThreads,
caution: defaultThresholds.caution,
alert: defaultThresholds.alert
}
} else if (metricConfig.aggregation === 'avg' ){
return defaultThresholds
} else if (metricConfig.aggregation === 'sum' ){
const jobFraction = job.numHWThreads / subClusterConfig.topology.node.length
return {
peak: round((defaultThresholds.peak * jobFraction), 0),
normal: round((defaultThresholds.normal * jobFraction), 0),
caution: round((defaultThresholds.caution * jobFraction), 0),
alert: round((defaultThresholds.alert * jobFraction), 0)
}
} else {
console.warn('Missing or unkown aggregation mode (sum/avg) for metric:', metricConfig)
return null
}
} // Other job.exclusive cases?
}
</script>
<Card class="h-auto mt-1" style="width: {width}px;">
{#if view === 'job'}
<CardHeader>
<CardTitle class="mb-0 d-flex justify-content-center">
Core Metrics Footprint
</CardTitle>
</CardHeader>
{/if}
<CardBody>
{#each footprintData as fpd, index}
<div class="mb-1 d-flex justify-content-between">
<div>&nbsp;<b>{fpd.name}</b></div> <!-- For symmetry, see below ...-->
<div class="cursor-help d-inline-flex" id={`footprint-${job.jobId}-${index}`}>
<div class="mx-1">
<!-- Alerts Only -->
{#if fpd.impact === 3 || fpd.impact === -1}
<Icon name="exclamation-triangle-fill" class="text-danger"/>
{:else if fpd.impact === 2}
<Icon name="exclamation-triangle" class="text-warning"/>
{/if}
<!-- Emoji for all states-->
{#if fpd.impact === 3}
<Icon name="emoji-frown" class="text-danger"/>
{:else if fpd.impact === 2}
<Icon name="emoji-neutral" class="text-warning"/>
{:else if fpd.impact === 1}
<Icon name="emoji-smile" class="text-success"/>
{:else if fpd.impact === 0}
<Icon name="emoji-laughing" class="text-info"/>
{:else if fpd.impact === -1}
<Icon name="emoji-dizzy" class="text-danger"/>
{/if}
</div>
<div>
<!-- Print Values -->
{fpd.avg} / {fpd.max} {fpd.unit} &nbsp; <!-- To increase margin to tooltip: No other way manageable ... -->
</div>
</div>
<Tooltip target={`footprint-${job.jobId}-${index}`} placement="right" offset={[0, 20]}>{fpd.message}</Tooltip>
</div>
<div class="mb-2">
<Progress
value={fpd.avg}
max={fpd.max}
color={fpd.color}
/>
</div>
{/each}
{#if job?.metaData?.message}
<hr class="mt-1 mb-2"/>
{@html job.metaData.message}
{/if}
</CardBody>
</Card>
<style>
.cursor-help {
cursor: help;
}
</style>

View File

@ -23,6 +23,9 @@
let metrics = filterPresets.cluster
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || ccconfig.plot_list_selectedMetrics
: ccconfig.plot_list_selectedMetrics
let showFootprint = filterPresets.cluster
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
: !!ccconfig.plot_list_showFootprint
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
// The filterPresets are handled by the Filters component,
@ -81,7 +84,8 @@
bind:metrics={metrics}
bind:sorting={sorting}
bind:matchedJobs={matchedJobs}
bind:this={jobList} />
bind:this={jobList}
bind:showFootprint={showFootprint} />
</Col>
</Row>
@ -93,4 +97,6 @@
bind:cluster={selectedCluster}
configName="plot_list_selectedMetrics"
bind:metrics={metrics}
bind:isOpen={isMetricsSelectionOpen} />
bind:isOpen={isMetricsSelectionOpen}
bind:showFootprint={showFootprint}
view='list'/>

View File

@ -17,12 +17,15 @@
export let configName
export let allMetrics = null
export let cluster = null
export let showFootprint
export let view = 'job'
const clusters = getContext('clusters'),
onInit = getContext('on-init')
let newMetricsOrder = []
let unorderedMetrics = [...metrics]
let pendingShowFootprint = !!showFootprint
onInit(() => {
if (allMetrics == null) allMetrics = new Set()
@ -90,6 +93,8 @@
metrics = newMetricsOrder.filter(m => unorderedMetrics.includes(m))
isOpen = false
showFootprint = !!pendingShowFootprint
updateConfigurationMutation({
name: cluster == null ? configName : `${configName}:${cluster}`,
value: JSON.stringify(metrics)
@ -99,6 +104,16 @@
// console.log('Error on subscription: ' + res.error)
}
})
updateConfigurationMutation({
name: cluster == null ? 'plot_list_showFootprint' : `plot_list_showFootprint:${cluster}`,
value: JSON.stringify(showFootprint)
}).subscribe(res => {
if (res.fetching === false && res.error) {
console.log('Error on footprint subscription: ' + res.error)
throw res.error
}
})
}
</script>
@ -121,6 +136,12 @@
</ModalHeader>
<ModalBody>
<ListGroup>
{#if view === 'list'}
<li class="list-group-item">
<input type="checkbox" bind:checked={pendingShowFootprint}> Show Footprint
</li>
<hr/>
{/if}
{#each newMetricsOrder as metric, index (metric)}
<li class="cc-config-column list-group-item"
draggable={true} ondragover="return false"

View File

@ -27,6 +27,9 @@
let metrics = ccconfig.plot_list_selectedMetrics, isMetricsSelectionOpen = false
let w1, w2, histogramHeight = 250, isHistogramSelectionOpen = false
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
let showFootprint = filterPresets.cluster
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
: !!ccconfig.plot_list_showFootprint
$: metricsInHistograms = selectedCluster ? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] : (ccconfig.user_view_histogramMetrics || [])
@ -211,7 +214,8 @@
<JobList
bind:metrics={metrics}
bind:sorting={sorting}
bind:this={jobList} />
bind:this={jobList}
bind:showFootprint={showFootprint} />
</Col>
</Row>
@ -223,7 +227,9 @@
bind:cluster={selectedCluster}
configName="plot_list_selectedMetrics"
bind:metrics={metrics}
bind:isOpen={isMetricsSelectionOpen} />
bind:isOpen={isMetricsSelectionOpen}
bind:showFootprint={showFootprint}
view='list'/>
<HistogramSelection
bind:cluster={selectedCluster}

View File

@ -28,6 +28,7 @@
export let sorting = { field: "startTime", order: "DESC" };
export let matchedJobs = 0;
export let metrics = ccconfig.plot_list_selectedMetrics;
export let showFootprint;
let itemsPerPage = ccconfig.plot_list_jobsPerPage;
let page = 1;
@ -73,6 +74,9 @@
name
}
metaData
flopsAnyAvg
memBwAvg
loadAvg
}
count
}
@ -134,12 +138,19 @@
})
};
let plotWidth = null;
let tableWidth = null;
let jobInfoColumnWidth = 250;
$: plotWidth = Math.floor(
(tableWidth - jobInfoColumnWidth) / metrics.length - 10
);
$: if (showFootprint) {
plotWidth = Math.floor(
(tableWidth - jobInfoColumnWidth) / (metrics.length + 1) - 10
)
} else {
plotWidth = Math.floor(
(tableWidth - jobInfoColumnWidth) / metrics.length - 10
)
}
let headerPaddingTop = 0;
stickyHeader(
@ -160,6 +171,15 @@
>
Job Info
</th>
{#if showFootprint}
<th
class="position-sticky top-0"
scope="col"
style="width: {plotWidth}px; padding-top: {headerPaddingTop}px"
>
Job Footprint
</th>
{/if}
{#each metrics as metric (metric)}
<th
class="position-sticky top-0 text-center"
@ -212,7 +232,7 @@
</tr>
{:else if $jobs.data && $initialized}
{#each $jobs.data.jobs.items as job (job)}
<JobListRow {job} {metrics} {plotWidth} />
<JobListRow {job} {metrics} {plotWidth} {showFootprint}/>
{:else}
<tr>
<td colspan={metrics.length + 1}>

View File

@ -14,22 +14,28 @@
import { Card, Spinner } from "sveltestrap";
import MetricPlot from "../plots/MetricPlot.svelte";
import JobInfo from "./JobInfo.svelte";
import JobFootprint from "../JobFootprint.svelte";
import { maxScope, checkMetricDisabled } from "../utils.js";
export let job;
export let metrics;
export let plotWidth;
export let plotHeight = 275;
export let showFootprint;
let { id } = job;
let scopes = [job.numNodes == 1 ? "core" : "node"];
function distinct(value, index, array) {
return array.indexOf(value) === index;
}
const cluster = getContext("clusters").find((c) => c.name == job.cluster);
const metricConfig = getContext("metrics"); // Get all MetricConfs which include subCluster-specific settings for this job
const client = getContextClient();
const query = gql`
query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!) {
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes) {
query ($id: ID!, $queryMetrics: [String!]!, $scopes: [MetricScope!]!) {
jobMetrics(id: $id, metrics: $queryMetrics, scopes: $scopes) {
name
scope
metric {
@ -61,14 +67,23 @@
$: metricsQuery = queryStore({
client: client,
query: query,
variables: { id, metrics, scopes }
variables: { id, queryMetrics, scopes }
});
let queryMetrics = null
$: if (showFootprint) {
queryMetrics = ['cpu_load', 'flops_any', 'mem_used', 'mem_bw', 'acc_utilization', ...metrics].filter(distinct)
scopes = ["node"]
} else {
queryMetrics = [...metrics]
scopes = [job.numNodes == 1 ? "core" : "node"]
}
export function refresh() {
metricsQuery = queryStore({
client: client,
query: query,
variables: { id, metrics, scopes },
variables: { id, queryMetrics, scopes },
// requestPolicy: 'network-only' // use default cache-first for refresh
});
}
@ -122,6 +137,16 @@
</Card>
</td>
{:else}
{#if showFootprint}
<td>
<JobFootprint
job={job}
jobMetrics={$metricsQuery.data.jobMetrics}
width={plotWidth}
view="list"
/>
</td>
{/if}
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
<td>
<!-- Subluster Metricconfig remove keyword for jobtables (joblist main, user joblist, project joblist) to be used here as toplevel case-->

View File

@ -161,8 +161,8 @@
? (statisticsSeries.max.reduce((max, x) => Math.max(max, x), thresholds.normal) || thresholds.normal)
: (series.reduce((max, series) => Math.max(max, series.statistics?.max), thresholds.normal) || thresholds.normal)
if (maxY >= (10 * thresholds.normal)) { // Hard y-range render limit if outliers in series data
maxY = (10 * thresholds.normal)
if (maxY >= (10 * thresholds.peak)) { // Hard y-range render limit if outliers in series data
maxY = (10 * thresholds.peak)
}
}
@ -390,12 +390,12 @@
if (scope == 'node' || metricConfig.aggregation == 'avg') {
if (metricConfig.subClusters && metricConfig.subClusters.length === 0) {
// console.log('subClusterConfigs array empty, use metricConfig defaults')
return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert }
return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert, peak: metricConfig.peak }
} else if (metricConfig.subClusters && metricConfig.subClusters.length > 0) {
// console.log('subClusterConfigs found, use subCluster Settings if matching jobs subcluster:')
let forSubCluster = metricConfig.subClusters.find(sc => sc.name == subCluster.name)
if (forSubCluster && forSubCluster.normal && forSubCluster.caution && forSubCluster.alert) return forSubCluster
else return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert }
if (forSubCluster && forSubCluster.normal && forSubCluster.caution && forSubCluster.alert && forSubCluster.peak) return forSubCluster
else return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert, peak: metricConfig.peak}
} else {
console.warn('metricConfig.subClusters not found!')
return null
@ -423,6 +423,7 @@
let mc = metricConfig?.subClusters?.find(sc => sc.name == subCluster.name) || metricConfig
return {
peak: mc.peak / divisor,
normal: mc.normal / divisor,
caution: mc.caution / divisor,
alert: mc.alert / divisor

View File

@ -176,12 +176,12 @@
// Debug get zoomLevel from browser
// console.log("Zoom", Math.round(window.devicePixelRatio * 100))
if (scalarKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Top horizontal roofline
if (scalarKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Lower horizontal roofline
u.ctx.moveTo(scalarKneeX, flopRateScalarY)
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateScalarY)
}
if (simdKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Lower horitontal roofline
if (simdKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Top horitontal roofline
u.ctx.moveTo(simdKneeX, flopRateSimdY)
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateSimdY)
}
@ -214,6 +214,7 @@
}
]
},
// cursor: { drag: { x: true, y: true } } // Activate zoom
};
uplot = new uPlot(opts, plotData, plotWrapper);
} else {