mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-08-08 14:27:14 +02:00
Merge branch 'dev' into ai-review
This commit is contained in:
+5
-4
@@ -28,9 +28,8 @@ type NodeStates {
|
|||||||
|
|
||||||
type NodeStatesTimed {
|
type NodeStatesTimed {
|
||||||
state: String!
|
state: String!
|
||||||
type: String!
|
counts: [Int!]!
|
||||||
count: Int!
|
times: [Int!]!
|
||||||
time: Int!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Job {
|
type Job {
|
||||||
@@ -263,6 +262,7 @@ enum SortByAggregate {
|
|||||||
|
|
||||||
type NodeMetrics {
|
type NodeMetrics {
|
||||||
host: String!
|
host: String!
|
||||||
|
state: String!
|
||||||
subCluster: String!
|
subCluster: String!
|
||||||
metrics: [JobMetricWithName!]!
|
metrics: [JobMetricWithName!]!
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ type Query {
|
|||||||
node(id: ID!): Node
|
node(id: ID!): Node
|
||||||
nodes(filter: [NodeFilter!], order: OrderByInput): NodeStateResultList!
|
nodes(filter: [NodeFilter!], order: OrderByInput): NodeStateResultList!
|
||||||
nodeStates(filter: [NodeFilter!]): [NodeStates!]!
|
nodeStates(filter: [NodeFilter!]): [NodeStates!]!
|
||||||
nodeStatesTimed(filter: [NodeFilter!]): [NodeStatesTimed!]!
|
nodeStatesTimed(filter: [NodeFilter!], type: String!): [NodeStatesTimed!]!
|
||||||
|
|
||||||
job(id: ID!): Job
|
job(id: ID!): Job
|
||||||
jobMetrics(
|
jobMetrics(
|
||||||
@@ -376,6 +376,7 @@ type Query {
|
|||||||
nodeMetricsList(
|
nodeMetricsList(
|
||||||
cluster: String!
|
cluster: String!
|
||||||
subCluster: String!
|
subCluster: String!
|
||||||
|
stateFilter: String!
|
||||||
nodeFilter: String!
|
nodeFilter: String!
|
||||||
scopes: [MetricScope!]
|
scopes: [MetricScope!]
|
||||||
metrics: [String!]
|
metrics: [String!]
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ type ResolverRoot interface {
|
|||||||
SubCluster() SubClusterResolver
|
SubCluster() SubClusterResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectiveRoot struct {
|
type DirectiveRoot struct{}
|
||||||
}
|
|
||||||
|
|
||||||
type ComplexityRoot struct {
|
type ComplexityRoot struct {
|
||||||
Accelerator struct {
|
Accelerator struct {
|
||||||
@@ -288,6 +287,7 @@ type ComplexityRoot struct {
|
|||||||
NodeMetrics struct {
|
NodeMetrics struct {
|
||||||
Host func(childComplexity int) int
|
Host func(childComplexity int) int
|
||||||
Metrics func(childComplexity int) int
|
Metrics func(childComplexity int) int
|
||||||
|
State func(childComplexity int) int
|
||||||
SubCluster func(childComplexity int) int
|
SubCluster func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,10 +302,9 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NodeStatesTimed struct {
|
NodeStatesTimed struct {
|
||||||
Count func(childComplexity int) int
|
Counts func(childComplexity int) int
|
||||||
State func(childComplexity int) int
|
State func(childComplexity int) int
|
||||||
Time func(childComplexity int) int
|
Times func(childComplexity int) int
|
||||||
Type func(childComplexity int) int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NodesResultList struct {
|
NodesResultList struct {
|
||||||
@@ -330,9 +329,9 @@ type ComplexityRoot struct {
|
|||||||
JobsStatistics func(childComplexity int, filter []*model.JobFilter, metrics []string, page *model.PageRequest, sortBy *model.SortByAggregate, groupBy *model.Aggregate, numDurationBins *string, numMetricBins *int) int
|
JobsStatistics func(childComplexity int, filter []*model.JobFilter, metrics []string, page *model.PageRequest, sortBy *model.SortByAggregate, groupBy *model.Aggregate, numDurationBins *string, numMetricBins *int) int
|
||||||
Node func(childComplexity int, id string) int
|
Node func(childComplexity int, id string) int
|
||||||
NodeMetrics func(childComplexity int, cluster string, nodes []string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time) int
|
NodeMetrics func(childComplexity int, cluster string, nodes []string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time) int
|
||||||
NodeMetricsList func(childComplexity int, cluster string, subCluster string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) int
|
NodeMetricsList func(childComplexity int, cluster string, subCluster string, stateFilter string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) int
|
||||||
NodeStates func(childComplexity int, filter []*model.NodeFilter) int
|
NodeStates func(childComplexity int, filter []*model.NodeFilter) int
|
||||||
NodeStatesTimed func(childComplexity int, filter []*model.NodeFilter) int
|
NodeStatesTimed func(childComplexity int, filter []*model.NodeFilter, typeArg string) int
|
||||||
Nodes func(childComplexity int, filter []*model.NodeFilter, order *model.OrderByInput) int
|
Nodes func(childComplexity int, filter []*model.NodeFilter, order *model.OrderByInput) int
|
||||||
RooflineHeatmap func(childComplexity int, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) int
|
RooflineHeatmap func(childComplexity int, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) int
|
||||||
ScopedJobStats func(childComplexity int, id string, metrics []string, scopes []schema.MetricScope) int
|
ScopedJobStats func(childComplexity int, id string, metrics []string, scopes []schema.MetricScope) int
|
||||||
@@ -473,7 +472,7 @@ type QueryResolver interface {
|
|||||||
Node(ctx context.Context, id string) (*schema.Node, error)
|
Node(ctx context.Context, id string) (*schema.Node, error)
|
||||||
Nodes(ctx context.Context, filter []*model.NodeFilter, order *model.OrderByInput) (*model.NodeStateResultList, error)
|
Nodes(ctx context.Context, filter []*model.NodeFilter, order *model.OrderByInput) (*model.NodeStateResultList, error)
|
||||||
NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error)
|
NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error)
|
||||||
NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStatesTimed, error)
|
NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter, typeArg string) ([]*model.NodeStatesTimed, error)
|
||||||
Job(ctx context.Context, id string) (*schema.Job, error)
|
Job(ctx context.Context, id string) (*schema.Job, error)
|
||||||
JobMetrics(ctx context.Context, id string, metrics []string, scopes []schema.MetricScope, resolution *int) ([]*model.JobMetricWithName, error)
|
JobMetrics(ctx context.Context, id string, metrics []string, scopes []schema.MetricScope, resolution *int) ([]*model.JobMetricWithName, error)
|
||||||
JobStats(ctx context.Context, id string, metrics []string) ([]*model.NamedStats, error)
|
JobStats(ctx context.Context, id string, metrics []string) ([]*model.NamedStats, error)
|
||||||
@@ -484,7 +483,7 @@ type QueryResolver interface {
|
|||||||
JobsFootprints(ctx context.Context, filter []*model.JobFilter, metrics []string) (*model.Footprints, error)
|
JobsFootprints(ctx context.Context, filter []*model.JobFilter, metrics []string) (*model.Footprints, error)
|
||||||
RooflineHeatmap(ctx context.Context, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) ([][]float64, error)
|
RooflineHeatmap(ctx context.Context, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) ([][]float64, error)
|
||||||
NodeMetrics(ctx context.Context, cluster string, nodes []string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error)
|
NodeMetrics(ctx context.Context, cluster string, nodes []string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error)
|
||||||
NodeMetricsList(ctx context.Context, cluster string, subCluster string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) (*model.NodesResultList, error)
|
NodeMetricsList(ctx context.Context, cluster string, subCluster string, stateFilter string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) (*model.NodesResultList, error)
|
||||||
}
|
}
|
||||||
type SubClusterResolver interface {
|
type SubClusterResolver interface {
|
||||||
NumberOfNodes(ctx context.Context, obj *schema.SubCluster) (int, error)
|
NumberOfNodes(ctx context.Context, obj *schema.SubCluster) (int, error)
|
||||||
@@ -1455,12 +1454,21 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.NodeMetrics.Host(childComplexity), true
|
return e.complexity.NodeMetrics.Host(childComplexity), true
|
||||||
|
|
||||||
case "NodeMetrics.metrics":
|
case "NodeMetrics.metrics":
|
||||||
if e.complexity.NodeMetrics.Metrics == nil {
|
if e.complexity.NodeMetrics.Metrics == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.NodeMetrics.Metrics(childComplexity), true
|
return e.complexity.NodeMetrics.Metrics(childComplexity), true
|
||||||
|
|
||||||
|
case "NodeMetrics.state":
|
||||||
|
if e.complexity.NodeMetrics.State == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.NodeMetrics.State(childComplexity), true
|
||||||
|
|
||||||
case "NodeMetrics.subCluster":
|
case "NodeMetrics.subCluster":
|
||||||
if e.complexity.NodeMetrics.SubCluster == nil {
|
if e.complexity.NodeMetrics.SubCluster == nil {
|
||||||
break
|
break
|
||||||
@@ -1494,30 +1502,26 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
|
|
||||||
return e.complexity.NodeStates.State(childComplexity), true
|
return e.complexity.NodeStates.State(childComplexity), true
|
||||||
|
|
||||||
case "NodeStatesTimed.count":
|
case "NodeStatesTimed.counts":
|
||||||
if e.complexity.NodeStatesTimed.Count == nil {
|
if e.complexity.NodeStatesTimed.Counts == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.NodeStatesTimed.Count(childComplexity), true
|
return e.complexity.NodeStatesTimed.Counts(childComplexity), true
|
||||||
|
|
||||||
case "NodeStatesTimed.state":
|
case "NodeStatesTimed.state":
|
||||||
if e.complexity.NodeStatesTimed.State == nil {
|
if e.complexity.NodeStatesTimed.State == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.NodeStatesTimed.State(childComplexity), true
|
return e.complexity.NodeStatesTimed.State(childComplexity), true
|
||||||
case "NodeStatesTimed.time":
|
|
||||||
if e.complexity.NodeStatesTimed.Time == nil {
|
case "NodeStatesTimed.times":
|
||||||
|
if e.complexity.NodeStatesTimed.Times == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.NodeStatesTimed.Time(childComplexity), true
|
return e.complexity.NodeStatesTimed.Times(childComplexity), true
|
||||||
case "NodeStatesTimed.type":
|
|
||||||
if e.complexity.NodeStatesTimed.Type == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.complexity.NodeStatesTimed.Type(childComplexity), true
|
|
||||||
|
|
||||||
case "NodesResultList.count":
|
case "NodesResultList.count":
|
||||||
if e.complexity.NodesResultList.Count == nil {
|
if e.complexity.NodesResultList.Count == nil {
|
||||||
@@ -1688,7 +1692,8 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Query.NodeMetricsList(childComplexity, args["cluster"].(string), args["subCluster"].(string), args["nodeFilter"].(string), args["scopes"].([]schema.MetricScope), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time), args["page"].(*model.PageRequest), args["resolution"].(*int)), true
|
return e.complexity.Query.NodeMetricsList(childComplexity, args["cluster"].(string), args["subCluster"].(string), args["stateFilter"].(string), args["nodeFilter"].(string), args["scopes"].([]schema.MetricScope), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time), args["page"].(*model.PageRequest), args["resolution"].(*int)), true
|
||||||
|
|
||||||
case "Query.nodeStates":
|
case "Query.nodeStates":
|
||||||
if e.complexity.Query.NodeStates == nil {
|
if e.complexity.Query.NodeStates == nil {
|
||||||
break
|
break
|
||||||
@@ -1710,7 +1715,8 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Query.NodeStatesTimed(childComplexity, args["filter"].([]*model.NodeFilter)), true
|
return e.complexity.Query.NodeStatesTimed(childComplexity, args["filter"].([]*model.NodeFilter), args["type"].(string)), true
|
||||||
|
|
||||||
case "Query.nodes":
|
case "Query.nodes":
|
||||||
if e.complexity.Query.Nodes == nil {
|
if e.complexity.Query.Nodes == nil {
|
||||||
break
|
break
|
||||||
@@ -2248,9 +2254,8 @@ type NodeStates {
|
|||||||
|
|
||||||
type NodeStatesTimed {
|
type NodeStatesTimed {
|
||||||
state: String!
|
state: String!
|
||||||
type: String!
|
counts: [Int!]!
|
||||||
count: Int!
|
times: [Int!]!
|
||||||
time: Int!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Job {
|
type Job {
|
||||||
@@ -2483,6 +2488,7 @@ enum SortByAggregate {
|
|||||||
|
|
||||||
type NodeMetrics {
|
type NodeMetrics {
|
||||||
host: String!
|
host: String!
|
||||||
|
state: String!
|
||||||
subCluster: String!
|
subCluster: String!
|
||||||
metrics: [JobMetricWithName!]!
|
metrics: [JobMetricWithName!]!
|
||||||
}
|
}
|
||||||
@@ -2537,7 +2543,7 @@ type Query {
|
|||||||
node(id: ID!): Node
|
node(id: ID!): Node
|
||||||
nodes(filter: [NodeFilter!], order: OrderByInput): NodeStateResultList!
|
nodes(filter: [NodeFilter!], order: OrderByInput): NodeStateResultList!
|
||||||
nodeStates(filter: [NodeFilter!]): [NodeStates!]!
|
nodeStates(filter: [NodeFilter!]): [NodeStates!]!
|
||||||
nodeStatesTimed(filter: [NodeFilter!]): [NodeStatesTimed!]!
|
nodeStatesTimed(filter: [NodeFilter!], type: String!): [NodeStatesTimed!]!
|
||||||
|
|
||||||
job(id: ID!): Job
|
job(id: ID!): Job
|
||||||
jobMetrics(
|
jobMetrics(
|
||||||
@@ -2596,6 +2602,7 @@ type Query {
|
|||||||
nodeMetricsList(
|
nodeMetricsList(
|
||||||
cluster: String!
|
cluster: String!
|
||||||
subCluster: String!
|
subCluster: String!
|
||||||
|
stateFilter: String!
|
||||||
nodeFilter: String!
|
nodeFilter: String!
|
||||||
scopes: [MetricScope!]
|
scopes: [MetricScope!]
|
||||||
metrics: [String!]
|
metrics: [String!]
|
||||||
@@ -3040,41 +3047,46 @@ func (ec *executionContext) field_Query_nodeMetricsList_args(ctx context.Context
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["subCluster"] = arg1
|
args["subCluster"] = arg1
|
||||||
arg2, err := graphql.ProcessArgField(ctx, rawArgs, "nodeFilter", ec.unmarshalNString2string)
|
arg2, err := graphql.ProcessArgField(ctx, rawArgs, "stateFilter", ec.unmarshalNString2string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["nodeFilter"] = arg2
|
args["stateFilter"] = arg2
|
||||||
arg3, err := graphql.ProcessArgField(ctx, rawArgs, "scopes", ec.unmarshalOMetricScope2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐMetricScopeᚄ)
|
arg3, err := graphql.ProcessArgField(ctx, rawArgs, "nodeFilter", ec.unmarshalNString2string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["scopes"] = arg3
|
args["nodeFilter"] = arg3
|
||||||
arg4, err := graphql.ProcessArgField(ctx, rawArgs, "metrics", ec.unmarshalOString2ᚕstringᚄ)
|
arg4, err := graphql.ProcessArgField(ctx, rawArgs, "scopes", ec.unmarshalOMetricScope2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐMetricScopeᚄ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["metrics"] = arg4
|
args["scopes"] = arg4
|
||||||
arg5, err := graphql.ProcessArgField(ctx, rawArgs, "from", ec.unmarshalNTime2timeᚐTime)
|
arg5, err := graphql.ProcessArgField(ctx, rawArgs, "metrics", ec.unmarshalOString2ᚕstringᚄ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["from"] = arg5
|
args["metrics"] = arg5
|
||||||
arg6, err := graphql.ProcessArgField(ctx, rawArgs, "to", ec.unmarshalNTime2timeᚐTime)
|
arg6, err := graphql.ProcessArgField(ctx, rawArgs, "from", ec.unmarshalNTime2timeᚐTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["to"] = arg6
|
args["from"] = arg6
|
||||||
arg7, err := graphql.ProcessArgField(ctx, rawArgs, "page", ec.unmarshalOPageRequest2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐPageRequest)
|
arg7, err := graphql.ProcessArgField(ctx, rawArgs, "to", ec.unmarshalNTime2timeᚐTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["page"] = arg7
|
args["to"] = arg7
|
||||||
arg8, err := graphql.ProcessArgField(ctx, rawArgs, "resolution", ec.unmarshalOInt2ᚖint)
|
arg8, err := graphql.ProcessArgField(ctx, rawArgs, "page", ec.unmarshalOPageRequest2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐPageRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["resolution"] = arg8
|
args["page"] = arg8
|
||||||
|
arg9, err := graphql.ProcessArgField(ctx, rawArgs, "resolution", ec.unmarshalOInt2ᚖint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args["resolution"] = arg9
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3122,6 +3134,11 @@ func (ec *executionContext) field_Query_nodeStatesTimed_args(ctx context.Context
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
args["filter"] = arg0
|
args["filter"] = arg0
|
||||||
|
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "type", ec.unmarshalNString2string)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args["type"] = arg1
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7976,6 +7993,50 @@ func (ec *executionContext) fieldContext_NodeMetrics_host(_ context.Context, fie
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _NodeMetrics_state(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_NodeMetrics_state(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.State, 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_NodeMetrics_state(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "NodeMetrics",
|
||||||
|
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) _NodeMetrics_subCluster(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) {
|
func (ec *executionContext) _NodeMetrics_subCluster(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -8211,52 +8272,38 @@ func (ec *executionContext) fieldContext_NodeStatesTimed_state(_ context.Context
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _NodeStatesTimed_type(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) {
|
func (ec *executionContext) _NodeStatesTimed_counts(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
fc, err := ec.fieldContext_NodeStatesTimed_counts(ctx, field)
|
||||||
ctx,
|
if err != nil {
|
||||||
ec.OperationContext,
|
return graphql.Null
|
||||||
field,
|
|
||||||
ec.fieldContext_NodeStatesTimed_type,
|
|
||||||
func(ctx context.Context) (any, error) {
|
|
||||||
return obj.Type, nil
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
ec.marshalNString2string,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_NodeStatesTimed_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
|
||||||
fc = &graphql.FieldContext{
|
|
||||||
Object: "NodeStatesTimed",
|
|
||||||
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
|
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.Counts, 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.marshalNInt2ᚕintᚄ(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _NodeStatesTimed_count(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) {
|
func (ec *executionContext) fieldContext_NodeStatesTimed_counts(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
return graphql.ResolveField(
|
|
||||||
ctx,
|
|
||||||
ec.OperationContext,
|
|
||||||
field,
|
|
||||||
ec.fieldContext_NodeStatesTimed_count,
|
|
||||||
func(ctx context.Context) (any, error) {
|
|
||||||
return obj.Count, nil
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
ec.marshalNInt2int,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_NodeStatesTimed_count(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
|
||||||
fc = &graphql.FieldContext{
|
fc = &graphql.FieldContext{
|
||||||
Object: "NodeStatesTimed",
|
Object: "NodeStatesTimed",
|
||||||
Field: field,
|
Field: field,
|
||||||
@@ -8269,23 +8316,38 @@ func (ec *executionContext) fieldContext_NodeStatesTimed_count(_ context.Context
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _NodeStatesTimed_time(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) {
|
func (ec *executionContext) _NodeStatesTimed_times(ctx context.Context, field graphql.CollectedField, obj *model.NodeStatesTimed) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
fc, err := ec.fieldContext_NodeStatesTimed_times(ctx, field)
|
||||||
ctx,
|
if err != nil {
|
||||||
ec.OperationContext,
|
return graphql.Null
|
||||||
field,
|
}
|
||||||
ec.fieldContext_NodeStatesTimed_time,
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
func(ctx context.Context) (any, error) {
|
defer func() {
|
||||||
return obj.Time, nil
|
if r := recover(); r != nil {
|
||||||
},
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
nil,
|
ret = graphql.Null
|
||||||
ec.marshalNInt2int,
|
}
|
||||||
true,
|
}()
|
||||||
true,
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
)
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Times, 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.marshalNInt2ᚕintᚄ(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_NodeStatesTimed_time(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_NodeStatesTimed_times(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
fc = &graphql.FieldContext{
|
fc = &graphql.FieldContext{
|
||||||
Object: "NodeStatesTimed",
|
Object: "NodeStatesTimed",
|
||||||
Field: field,
|
Field: field,
|
||||||
@@ -8324,6 +8386,8 @@ func (ec *executionContext) fieldContext_NodesResultList_items(_ context.Context
|
|||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "host":
|
case "host":
|
||||||
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
||||||
|
case "state":
|
||||||
|
return ec.fieldContext_NodeMetrics_state(ctx, field)
|
||||||
case "subCluster":
|
case "subCluster":
|
||||||
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
||||||
case "metrics":
|
case "metrics":
|
||||||
@@ -8853,20 +8917,34 @@ func (ec *executionContext) fieldContext_Query_nodeStates(ctx context.Context, f
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Query_nodeStatesTimed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Query_nodeStatesTimed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
fc, err := ec.fieldContext_Query_nodeStatesTimed(ctx, field)
|
||||||
ctx,
|
if err != nil {
|
||||||
ec.OperationContext,
|
return graphql.Null
|
||||||
field,
|
}
|
||||||
ec.fieldContext_Query_nodeStatesTimed,
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
func(ctx context.Context) (any, error) {
|
defer func() {
|
||||||
fc := graphql.GetFieldContext(ctx)
|
if r := recover(); r != nil {
|
||||||
return ec.resolvers.Query().NodeStatesTimed(ctx, fc.Args["filter"].([]*model.NodeFilter))
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
},
|
ret = graphql.Null
|
||||||
nil,
|
}
|
||||||
ec.marshalNNodeStatesTimed2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNodeStatesTimedᚄ,
|
}()
|
||||||
true,
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
true,
|
ctx = rctx // use context from middleware stack in children
|
||||||
)
|
return ec.resolvers.Query().NodeStatesTimed(rctx, fc.Args["filter"].([]*model.NodeFilter), fc.Args["type"].(string))
|
||||||
|
})
|
||||||
|
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.NodeStatesTimed)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNNodeStatesTimed2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNodeStatesTimedᚄ(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_Query_nodeStatesTimed(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_Query_nodeStatesTimed(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
@@ -8879,12 +8957,10 @@ func (ec *executionContext) fieldContext_Query_nodeStatesTimed(ctx context.Conte
|
|||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "state":
|
case "state":
|
||||||
return ec.fieldContext_NodeStatesTimed_state(ctx, field)
|
return ec.fieldContext_NodeStatesTimed_state(ctx, field)
|
||||||
case "type":
|
case "counts":
|
||||||
return ec.fieldContext_NodeStatesTimed_type(ctx, field)
|
return ec.fieldContext_NodeStatesTimed_counts(ctx, field)
|
||||||
case "count":
|
case "times":
|
||||||
return ec.fieldContext_NodeStatesTimed_count(ctx, field)
|
return ec.fieldContext_NodeStatesTimed_times(ctx, field)
|
||||||
case "time":
|
|
||||||
return ec.fieldContext_NodeStatesTimed_time(ctx, field)
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type NodeStatesTimed", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type NodeStatesTimed", field.Name)
|
||||||
},
|
},
|
||||||
@@ -9453,6 +9529,8 @@ func (ec *executionContext) fieldContext_Query_nodeMetrics(ctx context.Context,
|
|||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "host":
|
case "host":
|
||||||
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
||||||
|
case "state":
|
||||||
|
return ec.fieldContext_NodeMetrics_state(ctx, field)
|
||||||
case "subCluster":
|
case "subCluster":
|
||||||
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
||||||
case "metrics":
|
case "metrics":
|
||||||
@@ -9476,20 +9554,34 @@ func (ec *executionContext) fieldContext_Query_nodeMetrics(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Query_nodeMetricsList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Query_nodeMetricsList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
fc, err := ec.fieldContext_Query_nodeMetricsList(ctx, field)
|
||||||
ctx,
|
if err != nil {
|
||||||
ec.OperationContext,
|
return graphql.Null
|
||||||
field,
|
}
|
||||||
ec.fieldContext_Query_nodeMetricsList,
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
func(ctx context.Context) (any, error) {
|
defer func() {
|
||||||
fc := graphql.GetFieldContext(ctx)
|
if r := recover(); r != nil {
|
||||||
return ec.resolvers.Query().NodeMetricsList(ctx, fc.Args["cluster"].(string), fc.Args["subCluster"].(string), fc.Args["nodeFilter"].(string), fc.Args["scopes"].([]schema.MetricScope), fc.Args["metrics"].([]string), fc.Args["from"].(time.Time), fc.Args["to"].(time.Time), fc.Args["page"].(*model.PageRequest), fc.Args["resolution"].(*int))
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
},
|
ret = graphql.Null
|
||||||
nil,
|
}
|
||||||
ec.marshalNNodesResultList2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNodesResultList,
|
}()
|
||||||
true,
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
true,
|
ctx = rctx // use context from middleware stack in children
|
||||||
)
|
return ec.resolvers.Query().NodeMetricsList(rctx, fc.Args["cluster"].(string), fc.Args["subCluster"].(string), fc.Args["stateFilter"].(string), fc.Args["nodeFilter"].(string), fc.Args["scopes"].([]schema.MetricScope), fc.Args["metrics"].([]string), fc.Args["from"].(time.Time), fc.Args["to"].(time.Time), fc.Args["page"].(*model.PageRequest), fc.Args["resolution"].(*int))
|
||||||
|
})
|
||||||
|
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.NodesResultList)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNNodesResultList2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNodesResultList(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_Query_nodeMetricsList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_Query_nodeMetricsList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
@@ -15315,6 +15407,11 @@ func (ec *executionContext) _NodeMetrics(ctx context.Context, sel ast.SelectionS
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
out.Values[i] = ec._NodeMetrics_state(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
out.Invalids++
|
||||||
|
}
|
||||||
case "subCluster":
|
case "subCluster":
|
||||||
out.Values[i] = ec._NodeMetrics_subCluster(ctx, field, obj)
|
out.Values[i] = ec._NodeMetrics_subCluster(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
@@ -15449,18 +15546,13 @@ func (ec *executionContext) _NodeStatesTimed(ctx context.Context, sel ast.Select
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
case "type":
|
case "counts":
|
||||||
out.Values[i] = ec._NodeStatesTimed_type(ctx, field, obj)
|
out.Values[i] = ec._NodeStatesTimed_counts(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
case "count":
|
case "times":
|
||||||
out.Values[i] = ec._NodeStatesTimed_count(ctx, field, obj)
|
out.Values[i] = ec._NodeStatesTimed_times(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
|
||||||
out.Invalids++
|
|
||||||
}
|
|
||||||
case "time":
|
|
||||||
out.Values[i] = ec._NodeStatesTimed_time(ctx, field, obj)
|
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
@@ -19234,7 +19326,7 @@ func (ec *executionContext) unmarshalOAggregate2ᚖgithubᚗcomᚋClusterCockpit
|
|||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var res = new(model.Aggregate)
|
res := new(model.Aggregate)
|
||||||
err := res.UnmarshalGQL(v)
|
err := res.UnmarshalGQL(v)
|
||||||
return res, graphql.ErrorOnPath(ctx, err)
|
return res, graphql.ErrorOnPath(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -19900,7 +19992,7 @@ func (ec *executionContext) unmarshalOSortByAggregate2ᚖgithubᚗcomᚋClusterC
|
|||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var res = new(model.SortByAggregate)
|
res := new(model.SortByAggregate)
|
||||||
err := res.UnmarshalGQL(v)
|
err := res.UnmarshalGQL(v)
|
||||||
return res, graphql.ErrorOnPath(ctx, err)
|
return res, graphql.ErrorOnPath(ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ type NodeFilter struct {
|
|||||||
|
|
||||||
type NodeMetrics struct {
|
type NodeMetrics struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
|
State string `json:"state"`
|
||||||
SubCluster string `json:"subCluster"`
|
SubCluster string `json:"subCluster"`
|
||||||
Metrics []*JobMetricWithName `json:"metrics"`
|
Metrics []*JobMetricWithName `json:"metrics"`
|
||||||
}
|
}
|
||||||
@@ -197,9 +198,8 @@ type NodeStates struct {
|
|||||||
|
|
||||||
type NodeStatesTimed struct {
|
type NodeStatesTimed struct {
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
Type string `json:"type"`
|
Counts []int `json:"counts"`
|
||||||
Count int `json:"count"`
|
Times []int `json:"times"`
|
||||||
Time int `json:"time"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodesResultList struct {
|
type NodesResultList struct {
|
||||||
|
|||||||
@@ -312,7 +312,11 @@ func (r *nodeResolver) ID(ctx context.Context, obj *schema.Node) (string, error)
|
|||||||
|
|
||||||
// SchedulerState is the resolver for the schedulerState field.
|
// SchedulerState is the resolver for the schedulerState field.
|
||||||
func (r *nodeResolver) SchedulerState(ctx context.Context, obj *schema.Node) (schema.SchedulerState, error) {
|
func (r *nodeResolver) SchedulerState(ctx context.Context, obj *schema.Node) (schema.SchedulerState, error) {
|
||||||
panic(fmt.Errorf("not implemented: SchedulerState - schedulerState"))
|
if obj.NodeState != "" {
|
||||||
|
return obj.NodeState, nil
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("No SchedulerState (NodeState) on Object")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthState is the resolver for the healthState field.
|
// HealthState is the resolver for the healthState field.
|
||||||
@@ -387,13 +391,13 @@ func (r *queryResolver) Nodes(ctx context.Context, filter []*model.NodeFilter, o
|
|||||||
func (r *queryResolver) NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error) {
|
func (r *queryResolver) NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error) {
|
||||||
repo := repository.GetNodeRepository()
|
repo := repository.GetNodeRepository()
|
||||||
|
|
||||||
stateCounts, serr := repo.CountNodeStates(ctx, filter)
|
stateCounts, serr := repo.CountStates(ctx, filter, "node_state")
|
||||||
if serr != nil {
|
if serr != nil {
|
||||||
cclog.Warnf("Error while counting nodeStates: %s", serr.Error())
|
cclog.Warnf("Error while counting nodeStates: %s", serr.Error())
|
||||||
return nil, serr
|
return nil, serr
|
||||||
}
|
}
|
||||||
|
|
||||||
healthCounts, herr := repo.CountHealthStates(ctx, filter)
|
healthCounts, herr := repo.CountStates(ctx, filter, "health_state")
|
||||||
if herr != nil {
|
if herr != nil {
|
||||||
cclog.Warnf("Error while counting healthStates: %s", herr.Error())
|
cclog.Warnf("Error while counting healthStates: %s", herr.Error())
|
||||||
return nil, herr
|
return nil, herr
|
||||||
@@ -406,26 +410,28 @@ func (r *queryResolver) NodeStates(ctx context.Context, filter []*model.NodeFilt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NodeStatesTimed is the resolver for the nodeStatesTimed field.
|
// NodeStatesTimed is the resolver for the nodeStatesTimed field.
|
||||||
func (r *queryResolver) NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStatesTimed, error) {
|
func (r *queryResolver) NodeStatesTimed(ctx context.Context, filter []*model.NodeFilter, typeArg string) ([]*model.NodeStatesTimed, error) {
|
||||||
panic(fmt.Errorf("not implemented: NodeStatesTimed - NodeStatesTimed"))
|
repo := repository.GetNodeRepository()
|
||||||
// repo := repository.GetNodeRepository()
|
|
||||||
|
|
||||||
// stateCounts, serr := repo.CountNodeStates(ctx, filter)
|
if typeArg == "node" {
|
||||||
// if serr != nil {
|
stateCounts, serr := repo.CountStatesTimed(ctx, filter, "node_state")
|
||||||
// cclog.Warnf("Error while counting nodeStates: %s", serr.Error())
|
if serr != nil {
|
||||||
// return nil, serr
|
cclog.Warnf("Error while counting nodeStates in time: %s", serr.Error())
|
||||||
// }
|
return nil, serr
|
||||||
|
}
|
||||||
|
return stateCounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
// healthCounts, herr := repo.CountHealthStates(ctx, filter)
|
if typeArg == "health" {
|
||||||
// if herr != nil {
|
healthCounts, herr := repo.CountStatesTimed(ctx, filter, "health_state")
|
||||||
// cclog.Warnf("Error while counting healthStates: %s", herr.Error())
|
if herr != nil {
|
||||||
// return nil, herr
|
cclog.Warnf("Error while counting healthStates in time: %s", herr.Error())
|
||||||
// }
|
return nil, herr
|
||||||
|
}
|
||||||
|
return healthCounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
// allCounts := make([]*model.NodeStates, 0)
|
return nil, errors.New("Unknown Node State Query Type")
|
||||||
// allCounts = append(stateCounts, healthCounts...)
|
|
||||||
|
|
||||||
// return allCounts, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Job is the resolver for the job field.
|
// Job is the resolver for the job field.
|
||||||
@@ -750,10 +756,14 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, nodes [
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeRepo := repository.GetNodeRepository()
|
||||||
|
stateMap, _ := nodeRepo.MapNodes(cluster)
|
||||||
|
|
||||||
nodeMetrics := make([]*model.NodeMetrics, 0, len(data))
|
nodeMetrics := make([]*model.NodeMetrics, 0, len(data))
|
||||||
for hostname, metrics := range data {
|
for hostname, metrics := range data {
|
||||||
host := &model.NodeMetrics{
|
host := &model.NodeMetrics{
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
|
State: stateMap[hostname],
|
||||||
Metrics: make([]*model.JobMetricWithName, 0, len(metrics)*len(scopes)),
|
Metrics: make([]*model.JobMetricWithName, 0, len(metrics)*len(scopes)),
|
||||||
}
|
}
|
||||||
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
||||||
@@ -778,7 +788,7 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, nodes [
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NodeMetricsList is the resolver for the nodeMetricsList field.
|
// NodeMetricsList is the resolver for the nodeMetricsList field.
|
||||||
func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, subCluster string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) (*model.NodesResultList, error) {
|
func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, subCluster string, stateFilter string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) (*model.NodesResultList, error) {
|
||||||
if resolution == nil { // Load from Config
|
if resolution == nil { // Load from Config
|
||||||
if config.Keys.EnableResampling != nil {
|
if config.Keys.EnableResampling != nil {
|
||||||
defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions)
|
defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions)
|
||||||
@@ -800,9 +810,47 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, totalNodes, hasNextPage, err := metricDataDispatcher.LoadNodeListData(cluster, subCluster, nodeFilter, metrics, scopes, *resolution, from, to, page, ctx)
|
// Note: This Prefilter Logic Can Be Used To Completely Switch Node Source Of Truth To SQLite DB
|
||||||
|
// Adapt and extend filters/paging/sorting in QueryNodes Function to return []string array of hostnames, input array to LoadNodeListData
|
||||||
|
// LoadNodeListData, instead of building queried nodes from topoplogy anew, directly will use QueryNodes hostname array
|
||||||
|
// Caveat: "notindb" state will not be resolvable anymore by default, or needs reverse lookup by dedicated comparison to topology data after all
|
||||||
|
preFiltered := make([]string, 0)
|
||||||
|
stateMap := make(map[string]string)
|
||||||
|
if stateFilter != "all" {
|
||||||
|
nodeRepo := repository.GetNodeRepository()
|
||||||
|
stateQuery := make([]*model.NodeFilter, 0)
|
||||||
|
// Required Filters
|
||||||
|
stateQuery = append(stateQuery, &model.NodeFilter{Cluster: &model.StringInput{Eq: &cluster}})
|
||||||
|
if subCluster != "" {
|
||||||
|
stateQuery = append(stateQuery, &model.NodeFilter{Subcluster: &model.StringInput{Eq: &subCluster}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if stateFilter == "notindb" {
|
||||||
|
// Backward Filtering: Add Keyword, No Additional FIlters: Returns All Nodes For Cluster (and SubCluster)
|
||||||
|
preFiltered = append(preFiltered, "exclude")
|
||||||
|
} else {
|
||||||
|
// Workaround: If no nodes match, we need at least one element for trigger in LoadNodeListData
|
||||||
|
preFiltered = append(preFiltered, stateFilter)
|
||||||
|
// Forward Filtering: Match Only selected stateFilter
|
||||||
|
var queryState schema.SchedulerState = schema.SchedulerState(stateFilter)
|
||||||
|
stateQuery = append(stateQuery, &model.NodeFilter{SchedulerState: &queryState})
|
||||||
|
}
|
||||||
|
|
||||||
|
stateNodes, serr := nodeRepo.QueryNodes(ctx, stateQuery, &model.OrderByInput{}) // Order not Used
|
||||||
|
if serr != nil {
|
||||||
|
cclog.Warn("error while loading node database data (Resolver.NodeMetricsList)")
|
||||||
|
return nil, serr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range stateNodes {
|
||||||
|
preFiltered = append(preFiltered, node.Hostname)
|
||||||
|
stateMap[node.Hostname] = string(node.NodeState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, totalNodes, hasNextPage, err := metricDataDispatcher.LoadNodeListData(cluster, subCluster, nodeFilter, preFiltered, metrics, scopes, *resolution, from, to, page, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Warn("error while loading node data")
|
cclog.Warn("error while loading node data (Resolver.NodeMetricsList")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,6 +858,7 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub
|
|||||||
for hostname, metrics := range data {
|
for hostname, metrics := range data {
|
||||||
host := &model.NodeMetrics{
|
host := &model.NodeMetrics{
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
|
State: stateMap[hostname],
|
||||||
Metrics: make([]*model.JobMetricWithName, 0, len(metrics)*len(scopes)),
|
Metrics: make([]*model.JobMetricWithName, 0, len(metrics)*len(scopes)),
|
||||||
}
|
}
|
||||||
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ const configSchema = `{
|
|||||||
},
|
},
|
||||||
"nats": {
|
"nats": {
|
||||||
"description": "Configuration for accepting published data through NATS.",
|
"description": "Configuration for accepting published data through NATS.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
@@ -71,6 +73,8 @@ const configSchema = `{
|
|||||||
},
|
},
|
||||||
"subscriptions": {
|
"subscriptions": {
|
||||||
"description": "Array of various subscriptions. Allows to subscibe to different subjects and publishers.",
|
"description": "Array of various subscriptions. Allows to subscibe to different subjects and publishers.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"subscribe-to": {
|
"subscribe-to": {
|
||||||
@@ -86,4 +90,6 @@ const configSchema = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ func LoadNodeData(
|
|||||||
|
|
||||||
func LoadNodeListData(
|
func LoadNodeListData(
|
||||||
cluster, subCluster, nodeFilter string,
|
cluster, subCluster, nodeFilter string,
|
||||||
|
preFiltered []string,
|
||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int,
|
resolution int,
|
||||||
@@ -351,7 +352,7 @@ func LoadNodeListData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, totalNodes, hasNextPage, err := repo.LoadNodeListData(cluster, subCluster, nodeFilter, metrics, scopes, resolution, from, to, page, ctx)
|
data, totalNodes, hasNextPage, err := repo.LoadNodeListData(cluster, subCluster, nodeFilter, preFiltered, metrics, scopes, resolution, from, to, page, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(data) != 0 {
|
if len(data) != 0 {
|
||||||
cclog.Warnf("partial error: %s", err.Error())
|
cclog.Warnf("partial error: %s", err.Error())
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -678,6 +679,7 @@ func (ccms *CCMetricStoreInternal) LoadNodeData(
|
|||||||
// Used for Systems-View Node-List
|
// Used for Systems-View Node-List
|
||||||
func (ccms *CCMetricStoreInternal) LoadNodeListData(
|
func (ccms *CCMetricStoreInternal) LoadNodeListData(
|
||||||
cluster, subCluster, nodeFilter string,
|
cluster, subCluster, nodeFilter string,
|
||||||
|
preFiltered []string,
|
||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int,
|
resolution int,
|
||||||
@@ -701,18 +703,37 @@ func (ccms *CCMetricStoreInternal) LoadNodeListData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Filter nodes
|
// 2.1) Filter nodes by name
|
||||||
if nodeFilter != "" {
|
if nodeFilter != "" {
|
||||||
filteredNodes := []string{}
|
filteredNodesByName := []string{}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if strings.Contains(node, nodeFilter) {
|
if strings.Contains(node, nodeFilter) {
|
||||||
filteredNodes = append(filteredNodes, node)
|
filteredNodesByName = append(filteredNodesByName, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes = filteredNodes
|
nodes = filteredNodesByName
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.1) Count total nodes && Sort nodes -> Sorting invalidated after ccms return ...
|
// 2.2) Filter nodes by state using prefiltered match array
|
||||||
|
if len(preFiltered) > 0 {
|
||||||
|
filteredNodesByState := []string{}
|
||||||
|
if preFiltered[0] == "exclude" { // Backwards: PreFiltered contains all Nodes in DB > Return Missing Nodes
|
||||||
|
for _, node := range nodes {
|
||||||
|
if !slices.Contains(preFiltered, node) {
|
||||||
|
filteredNodesByState = append(filteredNodesByState, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Forwards: Prefiltered contains specific nodeState > Return Matches
|
||||||
|
for _, node := range nodes {
|
||||||
|
if slices.Contains(preFiltered, node) {
|
||||||
|
filteredNodesByState = append(filteredNodesByState, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes = filteredNodesByState
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.3) Count total nodes && Sort nodes -> Sorting invalidated after return ...
|
||||||
totalNodes = len(nodes)
|
totalNodes = len(nodes)
|
||||||
sort.Strings(nodes)
|
sort.Strings(nodes)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -800,6 +801,7 @@ func (ccms *CCMetricStore) LoadNodeData(
|
|||||||
// Used for Systems-View Node-List
|
// Used for Systems-View Node-List
|
||||||
func (ccms *CCMetricStore) LoadNodeListData(
|
func (ccms *CCMetricStore) LoadNodeListData(
|
||||||
cluster, subCluster, nodeFilter string,
|
cluster, subCluster, nodeFilter string,
|
||||||
|
preFiltered []string,
|
||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int,
|
resolution int,
|
||||||
@@ -824,18 +826,37 @@ func (ccms *CCMetricStore) LoadNodeListData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Filter nodes
|
// 2.1) Filter nodes by name
|
||||||
if nodeFilter != "" {
|
if nodeFilter != "" {
|
||||||
filteredNodes := []string{}
|
filteredNodesByName := []string{}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if strings.Contains(node, nodeFilter) {
|
if strings.Contains(node, nodeFilter) {
|
||||||
filteredNodes = append(filteredNodes, node)
|
filteredNodesByName = append(filteredNodesByName, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes = filteredNodes
|
nodes = filteredNodesByName
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.1) Count total nodes && Sort nodes -> Sorting invalidated after ccms return ...
|
// 2.2) Filter nodes by state using prefiltered match array
|
||||||
|
if len(preFiltered) > 0 {
|
||||||
|
filteredNodesByState := []string{}
|
||||||
|
if preFiltered[0] == "exclude" { // Backwards: PreFiltered contains all Nodes in DB > Return Missing Nodes
|
||||||
|
for _, node := range nodes {
|
||||||
|
if !slices.Contains(preFiltered, node) {
|
||||||
|
filteredNodesByState = append(filteredNodesByState, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Forwards: Prefiltered contains specific nodeState > Return Matches
|
||||||
|
for _, node := range nodes {
|
||||||
|
if slices.Contains(preFiltered, node) {
|
||||||
|
filteredNodesByState = append(filteredNodesByState, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes = filteredNodesByState
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.3) Count total nodes && Sort nodes -> Sorting invalidated after return ...
|
||||||
totalNodes = len(nodes)
|
totalNodes = len(nodes)
|
||||||
sort.Strings(nodes)
|
sort.Strings(nodes)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type MetricDataRepository interface {
|
|||||||
LoadNodeData(cluster string, metrics, nodes []string, scopes []schema.MetricScope, from, to time.Time, ctx context.Context) (map[string]map[string][]*schema.JobMetric, error)
|
LoadNodeData(cluster string, metrics, nodes []string, scopes []schema.MetricScope, from, to time.Time, ctx context.Context) (map[string]map[string][]*schema.JobMetric, error)
|
||||||
|
|
||||||
// Return a map of hosts to a map of metrics to a map of scopes for multiple nodes.
|
// Return a map of hosts to a map of metrics to a map of scopes for multiple nodes.
|
||||||
LoadNodeListData(cluster, subCluster, nodeFilter string, metrics []string, scopes []schema.MetricScope, resolution int, from, to time.Time, page *model.PageRequest, ctx context.Context) (map[string]schema.JobData, int, bool, error)
|
LoadNodeListData(cluster, subCluster, nodeFilter string, preFiltered []string, metrics []string, scopes []schema.MetricScope, resolution int, from, to time.Time, page *model.PageRequest, ctx context.Context) (map[string]schema.JobData, int, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var metricDataRepos map[string]MetricDataRepository = map[string]MetricDataRepository{}
|
var metricDataRepos map[string]MetricDataRepository = map[string]MetricDataRepository{}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -495,6 +496,7 @@ func (pdb *PrometheusDataRepository) LoadScopedStats(
|
|||||||
// Implemented by NHR@FAU; Used in NodeList-View
|
// Implemented by NHR@FAU; Used in NodeList-View
|
||||||
func (pdb *PrometheusDataRepository) LoadNodeListData(
|
func (pdb *PrometheusDataRepository) LoadNodeListData(
|
||||||
cluster, subCluster, nodeFilter string,
|
cluster, subCluster, nodeFilter string,
|
||||||
|
preFiltered []string,
|
||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int,
|
resolution int,
|
||||||
@@ -520,18 +522,37 @@ func (pdb *PrometheusDataRepository) LoadNodeListData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Filter nodes
|
// 2.1) Filter nodes by name
|
||||||
if nodeFilter != "" {
|
if nodeFilter != "" {
|
||||||
filteredNodes := []string{}
|
filteredNodesByName := []string{}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if strings.Contains(node, nodeFilter) {
|
if strings.Contains(node, nodeFilter) {
|
||||||
filteredNodes = append(filteredNodes, node)
|
filteredNodesByName = append(filteredNodesByName, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes = filteredNodes
|
nodes = filteredNodesByName
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.1) Count total nodes && Sort nodes -> Sorting invalidated after return ...
|
// 2.2) Filter nodes by state using prefiltered match array
|
||||||
|
if len(preFiltered) > 0 {
|
||||||
|
filteredNodesByState := []string{}
|
||||||
|
if preFiltered[0] == "exclude" { // Backwards: PreFiltered contains all Nodes in DB > Return Missing Nodes
|
||||||
|
for _, node := range nodes {
|
||||||
|
if !slices.Contains(preFiltered, node) {
|
||||||
|
filteredNodesByState = append(filteredNodesByState, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Forwards: Prefiltered contains specific nodeState > Return Matches
|
||||||
|
for _, node := range nodes {
|
||||||
|
if slices.Contains(preFiltered, node) {
|
||||||
|
filteredNodesByState = append(filteredNodesByState, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes = filteredNodesByState
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.3) Count total nodes && Sort nodes -> Sorting invalidated after return ...
|
||||||
totalNodes = len(nodes)
|
totalNodes = len(nodes)
|
||||||
sort.Strings(nodes)
|
sort.Strings(nodes)
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func (tmdr *TestMetricDataRepository) LoadNodeData(
|
|||||||
|
|
||||||
func (tmdr *TestMetricDataRepository) LoadNodeListData(
|
func (tmdr *TestMetricDataRepository) LoadNodeListData(
|
||||||
cluster, subCluster, nodeFilter string,
|
cluster, subCluster, nodeFilter string,
|
||||||
|
preFiltered []string,
|
||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int,
|
resolution int,
|
||||||
|
|||||||
+89
-39
@@ -79,6 +79,7 @@ func (r *NodeRepository) FetchMetadata(hostname string, cluster string) (map[str
|
|||||||
|
|
||||||
func (r *NodeRepository) GetNode(hostname string, cluster string, withMeta bool) (*schema.Node, error) {
|
func (r *NodeRepository) GetNode(hostname string, cluster string, withMeta bool) (*schema.Node, error) {
|
||||||
node := &schema.Node{}
|
node := &schema.Node{}
|
||||||
|
var timestamp int
|
||||||
if err := sq.Select("node.hostname", "node.cluster", "node.subcluster", "node_state.node_state",
|
if err := sq.Select("node.hostname", "node.cluster", "node.subcluster", "node_state.node_state",
|
||||||
"node_state.health_state", "MAX(node_state.time_stamp) as time").
|
"node_state.health_state", "MAX(node_state.time_stamp) as time").
|
||||||
From("node_state").
|
From("node_state").
|
||||||
@@ -87,8 +88,8 @@ func (r *NodeRepository) GetNode(hostname string, cluster string, withMeta bool)
|
|||||||
Where("node.cluster = ?", cluster).
|
Where("node.cluster = ?", cluster).
|
||||||
GroupBy("node_state.node_id").
|
GroupBy("node_state.node_id").
|
||||||
RunWith(r.DB).
|
RunWith(r.DB).
|
||||||
QueryRow().Scan(&node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, &node.HealthState); err != nil {
|
QueryRow().Scan(&node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, &node.HealthState, ×tamp); err != nil {
|
||||||
cclog.Warnf("Error while querying node '%s' from database: %v", hostname, err)
|
cclog.Warnf("Error while querying node '%s' at time '%d' from database: %v", hostname, timestamp, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ func (r *NodeRepository) GetNode(hostname string, cluster string, withMeta bool)
|
|||||||
|
|
||||||
func (r *NodeRepository) GetNodeById(id int64, withMeta bool) (*schema.Node, error) {
|
func (r *NodeRepository) GetNodeById(id int64, withMeta bool) (*schema.Node, error) {
|
||||||
node := &schema.Node{}
|
node := &schema.Node{}
|
||||||
|
var timestamp int
|
||||||
if err := sq.Select("node.hostname", "node.cluster", "node.subcluster", "node_state.node_state",
|
if err := sq.Select("node.hostname", "node.cluster", "node.subcluster", "node_state.node_state",
|
||||||
"node_state.health_state", "MAX(node_state.time_stamp) as time").
|
"node_state.health_state", "MAX(node_state.time_stamp) as time").
|
||||||
From("node_state").
|
From("node_state").
|
||||||
@@ -114,8 +116,8 @@ func (r *NodeRepository) GetNodeById(id int64, withMeta bool) (*schema.Node, err
|
|||||||
Where("node.id = ?", id).
|
Where("node.id = ?", id).
|
||||||
GroupBy("node_state.node_id").
|
GroupBy("node_state.node_id").
|
||||||
RunWith(r.DB).
|
RunWith(r.DB).
|
||||||
QueryRow().Scan(&node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, &node.HealthState); err != nil {
|
QueryRow().Scan(&node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, &node.HealthState, ×tamp); err != nil {
|
||||||
cclog.Warnf("Error while querying node ID '%d' from database: %v", id, err)
|
cclog.Warnf("Error while querying node ID '%d' at time '%d' from database: %v", id, timestamp, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,8 +240,8 @@ func (r *NodeRepository) QueryNodes(
|
|||||||
order *model.OrderByInput, // Currently unused!
|
order *model.OrderByInput, // Currently unused!
|
||||||
) ([]*schema.Node, error) {
|
) ([]*schema.Node, error) {
|
||||||
query, qerr := AccessCheck(ctx,
|
query, qerr := AccessCheck(ctx,
|
||||||
sq.Select("node.hostname", "node.cluster", "node.subcluster", "node_state.node_state",
|
sq.Select("hostname", "cluster", "subcluster", "node_state",
|
||||||
"node_state.health_state", "MAX(node_state.time_stamp) as time").
|
"health_state", "MAX(time_stamp) as time").
|
||||||
From("node").
|
From("node").
|
||||||
Join("node_state ON node_state.node_id = node.id"))
|
Join("node_state ON node_state.node_id = node.id"))
|
||||||
if qerr != nil {
|
if qerr != nil {
|
||||||
@@ -248,24 +250,31 @@ func (r *NodeRepository) QueryNodes(
|
|||||||
|
|
||||||
for _, f := range filters {
|
for _, f := range filters {
|
||||||
if f.Hostname != nil {
|
if f.Hostname != nil {
|
||||||
query = buildStringCondition("node.hostname", f.Hostname, query)
|
query = buildStringCondition("hostname", f.Hostname, query)
|
||||||
}
|
}
|
||||||
if f.Cluster != nil {
|
if f.Cluster != nil {
|
||||||
query = buildStringCondition("node.cluster", f.Cluster, query)
|
query = buildStringCondition("cluster", f.Cluster, query)
|
||||||
}
|
}
|
||||||
if f.Subcluster != nil {
|
if f.Subcluster != nil {
|
||||||
query = buildStringCondition("node.subcluster", f.Subcluster, query)
|
query = buildStringCondition("subcluster", f.Subcluster, query)
|
||||||
}
|
}
|
||||||
if f.SchedulerState != nil {
|
if f.SchedulerState != nil {
|
||||||
query = query.Where("node.node_state = ?", f.SchedulerState)
|
query = query.Where("node_state = ?", f.SchedulerState)
|
||||||
|
// Requires Additional time_stamp Filter: Else the last (past!) time_stamp with queried state will be returned
|
||||||
|
now := time.Now().Unix()
|
||||||
|
query = query.Where(sq.Gt{"time_stamp": (now - 60)})
|
||||||
}
|
}
|
||||||
if f.HealthState != nil {
|
if f.HealthState != nil {
|
||||||
query = query.Where("node.health_state = ?", f.HealthState)
|
query = query.Where("health_state = ?", f.HealthState)
|
||||||
|
// Requires Additional time_stamp Filter: Else the last (past!) time_stamp with queried state will be returned
|
||||||
|
now := time.Now().Unix()
|
||||||
|
query = query.Where(sq.Gt{"time_stamp": (now - 60)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Grouping after filters
|
// Add Grouping and ORder after filters
|
||||||
query = query.GroupBy("node_state.node_id")
|
query = query.GroupBy("node_id").
|
||||||
|
OrderBy("hostname ASC")
|
||||||
|
|
||||||
rows, err := query.RunWith(r.stmtCache).Query()
|
rows, err := query.RunWith(r.stmtCache).Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -277,11 +286,11 @@ func (r *NodeRepository) QueryNodes(
|
|||||||
nodes := make([]*schema.Node, 0, 50)
|
nodes := make([]*schema.Node, 0, 50)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
node := schema.Node{}
|
node := schema.Node{}
|
||||||
|
var timestamp int
|
||||||
if err := rows.Scan(&node.Hostname, &node.Cluster, &node.SubCluster,
|
if err := rows.Scan(&node.Hostname, &node.Cluster, &node.SubCluster,
|
||||||
&node.NodeState, &node.HealthState); err != nil {
|
&node.NodeState, &node.HealthState, ×tamp); err != nil {
|
||||||
rows.Close()
|
rows.Close()
|
||||||
cclog.Warn("Error while scanning rows (Nodes)")
|
cclog.Warnf("Error while scanning rows (QueryNodes) at time '%d'", timestamp)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nodes = append(nodes, &node)
|
nodes = append(nodes, &node)
|
||||||
@@ -308,9 +317,10 @@ func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) {
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
node := &schema.Node{}
|
node := &schema.Node{}
|
||||||
|
var timestamp int
|
||||||
if err := rows.Scan(&node.Hostname, &node.Cluster,
|
if err := rows.Scan(&node.Hostname, &node.Cluster,
|
||||||
&node.SubCluster, &node.NodeState, &node.HealthState); err != nil {
|
&node.SubCluster, &node.NodeState, &node.HealthState, ×tamp); err != nil {
|
||||||
cclog.Warn("Error while scanning node list")
|
cclog.Warnf("Error while scanning node list (ListNodes) at time '%d'", timestamp)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,8 +330,38 @@ func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) {
|
|||||||
return nodeList, nil
|
return nodeList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) {
|
func (r *NodeRepository) MapNodes(cluster string) (map[string]string, error) {
|
||||||
query, qerr := AccessCheck(ctx, sq.Select("hostname", "node_state", "MAX(time_stamp) as time").From("node"))
|
q := sq.Select("node.hostname", "node_state.node_state", "MAX(node_state.time_stamp) as time").
|
||||||
|
From("node").
|
||||||
|
Join("node_state ON node_state.node_id = node.id").
|
||||||
|
Where("node.cluster = ?", cluster).
|
||||||
|
GroupBy("node_state.node_id").
|
||||||
|
OrderBy("node.hostname ASC")
|
||||||
|
|
||||||
|
rows, err := q.RunWith(r.DB).Query()
|
||||||
|
if err != nil {
|
||||||
|
cclog.Warn("Error while querying node list")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMap := make(map[string]string)
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var hostname, nodestate string
|
||||||
|
var timestamp int
|
||||||
|
if err := rows.Scan(&hostname, &nodestate, ×tamp); err != nil {
|
||||||
|
cclog.Warnf("Error while scanning node list (MapNodes) at time '%d'", timestamp)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMap[hostname] = nodestate
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NodeRepository) CountStates(ctx context.Context, filters []*model.NodeFilter, column string) ([]*model.NodeStates, error) {
|
||||||
|
query, qerr := AccessCheck(ctx, sq.Select("hostname", column, "MAX(time_stamp) as time").From("node"))
|
||||||
if qerr != nil {
|
if qerr != nil {
|
||||||
return nil, qerr
|
return nil, qerr
|
||||||
}
|
}
|
||||||
@@ -358,16 +398,16 @@ func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.N
|
|||||||
|
|
||||||
stateMap := map[string]int{}
|
stateMap := map[string]int{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var hostname, node_state string
|
var hostname, state string
|
||||||
var timestamp int64
|
var timestamp int
|
||||||
|
|
||||||
if err := rows.Scan(&hostname, &node_state, ×tamp); err != nil {
|
if err := rows.Scan(&hostname, &state, ×tamp); err != nil {
|
||||||
rows.Close()
|
rows.Close()
|
||||||
cclog.Warn("Error while scanning rows (NodeStates)")
|
cclog.Warnf("Error while scanning rows (CountStates) at time '%d'", timestamp)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMap[node_state] += 1
|
stateMap[state] += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes := make([]*model.NodeStates, 0)
|
nodes := make([]*model.NodeStates, 0)
|
||||||
@@ -379,8 +419,8 @@ func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.N
|
|||||||
return nodes, nil
|
return nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) {
|
func (r *NodeRepository) CountStatesTimed(ctx context.Context, filters []*model.NodeFilter, column string) ([]*model.NodeStatesTimed, error) {
|
||||||
query, qerr := AccessCheck(ctx, sq.Select("hostname", "health_state", "MAX(time_stamp) as time").From("node"))
|
query, qerr := AccessCheck(ctx, sq.Select(column, "time_stamp", "count(*) as count").From("node")) // "cluster"?
|
||||||
if qerr != nil {
|
if qerr != nil {
|
||||||
return nil, qerr
|
return nil, qerr
|
||||||
}
|
}
|
||||||
@@ -388,6 +428,11 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model
|
|||||||
query = query.Join("node_state ON node_state.node_id = node.id")
|
query = query.Join("node_state ON node_state.node_id = node.id")
|
||||||
|
|
||||||
for _, f := range filters {
|
for _, f := range filters {
|
||||||
|
// Required
|
||||||
|
if f.TimeStart != nil {
|
||||||
|
query = query.Where("time_stamp > ?", f.TimeStart)
|
||||||
|
}
|
||||||
|
// Optional
|
||||||
if f.Hostname != nil {
|
if f.Hostname != nil {
|
||||||
query = buildStringCondition("hostname", f.Hostname, query)
|
query = buildStringCondition("hostname", f.Hostname, query)
|
||||||
}
|
}
|
||||||
@@ -406,7 +451,7 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add Group and Order
|
// Add Group and Order
|
||||||
query = query.GroupBy("hostname").OrderBy("hostname DESC")
|
query = query.GroupBy(column + ", time_stamp").OrderBy("time_stamp ASC")
|
||||||
|
|
||||||
rows, err := query.RunWith(r.stmtCache).Query()
|
rows, err := query.RunWith(r.stmtCache).Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -415,27 +460,32 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMap := map[string]int{}
|
rawData := make(map[string][][]int)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var hostname, health_state string
|
var state string
|
||||||
var timestamp int64
|
var timestamp, count int
|
||||||
|
|
||||||
if err := rows.Scan(&hostname, &health_state, ×tamp); err != nil {
|
if err := rows.Scan(&state, ×tamp, &count); err != nil {
|
||||||
rows.Close()
|
rows.Close()
|
||||||
cclog.Warn("Error while scanning rows (NodeStates)")
|
cclog.Warnf("Error while scanning rows (CountStatesTimed) at time '%d'", timestamp)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMap[health_state] += 1
|
if rawData[state] == nil {
|
||||||
|
rawData[state] = [][]int{make([]int, 0), make([]int, 0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes := make([]*model.NodeStates, 0)
|
rawData[state][0] = append(rawData[state][0], timestamp)
|
||||||
for state, counts := range stateMap {
|
rawData[state][1] = append(rawData[state][1], count)
|
||||||
node := model.NodeStates{State: state, Count: counts}
|
|
||||||
nodes = append(nodes, &node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes, nil
|
timedStates := make([]*model.NodeStatesTimed, 0)
|
||||||
|
for state, data := range rawData {
|
||||||
|
entry := model.NodeStatesTimed{State: state, Times: data[0], Counts: data[1]}
|
||||||
|
timedStates = append(timedStates, &entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return timedStates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccessCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) {
|
func AccessCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) {
|
||||||
|
|||||||
@@ -307,7 +307,6 @@ func (s3a *S3Archive) LoadJobData(job *schema.Job) (schema.JobData, error) {
|
|||||||
Bucket: aws.String(s3a.bucket),
|
Bucket: aws.String(s3a.bucket),
|
||||||
Key: aws.String(keyGz),
|
Key: aws.String(keyGz),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try uncompressed file
|
// Try uncompressed file
|
||||||
key := getS3Key(job, "data.json")
|
key := getS3Key(job, "data.json")
|
||||||
@@ -359,7 +358,6 @@ func (s3a *S3Archive) LoadJobStats(job *schema.Job) (schema.ScopedJobStats, erro
|
|||||||
Bucket: aws.String(s3a.bucket),
|
Bucket: aws.String(s3a.bucket),
|
||||||
Key: aws.String(keyGz),
|
Key: aws.String(keyGz),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try uncompressed file
|
// Try uncompressed file
|
||||||
key := getS3Key(job, "data.json")
|
key := getS3Key(job, "data.json")
|
||||||
@@ -445,7 +443,6 @@ func (s3a *S3Archive) StoreJobMeta(job *schema.Job) error {
|
|||||||
Key: aws.String(key),
|
Key: aws.String(key),
|
||||||
Body: bytes.NewReader(buf.Bytes()),
|
Body: bytes.NewReader(buf.Bytes()),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("S3Archive StoreJobMeta() > PutObject error: %v", err)
|
cclog.Errorf("S3Archive StoreJobMeta() > PutObject error: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
||||||
let cluster = $state(filterPresets?.cluster);
|
let cluster = $state({});
|
||||||
let rooflineMaxY = $state(0);
|
let rooflineMaxY = $state(0);
|
||||||
let maxY = $state(-1);
|
let maxY = $state(-1);
|
||||||
let colWidth1 = $state(0);
|
let colWidth1 = $state(0);
|
||||||
@@ -370,7 +370,7 @@
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{:else if $statsQuery.data}
|
{:else if $statsQuery.data}
|
||||||
<Row cols={3} class="mb-4">
|
<Row cols={3} style="margin-bottom: 2rem;">
|
||||||
<Col>
|
<Col>
|
||||||
<Table>
|
<Table>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -419,7 +419,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<Pie
|
<Pie
|
||||||
canvasId={`pie-${groupSelection.key}`}
|
canvasId={`pie-${groupSelection.key}`}
|
||||||
size={colWidth1}
|
size={colWidth1 / 1.9}
|
||||||
sliceLabel={sortSelection.label}
|
sliceLabel={sortSelection.label}
|
||||||
quantities={$topQuery.data.topList.map(
|
quantities={$topQuery.data.topList.map(
|
||||||
(t) => t[sortSelection.key],
|
(t) => t[sortSelection.key],
|
||||||
@@ -490,13 +490,21 @@
|
|||||||
{:else if $rooflineQuery.error}
|
{:else if $rooflineQuery.error}
|
||||||
<Card body color="danger">{$rooflineQuery.error.message}</Card>
|
<Card body color="danger">{$rooflineQuery.error.message}</Card>
|
||||||
{:else if $rooflineQuery.data && cluster}
|
{:else if $rooflineQuery.data && cluster}
|
||||||
|
<div class="d-flex justify-content-center align-baseline">
|
||||||
|
<h5>Job Roofline Heatmap</h5>
|
||||||
|
<Icon
|
||||||
|
style="cursor:help; margin-left:0.5rem;"
|
||||||
|
name="info-circle"
|
||||||
|
title="Most Populated Areas By Selected Jobs' Average Values"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div bind:clientWidth={colWidth2}>
|
<div bind:clientWidth={colWidth2}>
|
||||||
{#key $rooflineQuery.data}
|
{#key $rooflineQuery.data}
|
||||||
<RooflineHeatmap
|
<RooflineHeatmap
|
||||||
width={colWidth2}
|
width={colWidth2}
|
||||||
height={300}
|
height={280}
|
||||||
tiles={$rooflineQuery.data.rooflineHeatmap}
|
tiles={$rooflineQuery.data.rooflineHeatmap}
|
||||||
subCluster={cluster.subClusters.length == 1
|
subCluster={cluster.subClusters.length >= 1
|
||||||
? cluster.subClusters[0]
|
? cluster.subClusters[0]
|
||||||
: null}
|
: null}
|
||||||
maxY={rooflineMaxY}
|
maxY={rooflineMaxY}
|
||||||
|
|||||||
@@ -79,6 +79,11 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
// Load Metric-Selection for last selected cluster
|
||||||
|
metrics = selectedCluster ? ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] : ccconfig.metricConfig_jobListMetrics
|
||||||
|
});
|
||||||
|
|
||||||
/* On Mount */
|
/* On Mount */
|
||||||
// The filterPresets are handled by the Filters component,
|
// The filterPresets are handled by the Filters component,
|
||||||
// so we need to wait for it to be ready before we can start a query.
|
// so we need to wait for it to be ready before we can start a query.
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Input,
|
Input,
|
||||||
|
Button,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupText,
|
InputGroupText,
|
||||||
Icon,
|
Icon,
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
||||||
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
||||||
host
|
host
|
||||||
|
state
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name
|
name
|
||||||
@@ -97,6 +99,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
// Node State Colors
|
||||||
|
const stateColors = {
|
||||||
|
allocated: 'success',
|
||||||
|
reserved: 'info',
|
||||||
|
idle: 'primary',
|
||||||
|
mixed: 'warning',
|
||||||
|
down: 'danger',
|
||||||
|
unknown: 'dark',
|
||||||
|
notindb: 'secondary'
|
||||||
|
}
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let from = $state(presetFrom ? presetFrom : new Date(nowEpoch - (4 * 3600 * 1000)));
|
let from = $state(presetFrom ? presetFrom : new Date(nowEpoch - (4 * 3600 * 1000)));
|
||||||
@@ -123,6 +135,8 @@
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const thisNodeState = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.state ? $nodeMetricsData.data.nodeMetrics[0].state : 'notindb');
|
||||||
|
|
||||||
/* Effect */
|
/* Effect */
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
loadUnits($initialized);
|
loadUnits($initialized);
|
||||||
@@ -138,7 +152,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row cols={{ xs: 2, lg: 4 }}>
|
<Row cols={{ xs: 2, lg: 5 }}>
|
||||||
{#if $initq.error}
|
{#if $initq.error}
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
{:else if $initq.fetching}
|
{:else if $initq.fetching}
|
||||||
@@ -149,19 +163,18 @@
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
||||||
<InputGroupText>Selected Node</InputGroupText>
|
<InputGroupText>Selected Node</InputGroupText>
|
||||||
<Input style="background-color: white;"type="text" value="{hostname} [{cluster} ({$nodeMetricsData?.data ? $nodeMetricsData.data.nodeMetrics[0].subCluster : ''})]" disabled/>
|
<Input style="background-color: white;" type="text" value="{hostname} [{cluster} {$nodeMetricsData?.data ? `(${$nodeMetricsData.data.nodeMetrics[0].subCluster})` : ''}]" disabled/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
<!-- Time Col -->
|
<!-- State Col -->
|
||||||
<Col>
|
<Col>
|
||||||
<TimeSelection
|
<InputGroup>
|
||||||
presetFrom={from}
|
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||||
presetTo={to}
|
<InputGroupText>Node State</InputGroupText>
|
||||||
applyTime={(newFrom, newTo) => {
|
<Button class="flex-grow-1 text-center" color={stateColors[thisNodeState]} disabled>
|
||||||
from = newFrom;
|
{thisNodeState}
|
||||||
to = newTo;
|
</Button>
|
||||||
}}
|
</InputGroup>
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
<!-- Concurrent Col -->
|
<!-- Concurrent Col -->
|
||||||
<Col class="mt-2 mt-lg-0">
|
<Col class="mt-2 mt-lg-0">
|
||||||
@@ -184,6 +197,17 @@
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
|
<!-- Time Col -->
|
||||||
|
<Col>
|
||||||
|
<TimeSelection
|
||||||
|
presetFrom={from}
|
||||||
|
presetTo={to}
|
||||||
|
applyTime={(newFrom, newTo) => {
|
||||||
|
from = newFrom;
|
||||||
|
to = newTo;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
<!-- Refresh Col-->
|
<!-- Refresh Col-->
|
||||||
<Col class="mt-2 mt-lg-0">
|
<Col class="mt-2 mt-lg-0">
|
||||||
<Refresher
|
<Refresher
|
||||||
@@ -217,6 +241,7 @@
|
|||||||
cluster={clusters.find((c) => c.name == cluster)}
|
cluster={clusters.find((c) => c.name == cluster)}
|
||||||
subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster}
|
subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster}
|
||||||
series={item.metric.series}
|
series={item.metric.series}
|
||||||
|
enableFlip
|
||||||
forNode
|
forNode
|
||||||
/>
|
/>
|
||||||
{:else if item.disabled === true && item.metric}
|
{:else if item.disabled === true && item.metric}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@
|
|||||||
import {
|
import {
|
||||||
getContext
|
getContext
|
||||||
} from "svelte"
|
} from "svelte"
|
||||||
|
import {
|
||||||
|
init,
|
||||||
|
} from "./generic/utils.js";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
TabContent,
|
TabContent,
|
||||||
TabPane
|
TabPane,
|
||||||
|
Spinner
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
import StatusDash from "./status/StatusDash.svelte";
|
import StatusDash from "./status/StatusDash.svelte";
|
||||||
@@ -28,8 +32,8 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/*Const Init */
|
/*Const Init */
|
||||||
|
const { query: initq } = init();
|
||||||
const useCbColors = getContext("cc-config")?.plotConfiguration_colorblindMode || false
|
const useCbColors = getContext("cc-config")?.plotConfiguration_colorblindMode || false
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Loading indicator & Refresh -->
|
<!-- Loading indicator & Refresh -->
|
||||||
@@ -40,11 +44,25 @@
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Card class="overflow-auto" style="height: auto;">
|
|
||||||
|
{#if $initq.fetching}
|
||||||
|
<Row cols={1} class="text-center mt-3">
|
||||||
|
<Col>
|
||||||
|
<Spinner />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{:else if $initq.error}
|
||||||
|
<Row cols={1} class="text-center mt-3">
|
||||||
|
<Col>
|
||||||
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{:else}
|
||||||
|
<Card class="overflow-auto" style="height: auto;">
|
||||||
<TabContent>
|
<TabContent>
|
||||||
<TabPane tabId="status-dash" tab="Status" active>
|
<TabPane tabId="status-dash" tab="Status" active>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<StatusDash {presetCluster} {useCbColors} useAltColors></StatusDash>
|
<StatusDash clusters={$initq.data.clusters} {presetCluster} {useCbColors} useAltColors></StatusDash>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
@@ -60,4 +78,5 @@
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
|
|
||||||
const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : [];
|
const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : [];
|
||||||
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
|
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
|
||||||
|
const stateOptions = ['all', 'allocated', 'idle', 'reserved', 'mixed', 'down', 'unknown', 'notindb'];
|
||||||
const nowDate = new Date(Date.now());
|
const nowDate = new Date(Date.now());
|
||||||
|
|
||||||
/* Var Init */
|
/* Var Init */
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
let from = $state(presetFrom || new Date(nowDate.setHours(nowDate.getHours() - 4)));
|
let from = $state(presetFrom || new Date(nowDate.setHours(nowDate.getHours() - 4)));
|
||||||
let selectedResolution = $state(resampleConfig ? resampleDefault : 0);
|
let selectedResolution = $state(resampleConfig ? resampleDefault : 0);
|
||||||
let hostnameFilter = $state("");
|
let hostnameFilter = $state("");
|
||||||
|
let hoststateFilter = $state("all");
|
||||||
let pendingHostnameFilter = $state("");
|
let pendingHostnameFilter = $state("");
|
||||||
let isMetricsSelectionOpen = $state(false);
|
let isMetricsSelectionOpen = $state(false);
|
||||||
|
|
||||||
@@ -154,7 +156,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- ROW1: Tools-->
|
<!-- ROW1: Tools-->
|
||||||
<Row cols={{ xs: 2, lg: !displayNodeOverview ? (resampleConfig ? 5 : 4) : 4 }} class="mb-3">
|
<Row cols={{ xs: 2, lg: !displayNodeOverview ? (resampleConfig ? 6 : 5) : 5 }} class="mb-3">
|
||||||
{#if $initq.data}
|
{#if $initq.data}
|
||||||
<!-- List Metric Select Col-->
|
<!-- List Metric Select Col-->
|
||||||
{#if !displayNodeOverview}
|
{#if !displayNodeOverview}
|
||||||
@@ -191,7 +193,7 @@
|
|||||||
<Col class="mt-2 mt-lg-0">
|
<Col class="mt-2 mt-lg-0">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
||||||
<InputGroupText>Find Node(s)</InputGroupText>
|
<InputGroupText>Node(s)</InputGroupText>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter hostname ..."
|
placeholder="Filter hostname ..."
|
||||||
type="text"
|
type="text"
|
||||||
@@ -200,6 +202,18 @@
|
|||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
|
<!-- State Col-->
|
||||||
|
<Col class="mt-2 mt-lg-0">
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||||
|
<InputGroupText>State</InputGroupText>
|
||||||
|
<Input type="select" bind:value={hoststateFilter}>
|
||||||
|
{#each stateOptions as so}
|
||||||
|
<option value={so}>{so.charAt(0).toUpperCase() + so.slice(1)}</option>
|
||||||
|
{/each}
|
||||||
|
</Input>
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
<!-- Range Col-->
|
<!-- Range Col-->
|
||||||
<Col>
|
<Col>
|
||||||
<TimeSelection
|
<TimeSelection
|
||||||
@@ -252,10 +266,10 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#if displayNodeOverview}
|
{#if displayNodeOverview}
|
||||||
<!-- ROW2-1: Node Overview (Grid Included)-->
|
<!-- ROW2-1: Node Overview (Grid Included)-->
|
||||||
<NodeOverview {cluster} {ccconfig} {selectedMetric} {from} {to} {hostnameFilter}/>
|
<NodeOverview {cluster} {ccconfig} {selectedMetric} {from} {to} {hostnameFilter} {hoststateFilter}/>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- ROW2-2: Node List (Grid Included)-->
|
<!-- ROW2-2: Node List (Grid Included)-->
|
||||||
<NodeList {cluster} {subCluster} {ccconfig} {selectedMetrics} {selectedResolution} {hostnameFilter} {from} {to} {presetSystemUnits}/>
|
<NodeList {cluster} {subCluster} {ccconfig} {selectedMetrics} {selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {presetSystemUnits}/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,11 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
// Load Metric-Selection for last selected cluster
|
||||||
|
metrics = selectedCluster ? ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] : ccconfig.metricConfig_jobListMetrics
|
||||||
|
});
|
||||||
|
|
||||||
/* On Mount */
|
/* On Mount */
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
filterComponent.updateFilters();
|
filterComponent.updateFilters();
|
||||||
@@ -348,6 +353,7 @@
|
|||||||
ylabel="Number of Jobs"
|
ylabel="Number of Jobs"
|
||||||
yunit="Jobs"
|
yunit="Jobs"
|
||||||
usesBins
|
usesBins
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
function refreshIntervalChanged() {
|
function refreshIntervalChanged() {
|
||||||
if (refreshIntervalId != null) clearInterval(refreshIntervalId);
|
if (refreshIntervalId != null) clearInterval(refreshIntervalId);
|
||||||
if (refreshInterval == null) return;
|
if (refreshInterval == null) return;
|
||||||
refreshIntervalId = setInterval(() => onRefresh(), refreshInterval);
|
refreshIntervalId = setInterval(() => onRefresh(refreshInterval), refreshInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Svelte 5 onMount */
|
/* Svelte 5 onMount */
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</Input>
|
</Input>
|
||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
onclick={() => onRefresh()}
|
onclick={() => onRefresh(refreshInterval)}
|
||||||
disabled={refreshInterval != null}
|
disabled={refreshInterval != null}
|
||||||
>
|
>
|
||||||
<Icon name="arrow-clockwise" /> Refresh
|
<Icon name="arrow-clockwise" /> Refresh
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
yunit = "",
|
yunit = "",
|
||||||
title = "",
|
title = "",
|
||||||
forResources = false,
|
forResources = false,
|
||||||
plotSync,
|
plotSync = null,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
@@ -204,11 +204,7 @@
|
|||||||
live: true,
|
live: true,
|
||||||
},
|
},
|
||||||
cursor: {
|
cursor: {
|
||||||
drag: { x: true, y: true },
|
drag: { x: true, y: true }
|
||||||
sync: {
|
|
||||||
key: plotSync.key,
|
|
||||||
scales: ["x", null],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -275,9 +271,12 @@
|
|||||||
|
|
||||||
function update(u) {
|
function update(u) {
|
||||||
const { left, top } = u.cursor;
|
const { left, top } = u.cursor;
|
||||||
const width = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
const internalWidth = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
||||||
legendEl.style.transform =
|
if (left < (width/2)) {
|
||||||
"translate(" + (left - width - 15) + "px, " + (top + 15) + "px)";
|
legendEl.style.transform = "translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||||
|
} else {
|
||||||
|
legendEl.style.transform = "translate(" + (left - internalWidth - 15) + "px, " + (top + 15) + "px)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -293,6 +292,14 @@
|
|||||||
if (!uplot) {
|
if (!uplot) {
|
||||||
opts.width = ren_width;
|
opts.width = ren_width;
|
||||||
opts.height = ren_height;
|
opts.height = ren_height;
|
||||||
|
|
||||||
|
if (plotSync) {
|
||||||
|
opts.cursor.sync = {
|
||||||
|
key: plotSync.key,
|
||||||
|
scales: ["x", null],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uplot = new uPlot(opts, data, plotWrapper); // Data is uplot formatted [[X][Ymin][Yavg][Ymax]]
|
uplot = new uPlot(opts, data, plotWrapper); // Data is uplot formatted [[X][Ymin][Yavg][Ymax]]
|
||||||
plotSync.sub(uplot)
|
plotSync.sub(uplot)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
xtime = false,
|
xtime = false,
|
||||||
ylabel = "",
|
ylabel = "",
|
||||||
yunit = "",
|
yunit = "",
|
||||||
|
enableFlip = false,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
@@ -117,8 +118,12 @@
|
|||||||
|
|
||||||
function update(u) {
|
function update(u) {
|
||||||
const { left, top } = u.cursor;
|
const { left, top } = u.cursor;
|
||||||
legendEl.style.transform =
|
const internalWidth = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
||||||
"translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
if (enableFlip && (left < (width/2))) {
|
||||||
|
legendEl.style.transform = "translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||||
|
} else {
|
||||||
|
legendEl.style.transform = "translate(" + (left - internalWidth - 15) + "px, " + (top + 15) + "px)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
- `statisticsSeries [GraphQL.StatisticsSeries]?`: Min/Max/Median representation of metric data [Default: null]
|
- `statisticsSeries [GraphQL.StatisticsSeries]?`: Min/Max/Median representation of metric data [Default: null]
|
||||||
- `cluster String?`: Cluster name of the parent job / data [Default: ""]
|
- `cluster String?`: Cluster name of the parent job / data [Default: ""]
|
||||||
- `subCluster String`: Name of the subCluster of the parent job
|
- `subCluster String`: Name of the subCluster of the parent job
|
||||||
- `isShared Bool?`: If this job used shared resources; will adapt threshold indicators accordingly [Default: false]
|
- `isShared Bool?`: If this job used shared resources; for additional legend display [Default: false]
|
||||||
- `forNode Bool?`: If this plot is used for node data display; will render x-axis as negative time with $now as maximum [Default: false]
|
- `forNode Bool?`: If this plot is used for node data display; will render x-axis as negative time with $now as maximum [Default: false]
|
||||||
- `numhwthreads Number?`: Number of job HWThreads [Default: 0]
|
- `numhwthreads Number?`: Number of job HWThreads [Default: 0]
|
||||||
- `numaccs Number?`: Number of job Accelerators [Default: 0]
|
- `numaccs Number?`: Number of job Accelerators [Default: 0]
|
||||||
@@ -48,6 +48,8 @@
|
|||||||
zoomState = null,
|
zoomState = null,
|
||||||
thresholdState = null,
|
thresholdState = null,
|
||||||
extendedLegendData = null,
|
extendedLegendData = null,
|
||||||
|
plotSync = null,
|
||||||
|
enableFlip = false,
|
||||||
onZoom
|
onZoom
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
@@ -83,7 +85,6 @@
|
|||||||
subClusterTopology,
|
subClusterTopology,
|
||||||
metricConfig,
|
metricConfig,
|
||||||
scope,
|
scope,
|
||||||
isShared,
|
|
||||||
numhwthreads,
|
numhwthreads,
|
||||||
numaccs
|
numaccs
|
||||||
));
|
));
|
||||||
@@ -277,7 +278,6 @@
|
|||||||
subClusterTopology,
|
subClusterTopology,
|
||||||
metricConfig,
|
metricConfig,
|
||||||
scope,
|
scope,
|
||||||
isShared,
|
|
||||||
numhwthreads,
|
numhwthreads,
|
||||||
numaccs
|
numaccs
|
||||||
) {
|
) {
|
||||||
@@ -293,10 +293,8 @@
|
|||||||
scope = statParts[0]
|
scope = statParts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (metricConfig?.aggregation == "avg") {
|
||||||
(scope == "node" && isShared == false) ||
|
// Return as Configured
|
||||||
metricConfig?.aggregation == "avg"
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
normal: metricConfig.normal,
|
normal: metricConfig.normal,
|
||||||
caution: metricConfig.caution,
|
caution: metricConfig.caution,
|
||||||
@@ -306,19 +304,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (metricConfig?.aggregation == "sum") {
|
if (metricConfig?.aggregation == "sum") {
|
||||||
|
// Scale Thresholds
|
||||||
|
let fraction;
|
||||||
|
if (numaccs > 0) fraction = subClusterTopology.accelerators.length / numaccs;
|
||||||
|
else if (numhwthreads > 0) fraction = subClusterTopology.core.length / numhwthreads;
|
||||||
|
else fraction = 1; // Fallback
|
||||||
|
|
||||||
let divisor;
|
let divisor;
|
||||||
if (isShared == true) { // Shared
|
// Exclusive: Fraction = 1; Shared: Fraction > 1
|
||||||
if (numaccs > 0) divisor = subClusterTopology.accelerators.length / numaccs;
|
if (scope == 'node') divisor = fraction;
|
||||||
else if (numhwthreads > 0) divisor = subClusterTopology.core.length / numhwthreads;
|
// Cap divisor at number of available sockets or domains
|
||||||
}
|
else if (scope == 'socket') divisor = (fraction < subClusterTopology.socket.length) ? subClusterTopology.socket.length : fraction;
|
||||||
else if (scope == 'node') divisor = 1; // Use as configured for nodes
|
else if (scope == "memoryDomain") divisor = (fraction < subClusterTopology.memoryDomain.length) ? subClusterTopology.socket.length : fraction;
|
||||||
else if (scope == 'socket') divisor = subClusterTopology.socket.length;
|
// Use Maximum Division for Smallest Scopes
|
||||||
else if (scope == "memoryDomain") divisor = subClusterTopology.memoryDomain.length;
|
|
||||||
else if (scope == "core") divisor = subClusterTopology.core.length;
|
else if (scope == "core") divisor = subClusterTopology.core.length;
|
||||||
else if (scope == "hwthread") divisor = subClusterTopology.core.length; // alt. name for core
|
else if (scope == "hwthread") divisor = subClusterTopology.core.length; // alt. name for core
|
||||||
else if (scope == "accelerator") divisor = subClusterTopology.accelerators.length;
|
else if (scope == "accelerator") divisor = subClusterTopology.accelerators.length;
|
||||||
else {
|
else {
|
||||||
console.log('Unknown scope, return default aggregation thresholds ', scope)
|
console.log('Unknown scope, return default aggregation thresholds for sum', scope)
|
||||||
divisor = 1;
|
divisor = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,9 +398,12 @@
|
|||||||
|
|
||||||
function update(u) {
|
function update(u) {
|
||||||
const { left, top } = u.cursor;
|
const { left, top } = u.cursor;
|
||||||
const width = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
const internalWidth = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
||||||
legendEl.style.transform =
|
if (enableFlip && (left < (width/2))) {
|
||||||
"translate(" + (left - width - 15) + "px, " + (top + 15) + "px)";
|
legendEl.style.transform = "translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||||
|
} else {
|
||||||
|
legendEl.style.transform = "translate(" + (left - internalWidth - 15) + "px, " + (top + 15) + "px)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataSize <= 12 || useStatsSeries) {
|
if (dataSize <= 12 || useStatsSeries) {
|
||||||
@@ -576,6 +582,14 @@
|
|||||||
if (!uplot) {
|
if (!uplot) {
|
||||||
opts.width = ren_width;
|
opts.width = ren_width;
|
||||||
opts.height = ren_height;
|
opts.height = ren_height;
|
||||||
|
|
||||||
|
if (plotSync) {
|
||||||
|
opts.cursor.sync = {
|
||||||
|
key: plotSync.key,
|
||||||
|
scales: ["x", null],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (zoomState && metricConfig?.aggregation == "avg") {
|
if (zoomState && metricConfig?.aggregation == "avg") {
|
||||||
opts.scales = {...zoomState}
|
opts.scales = {...zoomState}
|
||||||
} else if (zoomState && metricConfig?.aggregation == "sum") {
|
} else if (zoomState && metricConfig?.aggregation == "sum") {
|
||||||
@@ -584,6 +598,7 @@
|
|||||||
if ((thresholdState === thresholds?.normal)) { opts.scales = {...zoomState} };
|
if ((thresholdState === thresholds?.normal)) { opts.scales = {...zoomState} };
|
||||||
} // else: reset scaling to default
|
} // else: reset scaling to default
|
||||||
}
|
}
|
||||||
|
|
||||||
uplot = new uPlot(opts, plotData, plotWrapper);
|
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||||
} else {
|
} else {
|
||||||
uplot.setSize({ width: ren_width, height: ren_height });
|
uplot.setSize({ width: ren_width, height: ren_height });
|
||||||
|
|||||||
@@ -276,13 +276,13 @@
|
|||||||
// Nodes: Color based on Idle vs. Allocated
|
// Nodes: Color based on Idle vs. Allocated
|
||||||
} else if (nodesData) {
|
} else if (nodesData) {
|
||||||
// console.log('In Plot Handler NodesData', nodesData)
|
// console.log('In Plot Handler NodesData', nodesData)
|
||||||
if (nodesData[i]?.nodeState == "idle") {
|
if (nodesData[i]?.schedulerState == "idle") {
|
||||||
//u.ctx.strokeStyle = "rgb(0, 0, 255)";
|
//u.ctx.strokeStyle = "rgb(0, 0, 255)";
|
||||||
u.ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
|
u.ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
|
||||||
} else if (nodesData[i]?.nodeState == "allocated") {
|
} else if (nodesData[i]?.schedulerState == "allocated") {
|
||||||
//u.ctx.strokeStyle = "rgb(0, 255, 0)";
|
//u.ctx.strokeStyle = "rgb(0, 255, 0)";
|
||||||
u.ctx.fillStyle = "rgba(0, 255, 0, 0.5)";
|
u.ctx.fillStyle = "rgba(0, 255, 0, 0.5)";
|
||||||
} else if (nodesData[i]?.nodeState == "notindb") {
|
} else if (nodesData[i]?.schedulerState == "notindb") {
|
||||||
//u.ctx.strokeStyle = "rgb(0, 0, 0)";
|
//u.ctx.strokeStyle = "rgb(0, 0, 0)";
|
||||||
u.ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
u.ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
||||||
} else { // Fallback: All other DEFINED states
|
} else { // Fallback: All other DEFINED states
|
||||||
@@ -436,11 +436,11 @@
|
|||||||
tooltip.style.borderColor = getRGB(u.data[2][i]);
|
tooltip.style.borderColor = getRGB(u.data[2][i]);
|
||||||
// Nodes: Color based on Idle vs. Allocated
|
// Nodes: Color based on Idle vs. Allocated
|
||||||
} else if (nodesData) {
|
} else if (nodesData) {
|
||||||
if (nodesData[i]?.nodeState == "idle") {
|
if (nodesData[i]?.schedulerState == "idle") {
|
||||||
tooltip.style.borderColor = "rgb(0, 0, 255)";
|
tooltip.style.borderColor = "rgb(0, 0, 255)";
|
||||||
} else if (nodesData[i]?.nodeState == "allocated") {
|
} else if (nodesData[i]?.schedulerState == "allocated") {
|
||||||
tooltip.style.borderColor = "rgb(0, 255, 0)";
|
tooltip.style.borderColor = "rgb(0, 255, 0)";
|
||||||
} else if (nodesData[i]?.nodeState == "notindb") { // Missing from DB table
|
} else if (nodesData[i]?.schedulerState == "notindb") { // Missing from DB table
|
||||||
tooltip.style.borderColor = "rgb(0, 0, 0)";
|
tooltip.style.borderColor = "rgb(0, 0, 0)";
|
||||||
} else { // Fallback: All other DEFINED states
|
} else { // Fallback: All other DEFINED states
|
||||||
tooltip.style.borderColor = "rgb(255, 0, 0)";
|
tooltip.style.borderColor = "rgb(255, 0, 0)";
|
||||||
@@ -459,7 +459,7 @@
|
|||||||
} else if (nodesData && useColors) {
|
} else if (nodesData && useColors) {
|
||||||
tooltip.textContent = (
|
tooltip.textContent = (
|
||||||
// Tooltip Content as String for Node
|
// Tooltip Content as String for Node
|
||||||
`Host: ${getLegendData(u, i).nodeName}\nState: ${getLegendData(u, i).nodeState}\nJobs: ${getLegendData(u, i).numJobs}`
|
`Host: ${getLegendData(u, i).nodeName}\nState: ${getLegendData(u, i).schedulerState}\nJobs: ${getLegendData(u, i).numJobs}`
|
||||||
);
|
);
|
||||||
} else if (nodesData && !useColors) {
|
} else if (nodesData && !useColors) {
|
||||||
tooltip.textContent = (
|
tooltip.textContent = (
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
const paddingLeft = 40;
|
const paddingLeft = 40;
|
||||||
const paddingRight = 10;
|
const paddingRight = 10;
|
||||||
const paddingTop = 10;
|
const paddingTop = 10;
|
||||||
const paddingBottom = 5;
|
const paddingBottom = 40;
|
||||||
|
|
||||||
/* Var Init */
|
/* Var Init */
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
if (data.xLabel) {
|
if (data.xLabel) {
|
||||||
ctx.font = `${labelFontSize}px ${fontFamily}`
|
ctx.font = `${labelFontSize}px ${fontFamily}`
|
||||||
let textWidth = ctx.measureText(data.xLabel).width
|
let textWidth = ctx.measureText(data.xLabel).width
|
||||||
ctx.fillText(data.xLabel, Math.floor((width / 2) - (textWidth / 2)), height - 20)
|
ctx.fillText(data.xLabel, Math.floor((width / 2) - (textWidth / 2)), height - paddingBottom + 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.textAlign = 'center'
|
ctx.textAlign = 'center'
|
||||||
|
|||||||
@@ -1,34 +1,25 @@
|
|||||||
<!--
|
<!--
|
||||||
@component Node State/Health Data Stacked Plot Component, based on uPlot; states by timestamp
|
@component Node State/Health Data Stacked Plot Component, based on uPlot; states by timestamp
|
||||||
|
|
||||||
Only width/height should change reactively.
|
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `metric String?`: The metric name [Default: ""]
|
|
||||||
- `width Number?`: The plot width [Default: 0]
|
- `width Number?`: The plot width [Default: 0]
|
||||||
- `height Number?`: The plot height [Default: 300]
|
- `height Number?`: The plot height [Default: 300]
|
||||||
- `data [Array]`: The data object [Default: null]
|
- `data [Array]`: The data object [Default: null]
|
||||||
- `title String?`: Plot title [Default: ""]
|
|
||||||
- `xlabel String?`: Plot X axis label [Default: ""]
|
- `xlabel String?`: Plot X axis label [Default: ""]
|
||||||
- `ylabel String?`: Plot Y axis label [Default: ""]
|
- `ylabel String?`: Plot Y axis label [Default: ""]
|
||||||
- `yunit String?`: Plot Y axis unit [Default: ""]
|
- `yunit String?`: Plot Y axis unit [Default: ""]
|
||||||
- `xticks Array`: Array containing jobIDs [Default: []]
|
- `title String?`: Plot title [Default: ""]
|
||||||
- `xinfo Array`: Array containing job information [Default: []]
|
- `stateType String?`: Which states to render, affects plot render config [Options: Health, Node; Default: ""]
|
||||||
- `forResources Bool?`: Render this plot for allocated jobResources [Default: false]
|
|
||||||
- `plot Sync Object!`: uPlot cursor synchronization key
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import uPlot from "uplot";
|
import uPlot from "uplot";
|
||||||
import { roundTwoDigits, formatDurationTime, formatUnixTime, formatNumber } from "../units.js";
|
import { formatUnixTime } from "../units.js";
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
import { Card } from "@sveltestrap/sveltestrap";
|
import { Card } from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
// NOTE: Metric Thresholds non-required, Cluster Mixing Allowed
|
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
cluster = "",
|
|
||||||
width = 0,
|
width = 0,
|
||||||
height = 300,
|
height = 300,
|
||||||
data = null,
|
data = null,
|
||||||
@@ -36,16 +27,86 @@
|
|||||||
ylabel = "",
|
ylabel = "",
|
||||||
yunit = "",
|
yunit = "",
|
||||||
title = "",
|
title = "",
|
||||||
stateType = "" // Health, Slurm, Both
|
stateType = "" // Health, Node
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const clusterCockpitConfig = getContext("cc-config");
|
const clusterCockpitConfig = getContext("cc-config");
|
||||||
const lineWidth = clusterCockpitConfig?.plotConfiguration_lineWidth / window.devicePixelRatio || 2;
|
const lineWidth = clusterCockpitConfig?.plotConfiguration_lineWidth / window.devicePixelRatio || 2;
|
||||||
const cbmode = clusterCockpitConfig?.plotConfiguration_colorblindMode || false;
|
const cbmode = clusterCockpitConfig?.plotConfiguration_colorblindMode || false;
|
||||||
|
const seriesConfig = {
|
||||||
|
full: {
|
||||||
|
label: "Full",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(0, 110, 0, 0.4)" : "rgba(0, 128, 0, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(0, 110, 0)" : "green",
|
||||||
|
},
|
||||||
|
partial: {
|
||||||
|
label: "Partial",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(235, 172, 35, 0.4)" : "rgba(255, 215, 0, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(235, 172, 35)" : "gold",
|
||||||
|
},
|
||||||
|
failed: {
|
||||||
|
label: "Failed",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgb(181, 29, 20, 0.4)" : "rgba(255, 0, 0, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(181, 29, 20)" : "red",
|
||||||
|
},
|
||||||
|
idle: {
|
||||||
|
label: "Idle",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(0, 140, 249, 0.4)" : "rgba(0, 0, 255, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(0, 140, 249)" : "blue",
|
||||||
|
},
|
||||||
|
allocated: {
|
||||||
|
label: "Allocated",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(0, 110, 0, 0.4)" : "rgba(0, 128, 0, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(0, 110, 0)" : "green",
|
||||||
|
},
|
||||||
|
reserved: {
|
||||||
|
label: "Reserved",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(209, 99, 230, 0.4)" : "rgba(255, 0, 255, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(209, 99, 230)" : "magenta",
|
||||||
|
},
|
||||||
|
mixed: {
|
||||||
|
label: "Mixed",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(235, 172, 35, 0.4)" : "rgba(255, 215, 0, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(235, 172, 35)" : "gold",
|
||||||
|
},
|
||||||
|
down: {
|
||||||
|
label: "Down",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: cbmode ? "rgba(181, 29 ,20, 0.4)" : "rgba(255, 0, 0, 0.4)",
|
||||||
|
stroke: cbmode ? "rgb(181, 29, 20)" : "red",
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
label: "Unknown",
|
||||||
|
scale: "y",
|
||||||
|
width: lineWidth,
|
||||||
|
fill: "rgba(0, 0, 0, 0.4)",
|
||||||
|
stroke: "black",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Data Prep For uPlot
|
||||||
|
const sortedData = data.sort((a, b) => a.state.localeCompare(b.state));
|
||||||
|
const collectLabel = sortedData.map(d => d.state);
|
||||||
|
// Align Data to Timesteps, Introduces 'undefied' as placeholder, reiterate and set those to 0
|
||||||
|
const collectData = uPlot.join(sortedData.map(d => [d.times, d.counts])).map(d => d.map(i => i ? i : 0));
|
||||||
|
|
||||||
// STACKED CHART FUNCTIONS //
|
// STACKED CHART FUNCTIONS //
|
||||||
|
|
||||||
function stack(data, omit) {
|
function stack(data, omit) {
|
||||||
let data2 = [];
|
let data2 = [];
|
||||||
let bands = [];
|
let bands = [];
|
||||||
@@ -74,23 +135,46 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOpts(title, series) {
|
function getStackedOpts(title, width, height, series, data) {
|
||||||
return {
|
let opts = {
|
||||||
scales: {
|
width,
|
||||||
x: {
|
height,
|
||||||
time: false,
|
title,
|
||||||
},
|
plugins: [legendAsTooltipPlugin()],
|
||||||
},
|
series,
|
||||||
series
|
axes: [
|
||||||
};
|
{
|
||||||
|
scale: "x",
|
||||||
|
space: 25, // Tick Spacing
|
||||||
|
rotate: 30,
|
||||||
|
show: true,
|
||||||
|
label: xlabel,
|
||||||
|
values(self, splits) {
|
||||||
|
return splits.map(s => formatUnixTime(s));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: "y",
|
||||||
|
grid: { show: true },
|
||||||
|
labelFont: "sans-serif",
|
||||||
|
label: ylabel + (yunit ? ` (${yunit})` : ''),
|
||||||
|
// values: (u, vals) => vals.map((v) => formatNumber(v)),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
padding: [5, 10, 0, 0],
|
||||||
|
scales: {
|
||||||
|
x: { time: false },
|
||||||
|
y: { auto: true, distr: 1 },
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
drag: { x: true, y: true },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function getStackedOpts(title, series, data, interp) {
|
let stacked = stack(data, i => false);
|
||||||
let opts = getOpts(title, series);
|
|
||||||
|
|
||||||
let interped = interp ? interp(data) : data;
|
|
||||||
|
|
||||||
let stacked = stack(interped, i => false);
|
|
||||||
opts.bands = stacked.bands;
|
opts.bands = stacked.bands;
|
||||||
|
|
||||||
opts.cursor = opts.cursor || {};
|
opts.cursor = opts.cursor || {};
|
||||||
@@ -99,7 +183,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
opts.series.forEach(s => {
|
opts.series.forEach(s => {
|
||||||
s.value = (u, v, si, i) => data[si][i];
|
// Format Time Info from Unix TS to LocalTimeString
|
||||||
|
s.value = (u, v, si, i) => (si === 0) ? formatUnixTime(data[si][i]) : data[si][i];
|
||||||
|
|
||||||
s.points = s.points || {};
|
s.points = s.points || {};
|
||||||
|
|
||||||
@@ -138,331 +223,7 @@
|
|||||||
return {opts, data: stacked.data};
|
return {opts, data: stacked.data};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UPLOT PLUGIN: Converts the legend into a simple tooltip
|
||||||
function stack2(series) {
|
|
||||||
// for uplot data
|
|
||||||
let data = Array(series.length);
|
|
||||||
let bands = [];
|
|
||||||
|
|
||||||
let dataLen = series[0].values.length;
|
|
||||||
|
|
||||||
let zeroArr = Array(dataLen).fill(0);
|
|
||||||
|
|
||||||
let stackGroups = new Map();
|
|
||||||
let seriesStackKeys = Array(series.length);
|
|
||||||
|
|
||||||
series.forEach((s, si) => {
|
|
||||||
let vals = s.values.slice();
|
|
||||||
|
|
||||||
// apply negY
|
|
||||||
if (s.negY) {
|
|
||||||
for (let i = 0; i < vals.length; i++) {
|
|
||||||
if (vals[i] != null)
|
|
||||||
vals[i] *= -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.stacking.mode != 'none') {
|
|
||||||
let hasPos = vals.some(v => v > 0);
|
|
||||||
// derive stacking key
|
|
||||||
let stackKey = seriesStackKeys[si] = s.stacking.mode + s.scaleKey + s.stacking.group + (hasPos ? '+' : '-');
|
|
||||||
let group = stackGroups.get(stackKey);
|
|
||||||
|
|
||||||
// initialize stacking group
|
|
||||||
if (group == null) {
|
|
||||||
group = {
|
|
||||||
series: [],
|
|
||||||
acc: zeroArr.slice(),
|
|
||||||
dir: hasPos ? -1 : 1,
|
|
||||||
};
|
|
||||||
stackGroups.set(stackKey, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
// push for bands gen
|
|
||||||
group.series.unshift(si);
|
|
||||||
|
|
||||||
let stacked = data[si] = Array(dataLen);
|
|
||||||
let { acc } = group;
|
|
||||||
|
|
||||||
for (let i = 0; i < dataLen; i++) {
|
|
||||||
let v = vals[i];
|
|
||||||
|
|
||||||
if (v != null)
|
|
||||||
stacked[i] = (acc[i] += v);
|
|
||||||
else
|
|
||||||
stacked[i] = v; // we may want to coerce to 0 here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
data[si] = vals;
|
|
||||||
});
|
|
||||||
|
|
||||||
// re-compute by percent
|
|
||||||
series.forEach((s, si) => {
|
|
||||||
if (s.stacking.mode == 'percent') {
|
|
||||||
let group = stackGroups.get(seriesStackKeys[si]);
|
|
||||||
let { acc } = group;
|
|
||||||
|
|
||||||
// re-negatify percent
|
|
||||||
let sign = group.dir * -1;
|
|
||||||
|
|
||||||
let stacked = data[si];
|
|
||||||
|
|
||||||
for (let i = 0; i < dataLen; i++) {
|
|
||||||
let v = stacked[i];
|
|
||||||
|
|
||||||
if (v != null)
|
|
||||||
stacked[i] = sign * (v / acc[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// generate bands between adjacent group series
|
|
||||||
stackGroups.forEach(group => {
|
|
||||||
let { series, dir } = group;
|
|
||||||
let lastIdx = series.length - 1;
|
|
||||||
|
|
||||||
series.forEach((si, i) => {
|
|
||||||
if (i != lastIdx) {
|
|
||||||
let nextIdx = series[i + 1];
|
|
||||||
bands.push({
|
|
||||||
// since we're not passing x series[0] for stacking, real idxs are actually +1
|
|
||||||
series: [si + 1, nextIdx + 1],
|
|
||||||
dir,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
bands,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// UPLOT SERIES INIT //
|
|
||||||
|
|
||||||
const plotSeries = [
|
|
||||||
{
|
|
||||||
label: "Time",
|
|
||||||
scale: "x",
|
|
||||||
value: (u, ts, sidx, didx) =>
|
|
||||||
(didx == null) ? null : formatUnixTime(ts),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
if (stateType === "slurm") {
|
|
||||||
const resSeries = [
|
|
||||||
{
|
|
||||||
label: "Idle",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(136, 204, 238)" : "lightblue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Allocated",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(30, 136, 229)" : "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Reserved",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(211, 95, 183)" : "magenta",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Mixed",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(239, 230, 69)" : "yellow",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Down",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(225, 86, 44)" : "red",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Unknown",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: "black",
|
|
||||||
}
|
|
||||||
];
|
|
||||||
plotSeries.push(...resSeries)
|
|
||||||
} else if (stateType === "health") {
|
|
||||||
const resSeries = [
|
|
||||||
{
|
|
||||||
label: "Full",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(30, 136, 229)" : "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Partial",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(239, 230, 69)" : "yellow",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Failed",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(225, 86, 44)" : "red",
|
|
||||||
}
|
|
||||||
];
|
|
||||||
plotSeries.push(...resSeries)
|
|
||||||
} else {
|
|
||||||
const resSeries = [
|
|
||||||
{
|
|
||||||
label: "Full",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(30, 136, 229)" : "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Partial",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(239, 230, 69)" : "yellow",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Failed",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(225, 86, 44)" : "red",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Idle",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(136, 204, 238)" : "lightblue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Allocated",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(30, 136, 229)" : "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Reserved",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(211, 95, 183)" : "magenta",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Mixed",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(239, 230, 69)" : "yellow",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Down",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: cbmode ? "rgb(225, 86, 44)" : "red",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Unknown",
|
|
||||||
scale: "y",
|
|
||||||
width: lineWidth,
|
|
||||||
stroke: "black",
|
|
||||||
}
|
|
||||||
];
|
|
||||||
plotSeries.push(...resSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UPLOT BAND COLORS //
|
|
||||||
// const plotBands = [
|
|
||||||
// { series: [5, 4], fill: cbmode ? "rgba(0,0,255,0.1)" : "rgba(0,255,0,0.1)" },
|
|
||||||
// { series: [4, 3], fill: cbmode ? "rgba(0,255,0,0.1)" : "rgba(255,0,0,0.1)" },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// UPLOT OPTIONS //
|
|
||||||
const opts = {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
title,
|
|
||||||
plugins: [legendAsTooltipPlugin()],
|
|
||||||
series: plotSeries,
|
|
||||||
axes: [
|
|
||||||
{
|
|
||||||
scale: "x",
|
|
||||||
space: 25, // Tick Spacing
|
|
||||||
rotate: 30,
|
|
||||||
show: true,
|
|
||||||
label: xlabel,
|
|
||||||
// values(self, splits) {
|
|
||||||
// return splits.map(s => xticks[s]);
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scale: "y",
|
|
||||||
grid: { show: true },
|
|
||||||
labelFont: "sans-serif",
|
|
||||||
label: ylabel + (yunit ? ` (${yunit})` : ''),
|
|
||||||
// values: (u, vals) => vals.map((v) => formatNumber(v)),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// bands: forResources ? [] : plotBands,
|
|
||||||
padding: [5, 10, 0, 0],
|
|
||||||
// hooks: {
|
|
||||||
// draw: [
|
|
||||||
// (u) => {
|
|
||||||
// // Draw plot type label:
|
|
||||||
// let textl = forResources ? "Job Resources by Type" : "Metric Min/Avg/Max for Job Duration";
|
|
||||||
// let textr = "Earlier <- StartTime -> Later";
|
|
||||||
// u.ctx.save();
|
|
||||||
// u.ctx.textAlign = "start";
|
|
||||||
// u.ctx.fillStyle = "black";
|
|
||||||
// u.ctx.fillText(textl, u.bbox.left + 10, u.bbox.top + 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.restore();
|
|
||||||
// return;
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
scales: {
|
|
||||||
x: { time: false },
|
|
||||||
y: {auto: true, distr: 1},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
// Display legend
|
|
||||||
show: true,
|
|
||||||
live: true,
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
drag: { x: true, y: true },
|
|
||||||
// sync: {
|
|
||||||
// key: plotSync.key,
|
|
||||||
// scales: ["x", null],
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Var Init */
|
|
||||||
let timeoutId = null;
|
|
||||||
let uplot = null;
|
|
||||||
|
|
||||||
/* State Init */
|
|
||||||
let plotWrapper = $state(null);
|
|
||||||
|
|
||||||
/* Effects */
|
|
||||||
$effect(() => {
|
|
||||||
if (plotWrapper) {
|
|
||||||
onSizeChange(width, height);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
// UPLOT PLUGIN // converts the legend into a simple tooltip
|
|
||||||
function legendAsTooltipPlugin({
|
function legendAsTooltipPlugin({
|
||||||
className,
|
className,
|
||||||
style = { backgroundColor: "rgba(255, 249, 196, 0.92)", color: "black" },
|
style = { backgroundColor: "rgba(255, 249, 196, 0.92)", color: "black" },
|
||||||
@@ -476,7 +237,7 @@
|
|||||||
className && legendEl.classList.add(className);
|
className && legendEl.classList.add(className);
|
||||||
|
|
||||||
uPlot.assign(legendEl.style, {
|
uPlot.assign(legendEl.style, {
|
||||||
minWidth: "100px",
|
minWidth: "175px",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
display: "none",
|
display: "none",
|
||||||
@@ -489,9 +250,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// hide series color markers:
|
// hide series color markers:
|
||||||
const idents = legendEl.querySelectorAll(".u-marker");
|
// const idents = legendEl.querySelectorAll(".u-marker");
|
||||||
for (let i = 0; i < idents.length; i++)
|
// for (let i = 0; i < idents.length; i++)
|
||||||
idents[i].style.display = "none";
|
// idents[i].style.display = "none";
|
||||||
|
|
||||||
const overEl = u.over;
|
const overEl = u.over;
|
||||||
overEl.style.overflow = "visible";
|
overEl.style.overflow = "visible";
|
||||||
@@ -510,9 +271,12 @@
|
|||||||
|
|
||||||
function update(u) {
|
function update(u) {
|
||||||
const { left, top } = u.cursor;
|
const { left, top } = u.cursor;
|
||||||
const width = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
const internalWidth = u?.over?.querySelector(".u-legend")?.offsetWidth ? u.over.querySelector(".u-legend").offsetWidth : 0;
|
||||||
legendEl.style.transform =
|
if (left < (width/2)) {
|
||||||
"translate(" + (left - width - 15) + "px, " + (top + 15) + "px)";
|
legendEl.style.transform = "translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||||
|
} else {
|
||||||
|
legendEl.style.transform = "translate(" + (left - internalWidth - 15) + "px, " + (top + 15) + "px)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -523,13 +287,34 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// RENDER HANDLING
|
// UPLOT SERIES INIT
|
||||||
|
const plotSeries = [
|
||||||
|
{
|
||||||
|
label: "Time",
|
||||||
|
scale: "x"
|
||||||
|
},
|
||||||
|
...collectLabel.map(l => seriesConfig[l])
|
||||||
|
]
|
||||||
|
|
||||||
|
/* Var Init */
|
||||||
|
let timeoutId = null;
|
||||||
|
let uplot = null;
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
let plotWrapper = $state(null);
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
|
$effect(() => {
|
||||||
|
if (plotWrapper) {
|
||||||
|
onSizeChange(width, height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
function render(ren_width, ren_height) {
|
function render(ren_width, ren_height) {
|
||||||
if (!uplot) {
|
if (!uplot) {
|
||||||
opts.width = ren_width;
|
let { opts, data } = getStackedOpts(title, ren_width, ren_height, plotSeries, collectData);
|
||||||
opts.height = ren_height;
|
uplot = new uPlot(opts, data, plotWrapper); // Data is uplot formatted [[X][Y1][Y2]...]
|
||||||
uplot = new uPlot(opts, data, plotWrapper); // Data is uplot formatted [[X][Ymin][Yavg][Ymax]]
|
|
||||||
plotSync.sub(uplot)
|
|
||||||
} else {
|
} else {
|
||||||
uplot.setSize({ width: ren_width, height: ren_height });
|
uplot.setSize({ width: ren_width, height: ren_height });
|
||||||
}
|
}
|
||||||
@@ -559,12 +344,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Define $width Wrapper and NoData Card -->
|
<!-- Define $width Wrapper and NoData Card -->
|
||||||
{#if data && data[0].length > 0}
|
{#if data && collectData[0].length > 0}
|
||||||
<div bind:this={plotWrapper} bind:clientWidth={width}
|
<div bind:this={plotWrapper} bind:clientWidth={width}
|
||||||
style="background-color: rgba(255, 255, 255, 1.0);" class="rounded"
|
style="background-color: rgba(255, 255, 255, 1.0);" class="rounded"
|
||||||
></div>
|
></div>
|
||||||
{:else}
|
{:else}
|
||||||
<Card body color="warning" class="mx-4 my-2"
|
<Card body color="warning" class="mx-4 my-2"
|
||||||
>Cannot render plot: No series data returned for <code>{metric?metric:'job resources'}</code></Card
|
>Cannot render plot: No series data returned for <code>{stateType} State Stacked Chart</code></Card
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -98,7 +98,12 @@
|
|||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
return avail.map((av) => av.cluster).join(', ')
|
return avail.map((av) => av.cluster).join(', ')
|
||||||
} else {
|
} else {
|
||||||
return avail.find((av) => av.cluster === cluster).subClusters.join(', ')
|
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
|
||||||
|
if (subAvail) {
|
||||||
|
return subAvail.join(', ')
|
||||||
|
} else {
|
||||||
|
return `Not available for ${cluster}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const defaultTo = new Date(Date.now());
|
const defaultTo = new Date(Date.now());
|
||||||
const defaultFrom = new Date(defaultTo.setHours(defaultTo.getHours() - 4));
|
const defaultFrom = new Date(new Date(Date.now()).setHours(defaultTo.getHours() - 4));
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let timeType = $state("range");
|
let timeType = $state("range");
|
||||||
|
|||||||
@@ -51,12 +51,13 @@ export function formatDurationTime(t, forNode = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatUnixTime(t) {
|
export function formatUnixTime(t, withDate = false) {
|
||||||
if (t !== null) {
|
if (t !== null) {
|
||||||
if (isNaN(t)) {
|
if (isNaN(t)) {
|
||||||
return t;
|
return t;
|
||||||
} else {
|
} else {
|
||||||
return new Date(t * 1000).toLocaleString()
|
if (withDate) return new Date(t * 1000).toLocaleString();
|
||||||
|
else return new Date(t * 1000).toLocaleTimeString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,7 @@
|
|||||||
{isShared}
|
{isShared}
|
||||||
{zoomState}
|
{zoomState}
|
||||||
{thresholdState}
|
{thresholdState}
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
{:else if statsSeries[selectedScopeIndex] != null && patternMatches}
|
{:else if statsSeries[selectedScopeIndex] != null && patternMatches}
|
||||||
<MetricPlot
|
<MetricPlot
|
||||||
@@ -202,6 +203,7 @@
|
|||||||
{thresholdState}
|
{thresholdState}
|
||||||
statisticsSeries={statsSeries[selectedScopeIndex]}
|
statisticsSeries={statsSeries[selectedScopeIndex]}
|
||||||
useStatsSeries={!!statsSeries[selectedScopeIndex]}
|
useStatsSeries={!!statsSeries[selectedScopeIndex]}
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
getContextClient,
|
getContextClient,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
import {
|
import {
|
||||||
init,
|
|
||||||
convert2uplot,
|
convert2uplot,
|
||||||
} from "../generic/utils.js";
|
} from "../generic/utils.js";
|
||||||
import PlotGrid from "../generic/PlotGrid.svelte";
|
import PlotGrid from "../generic/PlotGrid.svelte";
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const { query: initq } = init();
|
|
||||||
const ccconfig = getContext("cc-config");
|
const ccconfig = getContext("cc-config");
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
|
||||||
@@ -101,25 +99,18 @@
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row cols={1} class="text-center mt-3">
|
<Row cols={1} class="text-center mt-3">
|
||||||
|
{#if $metricStatusQuery.fetching}
|
||||||
<Col>
|
<Col>
|
||||||
{#if $initq.fetching || $metricStatusQuery.fetching}
|
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else if $initq.error}
|
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
|
||||||
{:else}
|
|
||||||
<!-- ... -->
|
|
||||||
{/if}
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
{:else if $metricStatusQuery.error}
|
||||||
{#if $metricStatusQuery.error}
|
|
||||||
<Row cols={1}>
|
|
||||||
<Col>
|
<Col>
|
||||||
<Card body color="danger">{$metricStatusQuery.error.message}</Card>
|
<Card body color="danger">{$metricStatusQuery.error.message}</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
{/if}
|
||||||
{/if}
|
</Row>
|
||||||
|
|
||||||
{#if $initq.data && $metricStatusQuery.data}
|
{#if $metricStatusQuery.data}
|
||||||
<!-- Selectable Stats as Histograms : Average Values of Running Jobs -->
|
<!-- Selectable Stats as Histograms : Average Values of Running Jobs -->
|
||||||
{#if selectedHistograms}
|
{#if selectedHistograms}
|
||||||
<!-- Note: Ignore '#snippet' Error in IDE -->
|
<!-- Note: Ignore '#snippet' Error in IDE -->
|
||||||
@@ -132,6 +123,7 @@
|
|||||||
ylabel="Number of Jobs"
|
ylabel="Number of Jobs"
|
||||||
yunit="Jobs"
|
yunit="Jobs"
|
||||||
usesBins
|
usesBins
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
@@ -22,34 +22,36 @@
|
|||||||
gql,
|
gql,
|
||||||
getContextClient,
|
getContextClient,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
import {
|
import { formatDurationTime } from "../generic/units.js";
|
||||||
init,
|
|
||||||
} from "../generic/utils.js";
|
|
||||||
import { scaleNumbers, formatDurationTime } from "../generic/units.js";
|
|
||||||
import Refresher from "../generic/helper/Refresher.svelte";
|
import Refresher from "../generic/helper/Refresher.svelte";
|
||||||
|
import TimeSelection from "../generic/select/TimeSelection.svelte";
|
||||||
import Roofline from "../generic/plots/Roofline.svelte";
|
import Roofline from "../generic/plots/Roofline.svelte";
|
||||||
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
||||||
|
import Stacked from "../generic/plots/Stacked.svelte";
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
|
clusters,
|
||||||
presetCluster,
|
presetCluster,
|
||||||
useCbColors = false,
|
useCbColors = false,
|
||||||
useAltColors = false,
|
useAltColors = false,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const { query: initq } = init();
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let cluster = $state(presetCluster);
|
let cluster = $state(presetCluster);
|
||||||
let pieWidth = $state(0);
|
let pieWidth = $state(0);
|
||||||
let stackedWidth = $state(0);
|
let stackedWidth1 = $state(0);
|
||||||
|
let stackedWidth2 = $state(0);
|
||||||
let plotWidths = $state([]);
|
let plotWidths = $state([]);
|
||||||
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
||||||
let to = $state(new Date(Date.now()));
|
let to = $state(new Date(Date.now()));
|
||||||
|
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
||||||
// Bar Gauges
|
// Bar Gauges
|
||||||
let allocatedNodes = $state({});
|
let allocatedNodes = $state({});
|
||||||
|
let allocatedCores = $state({});
|
||||||
let allocatedAccs = $state({});
|
let allocatedAccs = $state({});
|
||||||
let flopRate = $state({});
|
let flopRate = $state({});
|
||||||
let flopRateUnitPrefix = $state({});
|
let flopRateUnitPrefix = $state({});
|
||||||
@@ -63,46 +65,29 @@
|
|||||||
let totalAccs = $state({});
|
let totalAccs = $state({});
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
// Accumulated NodeStates for Piecharts
|
// States for Stacked charts
|
||||||
const nodesStateCounts = $derived(queryStore({
|
const statesTimed = $derived(queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query ($filter: [NodeFilter!]) {
|
query ($filter: [NodeFilter!], $typeNode: String!, $typeHealth: String!) {
|
||||||
nodeStates(filter: $filter) {
|
nodeStates: nodeStatesTimed(filter: $filter, type: $typeNode) {
|
||||||
state
|
state
|
||||||
count
|
counts
|
||||||
|
times
|
||||||
|
}
|
||||||
|
healthStates: nodeStatesTimed(filter: $filter, type: $typeHealth) {
|
||||||
|
state
|
||||||
|
counts
|
||||||
|
times
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: { cluster: { eq: cluster }}
|
filter: { cluster: { eq: cluster }, timeStart: stackedFrom},
|
||||||
},
|
typeNode: "node",
|
||||||
}));
|
typeHealth: "health"
|
||||||
|
|
||||||
const refinedStateData = $derived.by(() => {
|
|
||||||
return $nodesStateCounts?.data?.nodeStates.filter((e) => ['allocated', 'reserved', 'idle', 'mixed','down', 'unknown'].includes(e.state))
|
|
||||||
});
|
|
||||||
|
|
||||||
const refinedHealthData = $derived.by(() => {
|
|
||||||
return $nodesStateCounts?.data?.nodeStates.filter((e) => ['full', 'partial', 'failed'].includes(e.state))
|
|
||||||
});
|
|
||||||
|
|
||||||
// NodeStates for Stacked charts
|
|
||||||
const nodesStateTimes = $derived(queryStore({
|
|
||||||
client: client,
|
|
||||||
query: gql`
|
|
||||||
query ($filter: [NodeFilter!]) {
|
|
||||||
nodeStatesTimed(filter: $filter) {
|
|
||||||
state
|
|
||||||
type
|
|
||||||
count
|
|
||||||
time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
filter: { cluster: { eq: cluster }, timeStart: Date.now() - (24 * 3600 * 1000)} // Add Selector for Timeframe (4h, 12h, 24h)?
|
|
||||||
},
|
},
|
||||||
|
requestPolicy: "network-only"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Note: nodeMetrics are requested on configured $timestep resolution
|
// Note: nodeMetrics are requested on configured $timestep resolution
|
||||||
@@ -177,9 +162,14 @@
|
|||||||
hostname
|
hostname
|
||||||
cluster
|
cluster
|
||||||
subCluster
|
subCluster
|
||||||
nodeState
|
schedulerState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# Get Current States fir Pie Charts
|
||||||
|
nodeStates(filter: $nodeFilter) {
|
||||||
|
state
|
||||||
|
count
|
||||||
|
}
|
||||||
# totalNodes includes multiples if shared jobs
|
# totalNodes includes multiples if shared jobs
|
||||||
jobsStatistics(
|
jobsStatistics(
|
||||||
filter: $jobFilter
|
filter: $jobFilter
|
||||||
@@ -190,6 +180,7 @@
|
|||||||
id
|
id
|
||||||
totalJobs
|
totalJobs
|
||||||
totalUsers
|
totalUsers
|
||||||
|
totalCores
|
||||||
totalAccs
|
totalAccs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,10 +198,22 @@
|
|||||||
requestPolicy: "network-only"
|
requestPolicy: "network-only"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const refinedStateData = $derived.by(() => {
|
||||||
|
return $statusQuery?.data?.nodeStates.
|
||||||
|
filter((e) => ['allocated', 'reserved', 'idle', 'mixed','down', 'unknown'].includes(e.state)).
|
||||||
|
sort((a, b) => b.count - a.count)
|
||||||
|
});
|
||||||
|
|
||||||
|
const refinedHealthData = $derived.by(() => {
|
||||||
|
return $statusQuery?.data?.nodeStates.
|
||||||
|
filter((e) => ['full', 'partial', 'failed'].includes(e.state)).
|
||||||
|
sort((a, b) => b.count - a.count)
|
||||||
|
});
|
||||||
|
|
||||||
/* Effects */
|
/* Effects */
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($initq.data && $statusQuery.data) {
|
if ($statusQuery.data) {
|
||||||
let subClusters = $initq.data.clusters.find(
|
let subClusters = clusters.find(
|
||||||
(c) => c.name == cluster,
|
(c) => c.name == cluster,
|
||||||
).subClusters;
|
).subClusters;
|
||||||
for (let subCluster of subClusters) {
|
for (let subCluster of subClusters) {
|
||||||
@@ -219,6 +222,10 @@
|
|||||||
$statusQuery.data.allocatedNodes.find(
|
$statusQuery.data.allocatedNodes.find(
|
||||||
({ name }) => name == subCluster.name,
|
({ name }) => name == subCluster.name,
|
||||||
)?.count || 0;
|
)?.count || 0;
|
||||||
|
allocatedCores[subCluster.name] =
|
||||||
|
$statusQuery.data.jobsStatistics.find(
|
||||||
|
({ id }) => id == subCluster.name,
|
||||||
|
)?.totalCores || 0;
|
||||||
allocatedAccs[subCluster.name] =
|
allocatedAccs[subCluster.name] =
|
||||||
$statusQuery.data.jobsStatistics.find(
|
$statusQuery.data.jobsStatistics.find(
|
||||||
({ id }) => id == subCluster.name,
|
({ id }) => id == subCluster.name,
|
||||||
@@ -348,7 +355,7 @@
|
|||||||
for (let j = 0; j < subClusterData.length; j++) {
|
for (let j = 0; j < subClusterData.length; j++) {
|
||||||
const nodeName = subClusterData[j]?.host ? subClusterData[j].host : "unknown"
|
const nodeName = subClusterData[j]?.host ? subClusterData[j].host : "unknown"
|
||||||
const nodeMatch = $statusQuery?.data?.nodes?.items?.find((n) => n.hostname == nodeName && n.subCluster == subClusterData[j].subCluster);
|
const nodeMatch = $statusQuery?.data?.nodes?.items?.find((n) => n.hostname == nodeName && n.subCluster == subClusterData[j].subCluster);
|
||||||
const nodeState = nodeMatch?.nodeState ? nodeMatch.nodeState : "notindb"
|
const schedulerState = nodeMatch?.schedulerState ? nodeMatch.schedulerState : "notindb"
|
||||||
let numJobs = 0
|
let numJobs = 0
|
||||||
|
|
||||||
if ($statusQuery?.data) {
|
if ($statusQuery?.data) {
|
||||||
@@ -356,7 +363,7 @@
|
|||||||
numJobs = nodeJobs?.length ? nodeJobs.length : 0
|
numJobs = nodeJobs?.length ? nodeJobs.length : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({nodeName: nodeName, nodeState: nodeState, numJobs: numJobs})
|
result.push({nodeName: nodeName, schedulerState: schedulerState, numJobs: numJobs})
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return result
|
return result
|
||||||
@@ -378,14 +385,24 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Refresher and space for other options -->
|
<!-- Refresher and space for other options -->
|
||||||
<Row class="justify-content-end">
|
<Row class="justify-content-between">
|
||||||
|
<Col xs="12" md="5" lg="4" xl="3">
|
||||||
|
<TimeSelection
|
||||||
|
customEnabled={false}
|
||||||
|
applyTime={(newFrom, newTo) => {
|
||||||
|
stackedFrom = Math.floor(newFrom.getTime() / 1000);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
<Col xs="12" md="5" lg="4" xl="3">
|
<Col xs="12" md="5" lg="4" xl="3">
|
||||||
<Refresher
|
<Refresher
|
||||||
initially={120}
|
initially={120}
|
||||||
onRefresh={() => {
|
onRefresh={(interval) => {
|
||||||
console.log('Trigger Refresh StatusTab')
|
|
||||||
from = new Date(Date.now() - 5 * 60 * 1000);
|
from = new Date(Date.now() - 5 * 60 * 1000);
|
||||||
to = new Date(Date.now());
|
to = new Date(Date.now());
|
||||||
|
|
||||||
|
if (interval) stackedFrom += Math.floor(interval / 1000);
|
||||||
|
else stackedFrom += 1 // Workaround: TineSelection not linked, just trigger new data on manual refresh
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -394,43 +411,40 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<!-- Node Stack Charts Dev-->
|
<!-- Node Stack Charts Dev-->
|
||||||
<!--
|
{#if $statesTimed.data}
|
||||||
{#if $initq.data && $nodesStateTimes.data}
|
<Row cols={{ md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
||||||
<Row cols={{ lg: 4, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={stackedWidth}>
|
<div bind:clientWidth={stackedWidth1}>
|
||||||
{#key $nodesStateTimes.data}
|
{#key $statesTimed?.data?.nodeStates}
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States Over Time
|
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States Over Time
|
||||||
</h4>
|
</h4>
|
||||||
<Stacked
|
<Stacked
|
||||||
{cluster}
|
data={$statesTimed?.data?.nodeStates}
|
||||||
data={$nodesStateTimes?.data}
|
width={stackedWidth1 * 0.95}
|
||||||
width={stackedWidth * 0.55}
|
xlabel="Time"
|
||||||
xLabel="Time"
|
ylabel="Nodes"
|
||||||
yLabel="Nodes"
|
|
||||||
yunit = "#Count"
|
yunit = "#Count"
|
||||||
title = "Slurm States"
|
title = "Node States"
|
||||||
stateType = "slurm"
|
stateType = "Node"
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={stackedWidth}>
|
<div bind:clientWidth={stackedWidth2}>
|
||||||
{#key $nodesStateTimes.data}
|
{#key $statesTimed?.data?.healthStates}
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Health States Over Time
|
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Health States Over Time
|
||||||
</h4>
|
</h4>
|
||||||
<Stacked
|
<Stacked
|
||||||
{cluster}
|
data={$statesTimed?.data?.healthStates}
|
||||||
data={$nodesStateTimes?.data}
|
width={stackedWidth2 * 0.95}
|
||||||
width={stackedWidth * 0.55}
|
xlabel="Time"
|
||||||
xLabel="Time"
|
ylabel="Nodes"
|
||||||
yLabel="Nodes"
|
|
||||||
yunit = "#Count"
|
yunit = "#Count"
|
||||||
title = "Health States"
|
title = "Health States"
|
||||||
stateType = "health"
|
stateType = "Health"
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
@@ -439,17 +453,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<hr/>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Node Health Pis, later Charts -->
|
<!-- Node Health Pis, later Charts -->
|
||||||
{#if $initq.data && $nodesStateCounts.data}
|
{#if $statusQuery?.data?.nodeStates}
|
||||||
<Row cols={{ lg: 4, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
<Row cols={{ lg: 4, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={pieWidth}>
|
<div bind:clientWidth={pieWidth}>
|
||||||
{#key refinedStateData}
|
{#key refinedStateData}
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States
|
Current {cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States
|
||||||
</h4>
|
</h4>
|
||||||
<Pie
|
<Pie
|
||||||
{useAltColors}
|
{useAltColors}
|
||||||
@@ -489,7 +501,7 @@
|
|||||||
<div bind:clientWidth={pieWidth}>
|
<div bind:clientWidth={pieWidth}>
|
||||||
{#key refinedHealthData}
|
{#key refinedHealthData}
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node Health
|
Current {cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node Health
|
||||||
</h4>
|
</h4>
|
||||||
<Pie
|
<Pie
|
||||||
{useAltColors}
|
{useAltColors}
|
||||||
@@ -529,8 +541,8 @@
|
|||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<!-- Gauges & Roofline per Subcluster-->
|
<!-- Gauges & Roofline per Subcluster-->
|
||||||
{#if $initq.data && $statusQuery.data}
|
{#if $statusQuery.data}
|
||||||
{#each $initq.data.clusters.find((c) => c.name == cluster).subClusters as subCluster, i}
|
{#each clusters.find((c) => c.name == cluster).subClusters as subCluster, i}
|
||||||
<Row cols={{ lg: 3, md: 1 , sm: 1}} class="mb-3 justify-content-center">
|
<Row cols={{ lg: 3, md: 1 , sm: 1}} class="mb-3 justify-content-center">
|
||||||
<Col class="px-3">
|
<Col class="px-3">
|
||||||
<Card class="h-auto mt-1">
|
<Card class="h-auto mt-1">
|
||||||
@@ -579,6 +591,21 @@
|
|||||||
Nodes</td
|
Nodes</td
|
||||||
>
|
>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="py-2">
|
||||||
|
<th scope="col">Allocated Cores</th>
|
||||||
|
<td style="min-width: 100px;"
|
||||||
|
><div class="col">
|
||||||
|
<Progress
|
||||||
|
value={allocatedCores[subCluster.name]}
|
||||||
|
max={subCluster.socketsPerNode * subCluster.coresPerSocket * subCluster.numberOfNodes}
|
||||||
|
/>
|
||||||
|
</div></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
>{allocatedCores[subCluster.name]} / {subCluster.socketsPerNode * subCluster.coresPerSocket * subCluster.numberOfNodes}
|
||||||
|
Cores</td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
{#if totalAccs[subCluster.name] !== null}
|
{#if totalAccs[subCluster.name] !== null}
|
||||||
<tr class="py-2">
|
<tr class="py-2">
|
||||||
<th scope="col">Allocated Accelerators</th>
|
<th scope="col">Allocated Accelerators</th>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
getContextClient,
|
getContextClient,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
import {
|
import {
|
||||||
init,
|
|
||||||
scramble,
|
scramble,
|
||||||
scrambleNames,
|
scrambleNames,
|
||||||
convert2uplot,
|
convert2uplot,
|
||||||
@@ -41,7 +40,6 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const { query: initq } = init();
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
const durationBinOptions = ["1m","10m","1h","6h","12h"];
|
const durationBinOptions = ["1m","10m","1h","6h","12h"];
|
||||||
|
|
||||||
@@ -255,6 +253,7 @@
|
|||||||
height="275"
|
height="275"
|
||||||
usesBins
|
usesBins
|
||||||
xtime
|
xtime
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</Col>
|
</Col>
|
||||||
@@ -359,6 +358,7 @@
|
|||||||
ylabel="Number of Jobs"
|
ylabel="Number of Jobs"
|
||||||
yunit="Jobs"
|
yunit="Jobs"
|
||||||
height="275"
|
height="275"
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="6" md="3" lg="2" class="p-2">
|
<Col xs="6" md="3" lg="2" class="p-2">
|
||||||
@@ -462,6 +462,7 @@
|
|||||||
ylabel="Number of Jobs"
|
ylabel="Number of Jobs"
|
||||||
yunit="Jobs"
|
yunit="Jobs"
|
||||||
height="275"
|
height="275"
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="6" md="3" lg="2" class="p-2">
|
<Col xs="6" md="3" lg="2" class="p-2">
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
- `selectedMetrics [String]`: The array of selected metrics [Default []]
|
- `selectedMetrics [String]`: The array of selected metrics [Default []]
|
||||||
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
||||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||||
|
- `hoststateFilter String?`: The active hoststatefilter [Default: ""]
|
||||||
- `presetSystemUnits Object`: The object of metric units [Default: null]
|
- `presetSystemUnits Object`: The object of metric units [Default: null]
|
||||||
- `from Date?`: The selected "from" date [Default: null]
|
- `from Date?`: The selected "from" date [Default: null]
|
||||||
- `to Date?`: The selected "to" date [Default: null]
|
- `to Date?`: The selected "to" date [Default: null]
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
selectedMetrics = [],
|
selectedMetrics = [],
|
||||||
selectedResolution = 0,
|
selectedResolution = 0,
|
||||||
hostnameFilter = "",
|
hostnameFilter = "",
|
||||||
|
hoststateFilter = "",
|
||||||
presetSystemUnits = null,
|
presetSystemUnits = null,
|
||||||
from = null,
|
from = null,
|
||||||
to = null
|
to = null
|
||||||
@@ -37,11 +39,14 @@
|
|||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
const usePaging = ccconfig?.nodeList_usePaging || false;
|
const usePaging = ccconfig?.nodeList_usePaging || false;
|
||||||
const nodeListQuery = gql`
|
const nodeListQuery = gql`
|
||||||
query ($cluster: String!, $subCluster: String!, $nodeFilter: String!, $metrics: [String!], $scopes: [MetricScope!]!, $from: Time!, $to: Time!, $paging: PageRequest!, $selectedResolution: Int) {
|
query ($cluster: String!, $subCluster: String!, $nodeFilter: String!, $stateFilter: String!, $metrics: [String!],
|
||||||
|
$scopes: [MetricScope!]!, $from: Time!, $to: Time!, $paging: PageRequest!, $selectedResolution: Int
|
||||||
|
) {
|
||||||
nodeMetricsList(
|
nodeMetricsList(
|
||||||
cluster: $cluster
|
cluster: $cluster
|
||||||
subCluster: $subCluster
|
subCluster: $subCluster
|
||||||
nodeFilter: $nodeFilter
|
nodeFilter: $nodeFilter
|
||||||
|
stateFilter: $stateFilter,
|
||||||
scopes: $scopes
|
scopes: $scopes
|
||||||
metrics: $metrics
|
metrics: $metrics
|
||||||
from: $from
|
from: $from
|
||||||
@@ -51,6 +56,7 @@
|
|||||||
) {
|
) {
|
||||||
items {
|
items {
|
||||||
host
|
host
|
||||||
|
state
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name
|
name
|
||||||
@@ -100,6 +106,7 @@
|
|||||||
variables: {
|
variables: {
|
||||||
cluster: cluster,
|
cluster: cluster,
|
||||||
subCluster: subCluster,
|
subCluster: subCluster,
|
||||||
|
stateFilter: hoststateFilter,
|
||||||
nodeFilter: hostnameFilter,
|
nodeFilter: hostnameFilter,
|
||||||
scopes: ["core", "socket", "accelerator"],
|
scopes: ["core", "socket", "accelerator"],
|
||||||
metrics: selectedMetrics,
|
metrics: selectedMetrics,
|
||||||
@@ -137,7 +144,7 @@
|
|||||||
// Triggers (Except Paging)
|
// Triggers (Except Paging)
|
||||||
from, to
|
from, to
|
||||||
selectedMetrics, selectedResolution
|
selectedMetrics, selectedResolution
|
||||||
hostnameFilter
|
hostnameFilter, hoststateFilter
|
||||||
// Continous Scroll: Reset nodes and paging if parameters change: Existing entries will not match new selections
|
// Continous Scroll: Reset nodes and paging if parameters change: Existing entries will not match new selections
|
||||||
if (!usePaging) {
|
if (!usePaging) {
|
||||||
nodes = [];
|
nodes = [];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
- `cluster String`: The cluster to show status information for
|
- `cluster String`: The cluster to show status information for
|
||||||
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
||||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||||
|
- `hostnameFilter String?`: The active hoststatefilter [Default: ""]
|
||||||
- `from Date?`: The selected "from" date [Default: null]
|
- `from Date?`: The selected "from" date [Default: null]
|
||||||
- `to Date?`: The selected "to" date [Default: null]
|
- `to Date?`: The selected "to" date [Default: null]
|
||||||
-->
|
-->
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
||||||
import { Row, Col, Card, Spinner } from "@sveltestrap/sveltestrap";
|
import { Row, Col, Card, Spinner, Badge } from "@sveltestrap/sveltestrap";
|
||||||
import { checkMetricDisabled } from "../generic/utils.js";
|
import { checkMetricDisabled } from "../generic/utils.js";
|
||||||
import MetricPlot from "../generic/plots/MetricPlot.svelte";
|
import MetricPlot from "../generic/plots/MetricPlot.svelte";
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
cluster = "",
|
cluster = "",
|
||||||
selectedMetric = "",
|
selectedMetric = "",
|
||||||
hostnameFilter = "",
|
hostnameFilter = "",
|
||||||
|
hoststateFilter = "",
|
||||||
from = null,
|
from = null,
|
||||||
to = null
|
to = null
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -30,6 +32,16 @@
|
|||||||
/* Const Init */
|
/* Const Init */
|
||||||
const initialized = getContext("initialized");
|
const initialized = getContext("initialized");
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
// Node State Colors
|
||||||
|
const stateColors = {
|
||||||
|
allocated: 'success',
|
||||||
|
reserved: 'info',
|
||||||
|
idle: 'primary',
|
||||||
|
mixed: 'warning',
|
||||||
|
down: 'danger',
|
||||||
|
unknown: 'dark',
|
||||||
|
notindb: 'secondary'
|
||||||
|
}
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
const nodesQuery = $derived(queryStore({
|
const nodesQuery = $derived(queryStore({
|
||||||
@@ -43,6 +55,7 @@
|
|||||||
to: $to
|
to: $to
|
||||||
) {
|
) {
|
||||||
host
|
host
|
||||||
|
state
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name
|
name
|
||||||
@@ -75,7 +88,15 @@
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const mappedData = $derived(handleQueryData($initialized, $nodesQuery?.data));
|
const mappedData = $derived(handleQueryData($initialized, $nodesQuery?.data));
|
||||||
const filteredData = $derived(mappedData.filter((h) => h.host.includes(hostnameFilter)));
|
const filteredData = $derived(mappedData.filter((h) => {
|
||||||
|
if (hostnameFilter) {
|
||||||
|
if (hoststateFilter == 'all') return h.host.includes(hostnameFilter)
|
||||||
|
else return (h.host.includes(hostnameFilter) && h.state == hoststateFilter)
|
||||||
|
} else {
|
||||||
|
if (hoststateFilter == 'all') return true
|
||||||
|
else return h.state == hoststateFilter
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
function handleQueryData(isInitialized, queryData) {
|
function handleQueryData(isInitialized, queryData) {
|
||||||
@@ -94,6 +115,7 @@
|
|||||||
if (rawData.length > 0) {
|
if (rawData.length > 0) {
|
||||||
pendingMapped = rawData.map((h) => ({
|
pendingMapped = rawData.map((h) => ({
|
||||||
host: h.host,
|
host: h.host,
|
||||||
|
state: h?.state? h.state : 'notindb',
|
||||||
subCluster: h.subCluster,
|
subCluster: h.subCluster,
|
||||||
data: h.metrics.filter(
|
data: h.metrics.filter(
|
||||||
(m) => m?.name == selectedMetric && m.scope == "node",
|
(m) => m?.name == selectedMetric && m.scope == "node",
|
||||||
@@ -125,6 +147,7 @@
|
|||||||
{#key selectedMetric}
|
{#key selectedMetric}
|
||||||
{#each filteredData as item (item.host)}
|
{#each filteredData as item (item.host)}
|
||||||
<Col class="px-1">
|
<Col class="px-1">
|
||||||
|
<div class="d-flex align-items-baseline">
|
||||||
<h4 style="width: 100%; text-align: center;">
|
<h4 style="width: 100%; text-align: center;">
|
||||||
<a
|
<a
|
||||||
style="display: block;padding-top: 15px;"
|
style="display: block;padding-top: 15px;"
|
||||||
@@ -132,6 +155,10 @@
|
|||||||
>{item.host} ({item.subCluster})</a
|
>{item.host} ({item.subCluster})</a
|
||||||
>
|
>
|
||||||
</h4>
|
</h4>
|
||||||
|
<span style="margin-right: 0.5rem;">
|
||||||
|
<Badge color={stateColors[item?.state? item.state : 'notindb']}>{item?.state? item.state : 'notindb'}</Badge>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{#if item.disabled === true}
|
{#if item.disabled === true}
|
||||||
<Card body class="mx-3" color="info"
|
<Card body class="mx-3" color="info"
|
||||||
>Metric disabled for subcluster <code
|
>Metric disabled for subcluster <code
|
||||||
@@ -149,6 +176,7 @@
|
|||||||
{cluster}
|
{cluster}
|
||||||
subCluster={item.subCluster}
|
subCluster={item.subCluster}
|
||||||
forNode
|
forNode
|
||||||
|
enableFlip
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{:else if item.disabled === null}
|
{:else if item.disabled === null}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
Button,
|
Button,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardBody,
|
CardBody,
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
cluster,
|
cluster,
|
||||||
subCluster,
|
subCluster,
|
||||||
hostname,
|
hostname,
|
||||||
|
hoststate,
|
||||||
dataHealth,
|
dataHealth,
|
||||||
nodeJobsData = null,
|
nodeJobsData = null,
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -39,6 +42,16 @@
|
|||||||
const healthWarn = !dataHealth.includes(true);
|
const healthWarn = !dataHealth.includes(true);
|
||||||
// At least one non-returned selected metric: Metric config error?
|
// At least one non-returned selected metric: Metric config error?
|
||||||
const metricWarn = dataHealth.includes(false);
|
const metricWarn = dataHealth.includes(false);
|
||||||
|
// Node State Colors
|
||||||
|
const stateColors = {
|
||||||
|
allocated: 'success',
|
||||||
|
reserved: 'info',
|
||||||
|
idle: 'primary',
|
||||||
|
mixed: 'warning',
|
||||||
|
down: 'danger',
|
||||||
|
unknown: 'dark',
|
||||||
|
notindb: 'secondary'
|
||||||
|
}
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
const userList = $derived(nodeJobsData
|
const userList = $derived(nodeJobsData
|
||||||
@@ -68,80 +81,72 @@
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
<Row cols={{xs: 1, lg: 2}}>
|
||||||
|
<Col class="mb-2 mb-lg-0">
|
||||||
|
<InputGroup size="sm">
|
||||||
{#if healthWarn}
|
{#if healthWarn}
|
||||||
<InputGroup>
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<InputGroupText>
|
<Icon name="exclamation-circle" style="padding-right: 0.5rem;"/>
|
||||||
<Icon name="exclamation-circle"/>
|
<span>Jobs</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<InputGroupText>
|
<Button class="flex-grow-1" color="danger" disabled>
|
||||||
Status
|
No Metrics
|
||||||
</InputGroupText>
|
|
||||||
<Button color="danger" disabled>
|
|
||||||
Unhealthy
|
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
|
||||||
{:else if metricWarn}
|
{:else if metricWarn}
|
||||||
<InputGroup>
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<InputGroupText>
|
<Icon name="info-circle" style="padding-right: 0.5rem;"/>
|
||||||
<Icon name="info-circle"/>
|
<span>Jobs</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<InputGroupText>
|
<Button class="flex-grow-1" color="warning" disabled>
|
||||||
Status
|
|
||||||
</InputGroupText>
|
|
||||||
<Button color="warning" disabled>
|
|
||||||
Missing Metric
|
Missing Metric
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
|
||||||
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData.jobs.items[0].shared == "none"}
|
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData.jobs.items[0].shared == "none"}
|
||||||
<InputGroup>
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<InputGroupText>
|
<Icon name="circle-fill" style="padding-right: 0.5rem;"/>
|
||||||
<Icon name="circle-fill"/>
|
<span>Jobs</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<InputGroupText>
|
<Button class="flex-grow-1" color="success" disabled>
|
||||||
Status
|
|
||||||
</InputGroupText>
|
|
||||||
<Button color="success" disabled>
|
|
||||||
Exclusive
|
Exclusive
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
|
||||||
{:else if nodeJobsData.jobs.count >= 1 && !(nodeJobsData.jobs.items[0].shared == "none")}
|
{:else if nodeJobsData.jobs.count >= 1 && !(nodeJobsData.jobs.items[0].shared == "none")}
|
||||||
<InputGroup>
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<InputGroupText>
|
<Icon name="circle-half" style="padding-right: 0.5rem;"/>
|
||||||
<Icon name="circle-half"/>
|
<span>Jobs</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<InputGroupText>
|
<Button class="flex-grow-1" color="success" disabled>
|
||||||
Status
|
|
||||||
</InputGroupText>
|
|
||||||
<Button color="success" disabled>
|
|
||||||
Shared
|
Shared
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
|
||||||
<!-- Fallback -->
|
<!-- Fallback -->
|
||||||
{:else if nodeJobsData.jobs.count >= 1}
|
{:else if nodeJobsData.jobs.count >= 1}
|
||||||
<InputGroup>
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<InputGroupText>
|
<Icon name="circle-fill" style="padding-right: 0.5rem;"/>
|
||||||
<Icon name="circle-fill"/>
|
<span>Jobs</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<InputGroupText>
|
<Button class="flex-grow-1" color="success" disabled>
|
||||||
Status
|
Running
|
||||||
</InputGroupText>
|
|
||||||
<Button color="success" disabled>
|
|
||||||
Allocated Jobs
|
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
|
||||||
{:else}
|
{:else}
|
||||||
<InputGroup>
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<InputGroupText>
|
<Icon name="circle" style="padding-right: 0.5rem;"/>
|
||||||
<Icon name="circle"/>
|
<span>Jobs</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<InputGroupText>
|
<Button class="flex-grow-1" color="secondary" disabled>
|
||||||
Status
|
None
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<InputGroup size="sm">
|
||||||
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
|
State
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Button color="secondary" disabled>
|
<Button class="flex-grow-1" color={stateColors[hoststate]} disabled>
|
||||||
Idle
|
{hoststate.charAt(0).toUpperCase() + hoststate.slice(1)}
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
{/if}
|
</Col>
|
||||||
|
</Row>
|
||||||
<hr class="my-3"/>
|
<hr class="my-3"/>
|
||||||
<!-- JOBS -->
|
<!-- JOBS -->
|
||||||
<InputGroup size="sm" class="justify-content-between mb-3">
|
<InputGroup size="sm" class="justify-content-between mb-3">
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
gql,
|
gql,
|
||||||
getContextClient,
|
getContextClient,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
|
import uPlot from "uplot";
|
||||||
import { Card, CardBody, Spinner } from "@sveltestrap/sveltestrap";
|
import { Card, CardBody, Spinner } from "@sveltestrap/sveltestrap";
|
||||||
import { maxScope, checkMetricDisabled, scramble, scrambleNames } from "../../generic/utils.js";
|
import { maxScope, checkMetricDisabled, scramble, scrambleNames } from "../../generic/utils.js";
|
||||||
import MetricPlot from "../../generic/plots/MetricPlot.svelte";
|
import MetricPlot from "../../generic/plots/MetricPlot.svelte";
|
||||||
@@ -25,6 +26,9 @@
|
|||||||
selectedMetrics,
|
selectedMetrics,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
/* Var Init*/
|
||||||
|
let plotSync = uPlot.sync(`nodeMetricStack-${nodeData.host}`);
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
const paging = { itemsPerPage: 50, page: 1 };
|
const paging = { itemsPerPage: 50, page: 1 };
|
||||||
@@ -135,7 +139,13 @@
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
{:else}
|
{:else}
|
||||||
<NodeInfo nodeJobsData={$nodeJobsData.data} {cluster} subCluster={nodeData.subCluster} hostname={nodeData.host} {dataHealth}/>
|
<NodeInfo
|
||||||
|
{cluster}
|
||||||
|
{dataHealth}
|
||||||
|
nodeJobsData={$nodeJobsData.data}
|
||||||
|
subCluster={nodeData.subCluster}
|
||||||
|
hostname={nodeData.host}
|
||||||
|
hoststate={nodeData?.state? nodeData.state: 'notindb'}/>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
{#each refinedData as metricData (metricData.data.name)}
|
{#each refinedData as metricData (metricData.data.name)}
|
||||||
@@ -159,6 +169,7 @@
|
|||||||
statisticsSeries={metricData.data?.metric.statisticsSeries}
|
statisticsSeries={metricData.data?.metric.statisticsSeries}
|
||||||
useStatsSeries={!!metricData.data?.metric.statisticsSeries}
|
useStatsSeries={!!metricData.data?.metric.statisticsSeries}
|
||||||
height={175}
|
height={175}
|
||||||
|
{plotSync}
|
||||||
forNode
|
forNode
|
||||||
/>
|
/>
|
||||||
<div class="my-2"></div>
|
<div class="my-2"></div>
|
||||||
@@ -172,6 +183,7 @@
|
|||||||
series={metricData.data.metric.series}
|
series={metricData.data.metric.series}
|
||||||
height={175}
|
height={175}
|
||||||
{extendedLegendData}
|
{extendedLegendData}
|
||||||
|
{plotSync}
|
||||||
forNode
|
forNode
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|||||||
Reference in New Issue
Block a user