Merge pull request #116 from ClusterCockpit/97_107_mark_and_show_shared

97 107 mark and show shared
This commit is contained in:
Christoph Kluge 2023-06-01 17:55:16 +02:00 committed by GitHub
commit 0d4935e244
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 710 additions and 13 deletions

View File

@ -27,11 +27,17 @@ type Job {
state: JobState! state: JobState!
tags: [Tag!]! tags: [Tag!]!
resources: [Resource!]! resources: [Resource!]!
concurrentJobs: JobLinkResultList
metaData: Any metaData: Any
userData: User userData: User
} }
type JobLink {
id: ID!
jobId: Int!
}
type Cluster { type Cluster {
name: String! name: String!
partitions: [String!]! # Slurm partitions partitions: [String!]! # Slurm partitions
@ -230,6 +236,12 @@ input JobFilter {
memBwAvg: FloatRange memBwAvg: FloatRange
loadAvg: FloatRange loadAvg: FloatRange
memUsedMax: FloatRange memUsedMax: FloatRange
exclusive: Int
sharedNode: StringInput
selfJobId: StringInput
selfStartTime: Time
selfDuration: Int
} }
input OrderByInput { input OrderByInput {
@ -244,6 +256,7 @@ enum SortDirectionEnum {
input StringInput { input StringInput {
eq: String eq: String
neq: String
contains: String contains: String
startsWith: String startsWith: String
endsWith: String endsWith: String
@ -261,6 +274,11 @@ type JobResultList {
count: Int count: Int
} }
type JobLinkResultList {
items: [JobLink!]!
count: Int
}
type HistoPoint { type HistoPoint {
count: Int! count: Int!
value: Int! value: Int!

View File

@ -85,6 +85,7 @@ type ComplexityRoot struct {
Job struct { Job struct {
ArrayJobId func(childComplexity int) int ArrayJobId func(childComplexity int) int
Cluster func(childComplexity int) int Cluster func(childComplexity int) int
ConcurrentJobs func(childComplexity int) int
Duration func(childComplexity int) int Duration func(childComplexity int) int
Exclusive func(childComplexity int) int Exclusive func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
@ -108,6 +109,16 @@ type ComplexityRoot struct {
Walltime func(childComplexity int) int Walltime func(childComplexity int) int
} }
JobLink struct {
ID func(childComplexity int) int
JobID func(childComplexity int) int
}
JobLinkResultList struct {
Count func(childComplexity int) int
Items func(childComplexity int) int
}
JobMetric struct { JobMetric struct {
Series func(childComplexity int) int Series func(childComplexity int) int
StatisticsSeries func(childComplexity int) int StatisticsSeries func(childComplexity int) int
@ -280,6 +291,7 @@ type JobResolver interface {
Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag, error) 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) MetaData(ctx context.Context, obj *schema.Job) (interface{}, error)
UserData(ctx context.Context, obj *schema.Job) (*model.User, error) UserData(ctx context.Context, obj *schema.Job) (*model.User, error)
} }
@ -442,6 +454,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Job.Cluster(childComplexity), true return e.complexity.Job.Cluster(childComplexity), true
case "Job.concurrentJobs":
if e.complexity.Job.ConcurrentJobs == nil {
break
}
return e.complexity.Job.ConcurrentJobs(childComplexity), true
case "Job.duration": case "Job.duration":
if e.complexity.Job.Duration == nil { if e.complexity.Job.Duration == nil {
break break
@ -589,6 +608,34 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Job.Walltime(childComplexity), true return e.complexity.Job.Walltime(childComplexity), true
case "JobLink.id":
if e.complexity.JobLink.ID == nil {
break
}
return e.complexity.JobLink.ID(childComplexity), true
case "JobLink.jobId":
if e.complexity.JobLink.JobID == nil {
break
}
return e.complexity.JobLink.JobID(childComplexity), true
case "JobLinkResultList.count":
if e.complexity.JobLinkResultList.Count == nil {
break
}
return e.complexity.JobLinkResultList.Count(childComplexity), true
case "JobLinkResultList.items":
if e.complexity.JobLinkResultList.Items == nil {
break
}
return e.complexity.JobLinkResultList.Items(childComplexity), true
case "JobMetric.series": case "JobMetric.series":
if e.complexity.JobMetric.Series == nil { if e.complexity.JobMetric.Series == nil {
break break
@ -1468,11 +1515,17 @@ type Job {
state: JobState! state: JobState!
tags: [Tag!]! tags: [Tag!]!
resources: [Resource!]! resources: [Resource!]!
concurrentJobs: JobLinkResultList
metaData: Any metaData: Any
userData: User userData: User
} }
type JobLink {
id: ID!
jobId: Int!
}
type Cluster { type Cluster {
name: String! name: String!
partitions: [String!]! # Slurm partitions partitions: [String!]! # Slurm partitions
@ -1671,6 +1724,12 @@ input JobFilter {
memBwAvg: FloatRange memBwAvg: FloatRange
loadAvg: FloatRange loadAvg: FloatRange
memUsedMax: FloatRange memUsedMax: FloatRange
exclusive: Int
sharedNode: StringInput
selfJobId: StringInput
selfStartTime: Time
selfDuration: Int
} }
input OrderByInput { input OrderByInput {
@ -1685,6 +1744,7 @@ enum SortDirectionEnum {
input StringInput { input StringInput {
eq: String eq: String
neq: String
contains: String contains: String
startsWith: String startsWith: String
endsWith: String endsWith: String
@ -1702,6 +1762,11 @@ type JobResultList {
count: Int count: Int
} }
type JobLinkResultList {
items: [JobLink!]!
count: Int
}
type HistoPoint { type HistoPoint {
count: Int! count: Int!
value: Int! value: Int!
@ -3875,6 +3940,53 @@ func (ec *executionContext) fieldContext_Job_resources(ctx context.Context, fiel
return fc, nil return fc, nil
} }
func (ec *executionContext) _Job_concurrentJobs(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Job_concurrentJobs(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 ec.resolvers.Job().ConcurrentJobs(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*model.JobLinkResultList)
fc.Result = res
return ec.marshalOJobLinkResultList2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkResultList(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Job_concurrentJobs(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Job",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "items":
return ec.fieldContext_JobLinkResultList_items(ctx, field)
case "count":
return ec.fieldContext_JobLinkResultList_count(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type JobLinkResultList", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Job_metaData(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) { 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) fc, err := ec.fieldContext_Job_metaData(ctx, field)
if err != nil { if err != nil {
@ -3965,6 +4077,185 @@ func (ec *executionContext) fieldContext_Job_userData(ctx context.Context, field
return fc, nil return fc, nil
} }
func (ec *executionContext) _JobLink_id(ctx context.Context, field graphql.CollectedField, obj *model.JobLink) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_JobLink_id(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.ID, 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.marshalNID2string(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_JobLink_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "JobLink",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type ID does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _JobLink_jobId(ctx context.Context, field graphql.CollectedField, obj *model.JobLink) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_JobLink_jobId(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.JobID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_JobLink_jobId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "JobLink",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Int does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _JobLinkResultList_items(ctx context.Context, field graphql.CollectedField, obj *model.JobLinkResultList) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_JobLinkResultList_items(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.Items, 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.JobLink)
fc.Result = res
return ec.marshalNJobLink2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_JobLinkResultList_items(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "JobLinkResultList",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_JobLink_id(ctx, field)
case "jobId":
return ec.fieldContext_JobLink_jobId(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type JobLink", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _JobLinkResultList_count(ctx context.Context, field graphql.CollectedField, obj *model.JobLinkResultList) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_JobLinkResultList_count(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Count, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*int)
fc.Result = res
return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_JobLinkResultList_count(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "JobLinkResultList",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Int does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _JobMetric_unit(ctx context.Context, field graphql.CollectedField, obj *schema.JobMetric) (ret graphql.Marshaler) { func (ec *executionContext) _JobMetric_unit(ctx context.Context, field graphql.CollectedField, obj *schema.JobMetric) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_JobMetric_unit(ctx, field) fc, err := ec.fieldContext_JobMetric_unit(ctx, field)
if err != nil { if err != nil {
@ -4379,6 +4670,8 @@ func (ec *executionContext) fieldContext_JobResultList_items(ctx context.Context
return ec.fieldContext_Job_tags(ctx, field) return ec.fieldContext_Job_tags(ctx, field)
case "resources": case "resources":
return ec.fieldContext_Job_resources(ctx, field) return ec.fieldContext_Job_resources(ctx, field)
case "concurrentJobs":
return ec.fieldContext_Job_concurrentJobs(ctx, field)
case "metaData": case "metaData":
return ec.fieldContext_Job_metaData(ctx, field) return ec.fieldContext_Job_metaData(ctx, field)
case "userData": case "userData":
@ -6376,6 +6669,8 @@ func (ec *executionContext) fieldContext_Query_job(ctx context.Context, field gr
return ec.fieldContext_Job_tags(ctx, field) return ec.fieldContext_Job_tags(ctx, field)
case "resources": case "resources":
return ec.fieldContext_Job_resources(ctx, field) return ec.fieldContext_Job_resources(ctx, field)
case "concurrentJobs":
return ec.fieldContext_Job_concurrentJobs(ctx, field)
case "metaData": case "metaData":
return ec.fieldContext_Job_metaData(ctx, field) return ec.fieldContext_Job_metaData(ctx, field)
case "userData": case "userData":
@ -10741,7 +11036,7 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "jobName", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax"} fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "jobName", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax", "exclusive", "sharedNode", "selfJobId", "selfStartTime", "selfDuration"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -10900,6 +11195,46 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int
if err != nil { if err != nil {
return it, err return it, err
} }
case "exclusive":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("exclusive"))
it.Exclusive, err = ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil {
return it, err
}
case "sharedNode":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sharedNode"))
it.SharedNode, err = ec.unmarshalOStringInput2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐStringInput(ctx, v)
if err != nil {
return it, err
}
case "selfJobId":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("selfJobId"))
it.SelfJobID, err = ec.unmarshalOStringInput2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐStringInput(ctx, v)
if err != nil {
return it, err
}
case "selfStartTime":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("selfStartTime"))
it.SelfStartTime, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v)
if err != nil {
return it, err
}
case "selfDuration":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("selfDuration"))
it.SelfDuration, err = ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -10989,7 +11324,7 @@ func (ec *executionContext) unmarshalInputStringInput(ctx context.Context, obj i
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"eq", "contains", "startsWith", "endsWith", "in"} fieldsInOrder := [...]string{"eq", "neq", "contains", "startsWith", "endsWith", "in"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -11004,6 +11339,14 @@ func (ec *executionContext) unmarshalInputStringInput(ctx context.Context, obj i
if err != nil { if err != nil {
return it, err return it, err
} }
case "neq":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("neq"))
it.Neq, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "contains": case "contains":
var err error var err error
@ -11510,6 +11853,23 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1) atomic.AddUint32(&invalids, 1)
} }
case "concurrentJobs":
field := field
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Job_concurrentJobs(ctx, field, obj)
return res
}
out.Concurrently(i, func() graphql.Marshaler {
return innerFunc(ctx)
})
case "metaData": case "metaData":
field := field field := field
@ -11555,6 +11915,73 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj
return out return out
} }
var jobLinkImplementors = []string{"JobLink"}
func (ec *executionContext) _JobLink(ctx context.Context, sel ast.SelectionSet, obj *model.JobLink) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, jobLinkImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("JobLink")
case "id":
out.Values[i] = ec._JobLink_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "jobId":
out.Values[i] = ec._JobLink_jobId(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 jobLinkResultListImplementors = []string{"JobLinkResultList"}
func (ec *executionContext) _JobLinkResultList(ctx context.Context, sel ast.SelectionSet, obj *model.JobLinkResultList) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, jobLinkResultListImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("JobLinkResultList")
case "items":
out.Values[i] = ec._JobLinkResultList_items(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "count":
out.Values[i] = ec._JobLinkResultList_count(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var jobMetricImplementors = []string{"JobMetric"} var jobMetricImplementors = []string{"JobMetric"}
func (ec *executionContext) _JobMetric(ctx context.Context, sel ast.SelectionSet, obj *schema.JobMetric) graphql.Marshaler { func (ec *executionContext) _JobMetric(ctx context.Context, sel ast.SelectionSet, obj *schema.JobMetric) graphql.Marshaler {
@ -13686,6 +14113,60 @@ func (ec *executionContext) unmarshalNJobFilter2ᚖgithubᚗcomᚋClusterCockpit
return &res, graphql.ErrorOnPath(ctx, err) return &res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) marshalNJobLink2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.JobLink) 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.marshalNJobLink2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLink(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalNJobLink2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLink(ctx context.Context, sel ast.SelectionSet, v *model.JobLink) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._JobLink(ctx, sel, v)
}
func (ec *executionContext) marshalNJobMetric2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋpkgᚋschemaᚐJobMetric(ctx context.Context, sel ast.SelectionSet, v *schema.JobMetric) graphql.Marshaler { func (ec *executionContext) marshalNJobMetric2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋpkgᚋschemaᚐJobMetric(ctx context.Context, sel ast.SelectionSet, v *schema.JobMetric) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@ -14944,6 +15425,13 @@ func (ec *executionContext) unmarshalOJobFilter2ᚖgithubᚗcomᚋClusterCockpit
return &res, graphql.ErrorOnPath(ctx, err) return &res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) marshalOJobLinkResultList2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkResultList(ctx context.Context, sel ast.SelectionSet, v *model.JobLinkResultList) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._JobLinkResultList(ctx, sel, v)
}
func (ec *executionContext) unmarshalOJobState2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋpkgᚋschemaᚐJobStateᚄ(ctx context.Context, v interface{}) ([]schema.JobState, error) { func (ec *executionContext) unmarshalOJobState2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋpkgᚋschemaᚐJobStateᚄ(ctx context.Context, v interface{}) ([]schema.JobState, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@ -56,6 +56,21 @@ type JobFilter struct {
MemBwAvg *FloatRange `json:"memBwAvg"` MemBwAvg *FloatRange `json:"memBwAvg"`
LoadAvg *FloatRange `json:"loadAvg"` LoadAvg *FloatRange `json:"loadAvg"`
MemUsedMax *FloatRange `json:"memUsedMax"` MemUsedMax *FloatRange `json:"memUsedMax"`
Exclusive *int `json:"exclusive"`
SharedNode *StringInput `json:"sharedNode"`
SelfJobID *StringInput `json:"selfJobId"`
SelfStartTime *time.Time `json:"selfStartTime"`
SelfDuration *int `json:"selfDuration"`
}
type JobLink struct {
ID string `json:"id"`
JobID int `json:"jobId"`
}
type JobLinkResultList struct {
Items []*JobLink `json:"items"`
Count *int `json:"count"`
} }
type JobMetricWithName struct { type JobMetricWithName struct {
@ -105,6 +120,7 @@ type PageRequest struct {
type StringInput struct { type StringInput struct {
Eq *string `json:"eq"` Eq *string `json:"eq"`
Neq *string `json:"neq"`
Contains *string `json:"contains"` Contains *string `json:"contains"`
StartsWith *string `json:"startsWith"` StartsWith *string `json:"startsWith"`
EndsWith *string `json:"endsWith"` EndsWith *string `json:"endsWith"`

View File

@ -36,6 +36,39 @@ func (r *jobResolver) Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag,
return r.Repo.GetTags(&obj.ID) return r.Repo.GetTags(&obj.ID)
} }
// ConcurrentJobs is the resolver for the concurrentJobs field.
func (r *jobResolver) ConcurrentJobs(ctx context.Context, obj *schema.Job) (*model.JobLinkResultList, error) {
exc := int(obj.Exclusive)
if exc != 1 {
filter := []*model.JobFilter{}
jid := fmt.Sprint(obj.JobID)
jdu := int(obj.Duration)
filter = append(filter, &model.JobFilter{Exclusive: &exc})
filter = append(filter, &model.JobFilter{SharedNode: &model.StringInput{Contains: &obj.Resources[0].Hostname}})
filter = append(filter, &model.JobFilter{SelfJobID: &model.StringInput{Neq: &jid}})
filter = append(filter, &model.JobFilter{SelfStartTime: &obj.StartTime, SelfDuration: &jdu})
jobLinks, err := r.Repo.QueryJobLinks(ctx, filter)
if err != nil {
log.Warn("Error while querying jobLinks")
return nil, err
}
count, err := r.Repo.CountJobs(ctx, filter)
if err != nil {
log.Warn("Error while counting jobLinks")
return nil, err
}
result := &model.JobLinkResultList{Items: jobLinks, Count: &count}
return result, nil
}
return nil, nil
}
// MetaData is the resolver for the metaData field. // MetaData is the resolver for the metaData field.
func (r *jobResolver) MetaData(ctx context.Context, obj *schema.Job) (interface{}, error) { func (r *jobResolver) MetaData(ctx context.Context, obj *schema.Job) (interface{}, error) {
return r.Repo.FetchMetadata(obj) return r.Repo.FetchMetadata(obj)

View File

@ -74,7 +74,7 @@ func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
&job.ID, &job.JobID, &job.User, &job.Project, &job.Cluster, &job.SubCluster, &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.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.RawMetaData*/); err != nil {
log.Warnf("Error while scanning rows: %v", err) log.Warnf("Error while scanning rows (Job): %v", err)
return nil, err return nil, err
} }
@ -140,6 +140,17 @@ func (r *JobRepository) Flush() error {
return nil return nil
} }
func scanJobLink(row interface{ Scan(...interface{}) error }) (*model.JobLink, error) {
jobLink := &model.JobLink{}
if err := row.Scan(
&jobLink.ID, &jobLink.JobID); err != nil {
log.Warn("Error while scanning rows (jobLink)")
return nil, err
}
return jobLink, nil
}
func (r *JobRepository) FetchJobName(job *schema.Job) (*string, error) { func (r *JobRepository) FetchJobName(job *schema.Job) (*string, error) {
start := time.Now() start := time.Now()
cachekey := fmt.Sprintf("metadata:%d", job.ID) cachekey := fmt.Sprintf("metadata:%d", job.ID)

View File

@ -19,6 +19,7 @@ import (
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
) )
// SecurityCheck-less, private: Returns a list of jobs matching the provided filters. page and order are optional-
func (r *JobRepository) queryJobs( func (r *JobRepository) queryJobs(
query sq.SelectBuilder, query sq.SelectBuilder,
filters []*model.JobFilter, filters []*model.JobFilter,
@ -65,7 +66,7 @@ func (r *JobRepository) queryJobs(
job, err := scanJob(rows) job, err := scanJob(rows)
if err != nil { if err != nil {
rows.Close() rows.Close()
log.Warn("Error while scanning rows") log.Warn("Error while scanning rows (Jobs)")
return nil, err return nil, err
} }
jobs = append(jobs, job) jobs = append(jobs, job)
@ -74,6 +75,7 @@ func (r *JobRepository) queryJobs(
return jobs, nil return jobs, nil
} }
// testFunction for queryJobs
func (r *JobRepository) testQueryJobs( func (r *JobRepository) testQueryJobs(
filters []*model.JobFilter, filters []*model.JobFilter,
page *model.PageRequest, page *model.PageRequest,
@ -83,7 +85,7 @@ func (r *JobRepository) testQueryJobs(
filters, page, order) filters, page, order)
} }
// QueryJobs returns a list of jobs matching the provided filters. page and order are optional- // Public function with added securityCheck, calls private queryJobs function above
func (r *JobRepository) QueryJobs( func (r *JobRepository) QueryJobs(
ctx context.Context, ctx context.Context,
filters []*model.JobFilter, filters []*model.JobFilter,
@ -100,6 +102,63 @@ func (r *JobRepository) QueryJobs(
filters, page, order) filters, page, order)
} }
// SecurityCheck-less, private: returns a list of minimal job information (DB-ID and jobId) of shared jobs for link-building based the provided filters.
func (r *JobRepository) queryJobLinks(
query sq.SelectBuilder,
filters []*model.JobFilter) ([]*model.JobLink, error) {
for _, f := range filters {
query = BuildWhereClause(f, query)
}
sql, args, err := query.ToSql()
if err != nil {
log.Warn("Error while converting query to sql")
return nil, err
}
log.Debugf("SQL query: `%s`, args: %#v", sql, args)
rows, err := query.RunWith(r.stmtCache).Query()
if err != nil {
log.Error("Error while running query")
return nil, err
}
jobLinks := make([]*model.JobLink, 0, 50)
for rows.Next() {
jobLink, err := scanJobLink(rows)
if err != nil {
rows.Close()
log.Warn("Error while scanning rows (JobLinks)")
return nil, err
}
jobLinks = append(jobLinks, jobLink)
}
return jobLinks, nil
}
// testFunction for queryJobLinks
func (r *JobRepository) testQueryJobLinks(
filters []*model.JobFilter) ([]*model.JobLink, error) {
return r.queryJobLinks(sq.Select(jobColumns...).From("job"), filters)
}
func (r *JobRepository) QueryJobLinks(
ctx context.Context,
filters []*model.JobFilter) ([]*model.JobLink, error) {
query, qerr := SecurityCheck(ctx, sq.Select("job.id", "job.job_id").From("job"))
if qerr != nil {
return nil, qerr
}
return r.queryJobLinks(query, filters)
}
// SecurityCheck-less, private: Returns the number of jobs matching the filters
func (r *JobRepository) countJobs(query sq.SelectBuilder, func (r *JobRepository) countJobs(query sq.SelectBuilder,
filters []*model.JobFilter) (int, error) { filters []*model.JobFilter) (int, error) {
@ -122,12 +181,14 @@ func (r *JobRepository) countJobs(query sq.SelectBuilder,
return count, nil return count, nil
} }
// testFunction for countJobs
func (r *JobRepository) testCountJobs( func (r *JobRepository) testCountJobs(
filters []*model.JobFilter) (int, error) { filters []*model.JobFilter) (int, error) {
return r.countJobs(sq.Select("count(*)").From("job"), filters) return r.countJobs(sq.Select("count(*)").From("job"), filters)
} }
// Public function with added securityCheck, calls private countJobs function above
func (r *JobRepository) CountJobs( func (r *JobRepository) CountJobs(
ctx context.Context, ctx context.Context,
filters []*model.JobFilter) (int, error) { filters []*model.JobFilter) (int, error) {
@ -226,6 +287,21 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select
if filter.MemUsedMax != nil { if filter.MemUsedMax != nil {
query = buildFloatCondition("job.mem_used_max", filter.MemUsedMax, query) query = buildFloatCondition("job.mem_used_max", filter.MemUsedMax, query)
} }
// Shared Jobs Query
if filter.Exclusive != nil {
query = query.Where("job.exclusive = ?", *filter.Exclusive)
}
if filter.SharedNode != nil {
query = buildStringCondition("job.resources", filter.SharedNode, query)
}
if filter.SelfJobID != nil {
query = buildStringCondition("job.job_id", filter.SelfJobID, query)
}
if filter.SelfStartTime != nil && filter.SelfDuration != nil {
start := filter.SelfStartTime.Unix() + 10 // There does not seem to be a portable way to get the current unix timestamp accross different DBs.
end := start + int64(*filter.SelfDuration) - 20
query = query.Where("((job.start_time BETWEEN ? AND ?) OR ((job.start_time + job.duration) BETWEEN ? AND ?))", start, end, start, end)
}
return query return query
} }
@ -253,6 +329,9 @@ func buildStringCondition(field string, cond *model.StringInput, query sq.Select
if cond.Eq != nil { if cond.Eq != nil {
return query.Where(field+" = ?", *cond.Eq) return query.Where(field+" = ?", *cond.Eq)
} }
if cond.Neq != nil {
return query.Where(field+" != ?", *cond.Neq)
}
if cond.StartsWith != nil { if cond.StartsWith != nil {
return query.Where(field+" LIKE ?", fmt.Sprint(*cond.StartsWith, "%")) return query.Where(field+" LIKE ?", fmt.Sprint(*cond.StartsWith, "%"))
} }

View File

@ -38,6 +38,7 @@ type BaseJob struct {
Resources []*Resource `json:"resources"` // Resources used by job Resources []*Resource `json:"resources"` // Resources used by job
RawMetaData []byte `json:"-" db:"meta_data"` // Additional information about the job [As Bytes] RawMetaData []byte `json:"-" db:"meta_data"` // Additional information about the job [As Bytes]
MetaData map[string]string `json:"metaData"` // Additional information about the job MetaData map[string]string `json:"metaData"` // Additional information about the job
ConcurrentJobs JobLinkResultList `json:"concurrentJobs"`
} }
// Job struct type // Job struct type
@ -72,6 +73,17 @@ type Job struct {
// *int64 `json:"id,omitempty"` >> never used in the job-archive, only // *int64 `json:"id,omitempty"` >> never used in the job-archive, only
// available via REST-API // available via REST-API
// //
type JobLink struct {
ID int64 `json:"id"`
JobID int64 `json:"jobId"`
}
type JobLinkResultList struct {
Items []*JobLink `json:"items"`
Count int `json:"count"`
}
// JobMeta model // JobMeta model
// @Description Meta data information of a HPC job. // @Description Meta data information of a HPC job.
type JobMeta struct { type JobMeta struct {

View File

@ -14,6 +14,8 @@
import { getContext } from 'svelte' import { getContext } from 'svelte'
export let dbid export let dbid
export let authlevel
export let roles
const { query: initq } = init(` const { query: initq } = init(`
job(id: "${dbid}") { job(id: "${dbid}") {
@ -23,8 +25,9 @@
monitoringStatus, state, walltime, monitoringStatus, state, walltime,
tags { id, type, name }, tags { id, type, name },
resources { hostname, hwthreads, accelerators }, resources { hostname, hwthreads, accelerators },
metaData metaData,
userData { name, email } userData { name, email },
concurrentJobs { items { id, jobId }, count }
} }
`) `)
@ -101,6 +104,23 @@
{/if} {/if}
</Col> </Col>
{#if $jobMetrics.data && $initq.data} {#if $jobMetrics.data && $initq.data}
{#if $initq.data.job.concurrentJobs != null}
{#if authlevel > roles.manager}
<Col>
<h5>Concurrent Jobs <Icon name="info-circle" style="cursor:help;" title="Shared jobs running on the same node with overlapping runtimes"/></h5>
<ul>
{#each $initq.data.job.concurrentJobs.items as pjob, index}
<li><a href="/monitoring/job/{pjob.id}" target="_blank">{pjob.jobId}</a></li>
{/each}
</ul>
</Col>
{:else}
<Col>
<h5>{$initq.data.job.concurrentJobs.items.length} Concurrent Jobs</h5>
<p>Number of shared jobs on the same node with overlapping runtimes.</p>
</Col>
{/if}
{/if}
<Col> <Col>
<PolarPlot <PolarPlot
width={polarPlotSize} height={polarPlotSize} width={polarPlotSize} height={polarPlotSize}
@ -166,7 +186,8 @@
metricName={item.metric} metricName={item.metric}
rawData={item.data.map(x => x.metric)} rawData={item.data.map(x => x.metric)}
scopes={item.data.map(x => x.scope)} scopes={item.data.map(x => x.scope)}
width={width}/> width={width}
isShared={($initq.data.job.exclusive != 1)}/>
{:else} {:else}
<Card body color="warning">No data for <code>{item.metric}</code></Card> <Card body color="warning">No data for <code>{item.metric}</code></Card>
{/if} {/if}
@ -250,4 +271,10 @@
border-radius: 5px; border-radius: 5px;
padding: 5px; padding: 5px;
} }
ul {
columns: 2;
-webkit-columns: 2;
-moz-columns: 2;
}
</style> </style>

View File

@ -9,6 +9,7 @@
export let scopes export let scopes
export let width export let width
export let rawData export let rawData
export let isShared = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const cluster = getContext('clusters').find(cluster => cluster.name == job.cluster) const cluster = getContext('clusters').find(cluster => cluster.name == job.cluster)
@ -87,6 +88,7 @@
cluster={cluster} subCluster={subCluster} cluster={cluster} subCluster={subCluster}
timestep={data.timestep} timestep={data.timestep}
scope={selectedScope} metric={metricName} scope={selectedScope} metric={metricName}
series={series} /> series={series}
isShared={isShared} />
{/if} {/if}
{/key} {/key}

View File

@ -4,7 +4,9 @@ import Job from './Job.root.svelte'
new Job({ new Job({
target: document.getElementById('svelte-app'), target: document.getElementById('svelte-app'),
props: { props: {
dbid: jobInfos.id dbid: jobInfos.id,
authlevel: authlevel,
roles: roles
}, },
context: new Map([ context: new Map([
['cc-config', clusterCockpitConfig] ['cc-config', clusterCockpitConfig]

View File

@ -24,6 +24,7 @@
const seconds = duration; const seconds = duration;
return `${hours}:${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}`; return `${hours}:${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}`;
} }
</script> </script>
<div> <div>

View File

@ -174,6 +174,7 @@
metric={metric.data.name} metric={metric.data.name}
{cluster} {cluster}
subCluster={job.subCluster} subCluster={job.subCluster}
isShared={(job.exclusive != 1)}
/> />
{:else if metric.removed == true && metric.data == null} {:else if metric.removed == true && metric.data == null}
<Card body color="info" <Card body color="info"

View File

@ -35,6 +35,7 @@
export let metric export let metric
export let useStatsSeries = null export let useStatsSeries = null
export let scope = 'node' export let scope = 'node'
export let isShared = false
if (useStatsSeries == null) if (useStatsSeries == null)
useStatsSeries = statisticsSeries != null useStatsSeries = statisticsSeries != null
@ -142,13 +143,17 @@
hooks: { hooks: {
draw: [(u) => { draw: [(u) => {
// Draw plot type label: // Draw plot type label:
let text = `${scope}${plotSeries.length > 2 ? 's' : ''}${ let textl = `${scope}${plotSeries.length > 2 ? 's' : ''}${
useStatsSeries ? ': min/avg/max' : (metricConfig != null && scope != metricConfig.scope ? ` (${metricConfig.aggregation})` : '')}` useStatsSeries ? ': min/avg/max' : (metricConfig != null && scope != metricConfig.scope ? ` (${metricConfig.aggregation})` : '')}`
let textr = `${(isShared && (scope != 'core' && scope != 'accelerator')) ? '[Shared]' : '' }`
u.ctx.save() u.ctx.save()
u.ctx.textAlign = 'start' // 'end' u.ctx.textAlign = 'start' // 'end'
u.ctx.fillStyle = 'black' u.ctx.fillStyle = 'black'
u.ctx.fillText(text, u.bbox.left + 10, u.bbox.top + 10) u.ctx.fillText(textl, u.bbox.left + 10, u.bbox.top + 10)
// u.ctx.fillText(text, u.bbox.left + u.bbox.width - 10, u.bbox.top + u.bbox.height - 10) u.ctx.textAlign = 'end'
u.ctx.fillStyle = 'black'
u.ctx.fillText(textr, u.bbox.left + u.bbox.width - 10, u.bbox.top + 10)
// u.ctx.fillText(text, u.bbox.left + u.bbox.width - 10, u.bbox.top + u.bbox.height - 10) // Recipe for bottom right
if (!thresholds) { if (!thresholds) {
u.ctx.restore() u.ctx.restore()

View File

@ -13,6 +13,8 @@
clusterId: "{{ .Infos.clusterId }}" clusterId: "{{ .Infos.clusterId }}"
}; };
const clusterCockpitConfig = {{ .Config }}; const clusterCockpitConfig = {{ .Config }};
const authlevel = {{ .User.GetAuthLevel }};
const roles = {{ .Roles }};
</script> </script>
<script src='/build/job.js'></script> <script src='/build/job.js'></script>
{{end}} {{end}}