mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-08-31 00:47:15 +02:00
Merge branch 'feat/565-add-metric-tooltip' into metric-store-tickets
Resolve conflicts in the generated GraphQL code by regenerating it against
the merged schema. cc-lib v2.13.0 adds Tooltip to schema.MetricConfig and
schema.GlobalMetricListItem, so gqlgen now binds the tooltip field directly
and the hand-written globalMetricListItem/metricConfig resolvers introduced
on the tooltip branch are no longer needed.
Also migrate to the cc-lib v2.13.0 metric container types, which changed
from bare maps to structs carrying array-valued metric groups:
schema.JobData map -> {Metrics, Groups}
schema.ScopedJobStats map -> {Metrics, Groups}
job.Statistics map -> schema.JobStatisticsSet{Metrics, Groups}
Callers index .Metrics, return the zero struct instead of nil, and
deepCopy/DecodeJobStats now also carry the Groups payload through.
archive.GetStatistics returns the full JobStatisticsSet so group
statistics survive the round trip.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,7 @@ type MetricConfig {
|
|||||||
alert: Float!
|
alert: Float!
|
||||||
lowerIsBetter: Boolean
|
lowerIsBetter: Boolean
|
||||||
subClusters: [SubClusterConfig!]!
|
subClusters: [SubClusterConfig!]!
|
||||||
|
tooltip: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tag {
|
type Tag {
|
||||||
@@ -306,6 +307,7 @@ type GlobalMetricListItem {
|
|||||||
unit: Unit!
|
unit: Unit!
|
||||||
scope: MetricScope!
|
scope: MetricScope!
|
||||||
footprint: String
|
footprint: String
|
||||||
|
tooltip: String
|
||||||
availability: [ClusterSupport!]!
|
availability: [ClusterSupport!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ func cleanup() {
|
|||||||
func TestRestApi(t *testing.T) {
|
func TestRestApi(t *testing.T) {
|
||||||
restapi := setup(t)
|
restapi := setup(t)
|
||||||
t.Cleanup(cleanup)
|
t.Cleanup(cleanup)
|
||||||
testData := schema.JobData{
|
testData := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"load_one": map[schema.MetricScope]*schema.JobMetric{
|
"load_one": {
|
||||||
schema.MetricScopeNode: {
|
schema.MetricScopeNode: {
|
||||||
Unit: schema.Unit{Base: "load"},
|
Unit: schema.Unit{Base: "load"},
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -210,7 +210,7 @@ func TestRestApi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
metricstore.TestLoadDataCallback = func(job *schema.Job, metrics []string, scopes []schema.MetricScope, ctx context.Context, resolution int) (schema.JobData, error) {
|
metricstore.TestLoadDataCallback = func(job *schema.Job, metrics []string, scopes []schema.MetricScope, ctx context.Context, resolution int) (schema.JobData, error) {
|
||||||
return testData, nil
|
return testData, nil
|
||||||
@@ -497,8 +497,8 @@ func TestStopJobWithReusedJobId(t *testing.T) {
|
|||||||
restapi := setup(t)
|
restapi := setup(t)
|
||||||
t.Cleanup(cleanup)
|
t.Cleanup(cleanup)
|
||||||
|
|
||||||
testData := schema.JobData{
|
testData := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"load_one": map[schema.MetricScope]*schema.JobMetric{
|
"load_one": {
|
||||||
schema.MetricScopeNode: {
|
schema.MetricScopeNode: {
|
||||||
Unit: schema.Unit{Base: "load"},
|
Unit: schema.Unit{Base: "load"},
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -511,7 +511,7 @@ func TestStopJobWithReusedJobId(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
metricstore.TestLoadDataCallback = func(job *schema.Job, metrics []string, scopes []schema.MetricScope, ctx context.Context, resolution int) (schema.JobData, error) {
|
metricstore.TestLoadDataCallback = func(job *schema.Job, metrics []string, scopes []schema.MetricScope, ctx context.Context, resolution int) (schema.JobData, error) {
|
||||||
return testData, nil
|
return testData, nil
|
||||||
|
|||||||
+1
-1
@@ -412,7 +412,7 @@ func (api *RestAPI) getJobByID(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := []*JobMetricWithName{}
|
res := []*JobMetricWithName{}
|
||||||
for name, md := range data {
|
for name, md := range data.Metrics {
|
||||||
for scope, metric := range md {
|
for scope, metric := range md {
|
||||||
res = append(res, &JobMetricWithName{
|
res = append(res, &JobMetricWithName{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -531,8 +531,8 @@ func TestNatsHandleStopJob(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testData := schema.JobData{
|
testData := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"load_one": map[schema.MetricScope]*schema.JobMetric{
|
"load_one": {
|
||||||
schema.MetricScopeNode: {
|
schema.MetricScopeNode: {
|
||||||
Unit: schema.Unit{Base: "load"},
|
Unit: schema.Unit{Base: "load"},
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -545,7 +545,7 @@ func TestNatsHandleStopJob(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
metricstore.TestLoadDataCallback = func(job *schema.Job, metrics []string, scopes []schema.MetricScope, ctx context.Context, resolution int) (schema.JobData, error) {
|
metricstore.TestLoadDataCallback = func(job *schema.Job, metrics []string, scopes []schema.MetricScope, ctx context.Context, resolution int) (schema.JobData, error) {
|
||||||
return testData, nil
|
return testData, nil
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.Job, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Statistics = make(map[string]schema.JobStatistics)
|
job.Statistics = schema.JobStatisticsSet{Metrics: make(map[string]schema.JobStatistics, len(jobData.Metrics))}
|
||||||
|
|
||||||
for metric, data := range jobData {
|
for metric, data := range jobData.Metrics {
|
||||||
avg, min, max := 0.0, math.MaxFloat32, -math.MaxFloat32
|
avg, min, max := 0.0, math.MaxFloat32, -math.MaxFloat32
|
||||||
nodeData, ok := data["node"]
|
nodeData, ok := data["node"]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -82,7 +82,7 @@ func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.Job, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Round AVG Result to 2 Digits
|
// Round AVG Result to 2 Digits
|
||||||
job.Statistics[metric] = schema.JobStatistics{
|
job.Statistics.Metrics[metric] = schema.JobStatistics{
|
||||||
Unit: schema.Unit{
|
Unit: schema.Unit{
|
||||||
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
||||||
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ type ComplexityRoot struct {
|
|||||||
Footprint func(childComplexity int) int
|
Footprint func(childComplexity int) int
|
||||||
Name func(childComplexity int) int
|
Name func(childComplexity int) int
|
||||||
Scope func(childComplexity int) int
|
Scope func(childComplexity int) int
|
||||||
|
Tooltip func(childComplexity int) int
|
||||||
Unit func(childComplexity int) int
|
Unit func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +220,7 @@ type ComplexityRoot struct {
|
|||||||
Scope func(childComplexity int) int
|
Scope func(childComplexity int) int
|
||||||
SubClusters func(childComplexity int) int
|
SubClusters func(childComplexity int) int
|
||||||
Timestep func(childComplexity int) int
|
Timestep func(childComplexity int) int
|
||||||
|
Tooltip func(childComplexity int) int
|
||||||
Unit func(childComplexity int) int
|
Unit func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,6 +700,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.ComplexityRoot.GlobalMetricListItem.Scope(childComplexity), true
|
return e.ComplexityRoot.GlobalMetricListItem.Scope(childComplexity), true
|
||||||
|
case "GlobalMetricListItem.tooltip":
|
||||||
|
if e.ComplexityRoot.GlobalMetricListItem.Tooltip == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.ComplexityRoot.GlobalMetricListItem.Tooltip(childComplexity), true
|
||||||
case "GlobalMetricListItem.unit":
|
case "GlobalMetricListItem.unit":
|
||||||
if e.ComplexityRoot.GlobalMetricListItem.Unit == nil {
|
if e.ComplexityRoot.GlobalMetricListItem.Unit == nil {
|
||||||
break
|
break
|
||||||
@@ -1225,6 +1233,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.ComplexityRoot.MetricConfig.Timestep(childComplexity), true
|
return e.ComplexityRoot.MetricConfig.Timestep(childComplexity), true
|
||||||
|
case "MetricConfig.tooltip":
|
||||||
|
if e.ComplexityRoot.MetricConfig.Tooltip == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.ComplexityRoot.MetricConfig.Tooltip(childComplexity), true
|
||||||
case "MetricConfig.unit":
|
case "MetricConfig.unit":
|
||||||
if e.ComplexityRoot.MetricConfig.Unit == nil {
|
if e.ComplexityRoot.MetricConfig.Unit == nil {
|
||||||
break
|
break
|
||||||
@@ -2426,6 +2440,7 @@ type MetricConfig {
|
|||||||
alert: Float!
|
alert: Float!
|
||||||
lowerIsBetter: Boolean
|
lowerIsBetter: Boolean
|
||||||
subClusters: [SubClusterConfig!]!
|
subClusters: [SubClusterConfig!]!
|
||||||
|
tooltip: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tag {
|
type Tag {
|
||||||
@@ -2589,6 +2604,7 @@ type GlobalMetricListItem {
|
|||||||
unit: Unit!
|
unit: Unit!
|
||||||
scope: MetricScope!
|
scope: MetricScope!
|
||||||
footprint: String
|
footprint: String
|
||||||
|
tooltip: String
|
||||||
availability: [ClusterSupport!]!
|
availability: [ClusterSupport!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2973,6 +2989,8 @@ func (ec *executionContext) childFields_GlobalMetricListItem(ctx context.Context
|
|||||||
return ec.fieldContext_GlobalMetricListItem_scope(ctx, field)
|
return ec.fieldContext_GlobalMetricListItem_scope(ctx, field)
|
||||||
case "footprint":
|
case "footprint":
|
||||||
return ec.fieldContext_GlobalMetricListItem_footprint(ctx, field)
|
return ec.fieldContext_GlobalMetricListItem_footprint(ctx, field)
|
||||||
|
case "tooltip":
|
||||||
|
return ec.fieldContext_GlobalMetricListItem_tooltip(ctx, field)
|
||||||
case "availability":
|
case "availability":
|
||||||
return ec.fieldContext_GlobalMetricListItem_availability(ctx, field)
|
return ec.fieldContext_GlobalMetricListItem_availability(ctx, field)
|
||||||
}
|
}
|
||||||
@@ -3203,6 +3221,8 @@ func (ec *executionContext) childFields_MetricConfig(ctx context.Context, field
|
|||||||
return ec.fieldContext_MetricConfig_lowerIsBetter(ctx, field)
|
return ec.fieldContext_MetricConfig_lowerIsBetter(ctx, field)
|
||||||
case "subClusters":
|
case "subClusters":
|
||||||
return ec.fieldContext_MetricConfig_subClusters(ctx, field)
|
return ec.fieldContext_MetricConfig_subClusters(ctx, field)
|
||||||
|
case "tooltip":
|
||||||
|
return ec.fieldContext_MetricConfig_tooltip(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type MetricConfig", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type MetricConfig", field.Name)
|
||||||
}
|
}
|
||||||
@@ -5187,6 +5207,29 @@ func (ec *executionContext) fieldContext_GlobalMetricListItem_footprint(_ contex
|
|||||||
return graphql.NewScalarFieldContext("GlobalMetricListItem", field, false, false, errors.New("field of type String does not have child fields"))
|
return graphql.NewScalarFieldContext("GlobalMetricListItem", field, false, false, errors.New("field of type String does not have child fields"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _GlobalMetricListItem_tooltip(ctx context.Context, field graphql.CollectedField, obj *schema.GlobalMetricListItem) (ret graphql.Marshaler) {
|
||||||
|
return graphql.ResolveField(
|
||||||
|
ctx,
|
||||||
|
ec.OperationContext,
|
||||||
|
field,
|
||||||
|
func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return ec.fieldContext_GlobalMetricListItem_tooltip(ctx, field)
|
||||||
|
},
|
||||||
|
func(ctx context.Context) (any, error) {
|
||||||
|
return obj.Tooltip, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler {
|
||||||
|
return ec.marshalOString2string(ctx, selections, v)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func (ec *executionContext) fieldContext_GlobalMetricListItem_tooltip(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
return graphql.NewScalarFieldContext("GlobalMetricListItem", field, false, false, errors.New("field of type String does not have child fields"))
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _GlobalMetricListItem_availability(ctx context.Context, field graphql.CollectedField, obj *schema.GlobalMetricListItem) (ret graphql.Marshaler) {
|
func (ec *executionContext) _GlobalMetricListItem_availability(ctx context.Context, field graphql.CollectedField, obj *schema.GlobalMetricListItem) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -7377,6 +7420,29 @@ func (ec *executionContext) fieldContext_MetricConfig_subClusters(_ context.Cont
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _MetricConfig_tooltip(ctx context.Context, field graphql.CollectedField, obj *schema.MetricConfig) (ret graphql.Marshaler) {
|
||||||
|
return graphql.ResolveField(
|
||||||
|
ctx,
|
||||||
|
ec.OperationContext,
|
||||||
|
field,
|
||||||
|
func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return ec.fieldContext_MetricConfig_tooltip(ctx, field)
|
||||||
|
},
|
||||||
|
func(ctx context.Context) (any, error) {
|
||||||
|
return obj.Tooltip, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler {
|
||||||
|
return ec.marshalOString2string(ctx, selections, v)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func (ec *executionContext) fieldContext_MetricConfig_tooltip(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
return graphql.NewScalarFieldContext("MetricConfig", field, false, false, errors.New("field of type String does not have child fields"))
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _MetricFootprints_metric(ctx context.Context, field graphql.CollectedField, obj *model.MetricFootprints) (ret graphql.Marshaler) {
|
func (ec *executionContext) _MetricFootprints_metric(ctx context.Context, field graphql.CollectedField, obj *model.MetricFootprints) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -13311,6 +13377,11 @@ func (ec *executionContext) _GlobalMetricListItem(ctx context.Context, sel ast.S
|
|||||||
if out.Values[i] == graphql.RequiredNull {
|
if out.Values[i] == graphql.RequiredNull {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
|
case "tooltip":
|
||||||
|
out.Values[i] = ec._GlobalMetricListItem_tooltip(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.RequiredNull {
|
||||||
|
out.Invalids++
|
||||||
|
}
|
||||||
case "availability":
|
case "availability":
|
||||||
out.Values[i] = ec._GlobalMetricListItem_availability(ctx, field, obj)
|
out.Values[i] = ec._GlobalMetricListItem_availability(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
@@ -14340,6 +14411,11 @@ func (ec *executionContext) _MetricConfig(ctx context.Context, sel ast.Selection
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
|
case "tooltip":
|
||||||
|
out.Values[i] = ec._MetricConfig_tooltip(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.RequiredNull {
|
||||||
|
out.Invalids++
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ func (r *queryResolver) JobMetrics(ctx context.Context, id string, metrics []str
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := []*model.JobMetricWithName{}
|
res := []*model.JobMetricWithName{}
|
||||||
for name, md := range data {
|
for name, md := range data.Metrics {
|
||||||
for scope, metric := range md {
|
for scope, metric := range md {
|
||||||
res = append(res, &model.JobMetricWithName{
|
res = append(res, &model.JobMetricWithName{
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -581,7 +581,7 @@ func (r *queryResolver) ScopedJobStats(ctx context.Context, id string, metrics [
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := make([]*model.NamedStatsWithScope, 0)
|
res := make([]*model.NamedStatsWithScope, 0)
|
||||||
for name, scoped := range data {
|
for name, scoped := range data.Metrics {
|
||||||
for scope, stats := range scoped {
|
for scope, stats := range scoped {
|
||||||
|
|
||||||
mdlStats := make([]*model.ScopedStats, 0)
|
mdlStats := make([]*model.ScopedStats, 0)
|
||||||
@@ -939,7 +939,7 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub
|
|||||||
cclog.Warnf("error in nodeMetrics resolver: %s", err)
|
cclog.Warnf("error in nodeMetrics resolver: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for metric, scopedMetrics := range data[hostname] {
|
for metric, scopedMetrics := range data[hostname].Metrics {
|
||||||
for scope, scopedMetric := range scopedMetrics {
|
for scope, scopedMetric := range scopedMetrics {
|
||||||
host.Metrics = append(host.Metrics, &model.JobMetricWithName{
|
host.Metrics = append(host.Metrics, &model.JobMetricWithName{
|
||||||
Name: metric,
|
Name: metric,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (r *queryResolver) rooflineHeatmap(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
flops_, membw_ := jobdata["flops_any"], jobdata["mem_bw"]
|
flops_, membw_ := jobdata.Metrics["flops_any"], jobdata.Metrics["mem_bw"]
|
||||||
if flops_ == nil && membw_ == nil {
|
if flops_ == nil && membw_ == nil {
|
||||||
cclog.Warnf("rooflineHeatmap(): 'flops_any' or 'mem_bw' missing for job %d", *job.ID)
|
cclog.Warnf("rooflineHeatmap(): 'flops_any' or 'mem_bw' missing for job %d", *job.ID)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ func SanityChecks(job *schema.Job) error {
|
|||||||
//
|
//
|
||||||
// TODO: Either implement the metric normalization or remove this dead code.
|
// TODO: Either implement the metric normalization or remove this dead code.
|
||||||
func checkJobData(d *schema.JobData) error {
|
func checkJobData(d *schema.JobData) error {
|
||||||
for _, scopes := range *d {
|
for _, scopes := range d.Metrics {
|
||||||
// var newUnit schema.Unit
|
// var newUnit schema.Unit
|
||||||
// TODO Add node scope if missing
|
// TODO Add node scope if missing
|
||||||
for _, metric := range scopes {
|
for _, metric := range scopes {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func LoadData(job *schema.Job,
|
|||||||
|
|
||||||
jd, err = ms.LoadData(job, metrics, scopes, ctx, resolution)
|
jd, err = ms.LoadData(job, metrics, scopes, ctx, resolution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(jd) != 0 {
|
if len(jd.Metrics) != 0 {
|
||||||
cclog.Warnf("partial error loading metrics from store for job %d (user: %s, project: %s, cluster: %s-%s): %s",
|
cclog.Warnf("partial error loading metrics from store for job %d (user: %s, project: %s, cluster: %s-%s): %s",
|
||||||
job.JobID, job.User, job.Project, job.Cluster, job.SubCluster, err.Error())
|
job.JobID, job.User, job.Project, job.Cluster, job.SubCluster, err.Error())
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +144,7 @@ func LoadData(job *schema.Job,
|
|||||||
if rfErr != nil {
|
if rfErr != nil {
|
||||||
return rfErr, 0, 0
|
return rfErr, 0, 0
|
||||||
}
|
}
|
||||||
for _, v := range jd {
|
for _, v := range jd.Metrics {
|
||||||
for _, v_ := range v {
|
for _, v_ := range v {
|
||||||
timestep := int64(0)
|
timestep := int64(0)
|
||||||
for i := 0; i < len(v_.Series); i += 1 {
|
for i := 0; i < len(v_.Series); i += 1 {
|
||||||
@@ -160,17 +160,20 @@ func LoadData(job *schema.Job,
|
|||||||
// Filter job data to only include requested metrics and scopes, avoiding unnecessary data transfer.
|
// Filter job data to only include requested metrics and scopes, avoiding unnecessary data transfer.
|
||||||
if metrics != nil || scopes != nil {
|
if metrics != nil || scopes != nil {
|
||||||
if metrics == nil {
|
if metrics == nil {
|
||||||
metrics = make([]string, 0, len(jd))
|
metrics = make([]string, 0, len(jd.Metrics))
|
||||||
for k := range jd {
|
for k := range jd.Metrics {
|
||||||
metrics = append(metrics, k)
|
metrics = append(metrics, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := schema.JobData{}
|
res := schema.JobData{
|
||||||
|
Metrics: make(map[string]schema.ScopedMetrics, len(metrics)),
|
||||||
|
Groups: jd.Groups,
|
||||||
|
}
|
||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
if perscope, ok := jd[metric]; ok {
|
if perscope, ok := jd.Metrics[metric]; ok {
|
||||||
if len(perscope) > 1 {
|
if len(perscope) > 1 {
|
||||||
subset := make(map[schema.MetricScope]*schema.JobMetric)
|
subset := make(schema.ScopedMetrics)
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
if jm, ok := perscope[scope]; ok {
|
if jm, ok := perscope[scope]; ok {
|
||||||
subset[scope] = jm
|
subset[scope] = jm
|
||||||
@@ -182,7 +185,7 @@ func LoadData(job *schema.Job,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res[metric] = perscope
|
res.Metrics[metric] = perscope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jd = res
|
jd = res
|
||||||
@@ -199,7 +202,7 @@ func LoadData(job *schema.Job,
|
|||||||
// instead of overwhelming the UI with individual node lines. Note that newly calculated
|
// instead of overwhelming the UI with individual node lines. Note that newly calculated
|
||||||
// statistics use min/median/max, while archived statistics may use min/mean/max.
|
// statistics use min/median/max, while archived statistics may use min/mean/max.
|
||||||
const maxSeriesSize int = 8
|
const maxSeriesSize int = 8
|
||||||
for _, scopes := range jd {
|
for _, scopes := range jd.Metrics {
|
||||||
for _, jm := range scopes {
|
for _, jm := range scopes {
|
||||||
if jm.StatisticsSeries != nil || len(jm.Series) < maxSeriesSize {
|
if jm.StatisticsSeries != nil || len(jm.Series) < maxSeriesSize {
|
||||||
continue
|
continue
|
||||||
@@ -229,7 +232,7 @@ func LoadData(job *schema.Job,
|
|||||||
|
|
||||||
if err, ok := data.(error); ok {
|
if err, ok := data.(error); ok {
|
||||||
cclog.Errorf("error in cached dataset for job %d: %s", job.JobID, err.Error())
|
cclog.Errorf("error in cached dataset for job %d: %s", job.JobID, err.Error())
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.(schema.JobData), nil
|
return data.(schema.JobData), nil
|
||||||
@@ -296,14 +299,14 @@ func LoadScopedJobStats(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("failed to access metricDataRepo for cluster %s-%s: %s",
|
cclog.Errorf("failed to access metricDataRepo for cluster %s-%s: %s",
|
||||||
job.Cluster, job.SubCluster, err.Error())
|
job.Cluster, job.SubCluster, err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scopedStats, err := ms.LoadScopedStats(job, metrics, scopes, ctx)
|
scopedStats, err := ms.LoadScopedStats(job, metrics, scopes, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Warnf("failed to load scoped statistics from metric store for job %d (user: %s, project: %s, cluster: %s-%s): %s",
|
cclog.Warnf("failed to load scoped statistics from metric store for job %d (user: %s, project: %s, cluster: %s-%s): %s",
|
||||||
job.JobID, job.User, job.Project, job.Cluster, job.SubCluster, err.Error())
|
job.JobID, job.User, job.Project, job.Cluster, job.SubCluster, err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round Resulting Stat Values
|
// Round Resulting Stat Values
|
||||||
@@ -451,7 +454,7 @@ func LoadNodeListData(
|
|||||||
// Statistics are calculated as min/median/max.
|
// Statistics are calculated as min/median/max.
|
||||||
const maxSeriesSize int = 8
|
const maxSeriesSize int = 8
|
||||||
for _, jd := range data {
|
for _, jd := range data {
|
||||||
for _, scopes := range jd {
|
for _, scopes := range jd.Metrics {
|
||||||
for _, jm := range scopes {
|
for _, jm := range scopes {
|
||||||
if jm.StatisticsSeries != nil || len(jm.Series) < maxSeriesSize {
|
if jm.StatisticsSeries != nil || len(jm.Series) < maxSeriesSize {
|
||||||
continue
|
continue
|
||||||
@@ -472,14 +475,32 @@ func LoadNodeListData(
|
|||||||
// archived data (e.g., during resampling). This ensures the cached archive data remains
|
// archived data (e.g., during resampling). This ensures the cached archive data remains
|
||||||
// immutable while allowing per-request transformations.
|
// immutable while allowing per-request transformations.
|
||||||
func deepCopy(source schema.JobData) schema.JobData {
|
func deepCopy(source schema.JobData) schema.JobData {
|
||||||
result := make(schema.JobData, len(source))
|
result := schema.JobData{Metrics: copyScopedMetrics(source.Metrics)}
|
||||||
|
|
||||||
|
for _, group := range source.Groups {
|
||||||
|
copied := schema.MetricGroup{Key: group.Key}
|
||||||
|
for _, inst := range group.Instances {
|
||||||
|
copied.Instances = append(copied.Instances, schema.MetricGroupInstance{
|
||||||
|
Name: inst.Name,
|
||||||
|
Type: inst.Type,
|
||||||
|
Metrics: copyScopedMetrics(inst.Metrics),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result.Groups = append(result.Groups, copied)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyScopedMetrics(source map[string]schema.ScopedMetrics) map[string]schema.ScopedMetrics {
|
||||||
|
result := make(map[string]schema.ScopedMetrics, len(source))
|
||||||
|
|
||||||
for metricName, scopeMap := range source {
|
for metricName, scopeMap := range source {
|
||||||
result[metricName] = make(map[schema.MetricScope]*schema.JobMetric, len(scopeMap))
|
scopes := make(schema.ScopedMetrics, len(scopeMap))
|
||||||
|
|
||||||
for scope, jobMetric := range scopeMap {
|
for scope, jobMetric := range scopeMap {
|
||||||
result[metricName][scope] = copyJobMetric(jobMetric)
|
scopes[scope] = copyJobMetric(jobMetric)
|
||||||
}
|
}
|
||||||
|
result[metricName] = scopes
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestDeepCopy(t *testing.T) {
|
func TestDeepCopy(t *testing.T) {
|
||||||
nodeId := "0"
|
nodeId := "0"
|
||||||
original := schema.JobData{
|
original := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"cpu_load": {
|
"cpu_load": {
|
||||||
schema.MetricScopeNode: &schema.JobMetric{
|
schema.MetricScopeNode: &schema.JobMetric{
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -42,42 +42,42 @@ func TestDeepCopy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
copied := deepCopy(original)
|
copied := deepCopy(original)
|
||||||
|
|
||||||
original["cpu_load"][schema.MetricScopeNode].Series[0].Data[0] = 999.0
|
original.Metrics["cpu_load"][schema.MetricScopeNode].Series[0].Data[0] = 999.0
|
||||||
original["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0] = 888.0
|
original.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0] = 888.0
|
||||||
original["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles[25][0] = 777.0
|
original.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles[25][0] = 777.0
|
||||||
|
|
||||||
if copied["cpu_load"][schema.MetricScopeNode].Series[0].Data[0] != 1.0 {
|
if copied.Metrics["cpu_load"][schema.MetricScopeNode].Series[0].Data[0] != 1.0 {
|
||||||
t.Errorf("Series data was not deeply copied: got %v, want 1.0",
|
t.Errorf("Series data was not deeply copied: got %v, want 1.0",
|
||||||
copied["cpu_load"][schema.MetricScopeNode].Series[0].Data[0])
|
copied.Metrics["cpu_load"][schema.MetricScopeNode].Series[0].Data[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if copied["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0] != 1.0 {
|
if copied.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0] != 1.0 {
|
||||||
t.Errorf("StatisticsSeries was not deeply copied: got %v, want 1.0",
|
t.Errorf("StatisticsSeries was not deeply copied: got %v, want 1.0",
|
||||||
copied["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0])
|
copied.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if copied["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles[25][0] != 1.5 {
|
if copied.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles[25][0] != 1.5 {
|
||||||
t.Errorf("Percentiles was not deeply copied: got %v, want 1.5",
|
t.Errorf("Percentiles was not deeply copied: got %v, want 1.5",
|
||||||
copied["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles[25][0])
|
copied.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles[25][0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if copied["cpu_load"][schema.MetricScopeNode].Timestep != 60 {
|
if copied.Metrics["cpu_load"][schema.MetricScopeNode].Timestep != 60 {
|
||||||
t.Errorf("Timestep not copied correctly: got %v, want 60",
|
t.Errorf("Timestep not copied correctly: got %v, want 60",
|
||||||
copied["cpu_load"][schema.MetricScopeNode].Timestep)
|
copied.Metrics["cpu_load"][schema.MetricScopeNode].Timestep)
|
||||||
}
|
}
|
||||||
|
|
||||||
if copied["cpu_load"][schema.MetricScopeNode].Series[0].Hostname != "node001" {
|
if copied.Metrics["cpu_load"][schema.MetricScopeNode].Series[0].Hostname != "node001" {
|
||||||
t.Errorf("Hostname not copied correctly: got %v, want node001",
|
t.Errorf("Hostname not copied correctly: got %v, want node001",
|
||||||
copied["cpu_load"][schema.MetricScopeNode].Series[0].Hostname)
|
copied.Metrics["cpu_load"][schema.MetricScopeNode].Series[0].Hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeepCopyNilStatisticsSeries(t *testing.T) {
|
func TestDeepCopyNilStatisticsSeries(t *testing.T) {
|
||||||
original := schema.JobData{
|
original := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"mem_used": {
|
"mem_used": {
|
||||||
schema.MetricScopeNode: &schema.JobMetric{
|
schema.MetricScopeNode: &schema.JobMetric{
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -90,18 +90,18 @@ func TestDeepCopyNilStatisticsSeries(t *testing.T) {
|
|||||||
StatisticsSeries: nil,
|
StatisticsSeries: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
copied := deepCopy(original)
|
copied := deepCopy(original)
|
||||||
|
|
||||||
if copied["mem_used"][schema.MetricScopeNode].StatisticsSeries != nil {
|
if copied.Metrics["mem_used"][schema.MetricScopeNode].StatisticsSeries != nil {
|
||||||
t.Errorf("StatisticsSeries should be nil, got %v",
|
t.Errorf("StatisticsSeries should be nil, got %v",
|
||||||
copied["mem_used"][schema.MetricScopeNode].StatisticsSeries)
|
copied.Metrics["mem_used"][schema.MetricScopeNode].StatisticsSeries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeepCopyEmptyPercentiles(t *testing.T) {
|
func TestDeepCopyEmptyPercentiles(t *testing.T) {
|
||||||
original := schema.JobData{
|
original := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"cpu_load": {
|
"cpu_load": {
|
||||||
schema.MetricScopeNode: &schema.JobMetric{
|
schema.MetricScopeNode: &schema.JobMetric{
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -115,11 +115,11 @@ func TestDeepCopyEmptyPercentiles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
copied := deepCopy(original)
|
copied := deepCopy(original)
|
||||||
|
|
||||||
if copied["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles != nil {
|
if copied.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Percentiles != nil {
|
||||||
t.Errorf("Percentiles should be nil when source is nil/empty")
|
t.Errorf("Percentiles should be nil when source is nil/empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ func (ccms *CCMetricStore) LoadData(
|
|||||||
queries, assignedScope, err := ccms.buildQueries(job, metrics, scopes, resolution)
|
queries, assignedScope, err := ccms.buildQueries(job, metrics, scopes, resolution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify assignment is correct - log any inconsistencies for debugging
|
// Verify assignment is correct - log any inconsistencies for debugging
|
||||||
@@ -256,11 +256,11 @@ func (ccms *CCMetricStore) LoadData(
|
|||||||
resBody, err := ccms.doRequest(ctx, &req)
|
resBody, err := ccms.doRequest(ctx, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while performing request for job %d: %s", job.JobID, err.Error())
|
cclog.Errorf("Error while performing request for job %d: %s", job.JobID, err.Error())
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errors []string
|
var errors []string
|
||||||
jobData := make(schema.JobData)
|
jobData := schema.JobData{Metrics: make(map[string]schema.ScopedMetrics)}
|
||||||
|
|
||||||
// Add safety check for potential index out of range errors
|
// Add safety check for potential index out of range errors
|
||||||
if len(resBody.Results) != len(req.Queries) || len(assignedScope) != len(req.Queries) {
|
if len(resBody.Results) != len(req.Queries) || len(assignedScope) != len(req.Queries) {
|
||||||
@@ -285,8 +285,8 @@ func (ccms *CCMetricStore) LoadData(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := jobData[metric]; !ok {
|
if _, ok := jobData.Metrics[metric]; !ok {
|
||||||
jobData[metric] = make(map[schema.MetricScope]*schema.JobMetric)
|
jobData.Metrics[metric] = make(schema.ScopedMetrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := mc.Timestep
|
res := mc.Timestep
|
||||||
@@ -294,14 +294,14 @@ func (ccms *CCMetricStore) LoadData(
|
|||||||
res = row[0].Resolution
|
res = row[0].Resolution
|
||||||
}
|
}
|
||||||
|
|
||||||
jobMetric, ok := jobData[metric][scope]
|
jobMetric, ok := jobData.Metrics[metric][scope]
|
||||||
if !ok {
|
if !ok {
|
||||||
jobMetric = &schema.JobMetric{
|
jobMetric = &schema.JobMetric{
|
||||||
Unit: mc.Unit,
|
Unit: mc.Unit,
|
||||||
Timestep: res,
|
Timestep: res,
|
||||||
Series: make([]schema.Series, 0),
|
Series: make([]schema.Series, 0),
|
||||||
}
|
}
|
||||||
jobData[metric][scope] = jobMetric
|
jobData.Metrics[metric][scope] = jobMetric
|
||||||
}
|
}
|
||||||
|
|
||||||
for ndx, res := range row {
|
for ndx, res := range row {
|
||||||
@@ -329,9 +329,9 @@ func (ccms *CCMetricStore) LoadData(
|
|||||||
|
|
||||||
// So that one can later check len(jobData):
|
// So that one can later check len(jobData):
|
||||||
if len(jobMetric.Series) == 0 {
|
if len(jobMetric.Series) == 0 {
|
||||||
delete(jobData[metric], scope)
|
delete(jobData.Metrics[metric], scope)
|
||||||
if len(jobData[metric]) == 0 {
|
if len(jobData.Metrics[metric]) == 0 {
|
||||||
delete(jobData, metric)
|
delete(jobData.Metrics, metric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,7 +426,7 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
|||||||
queries, assignedScope, err := ccms.buildQueries(job, metrics, scopes, 0)
|
queries, assignedScope, err := ccms.buildQueries(job, metrics, scopes, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := APIQueryRequest{
|
req := APIQueryRequest{
|
||||||
@@ -441,23 +441,23 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
|||||||
resBody, err := ccms.doRequest(ctx, &req)
|
resBody, err := ccms.doRequest(ctx, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while performing request for job %d: %s", job.JobID, err.Error())
|
cclog.Errorf("Error while performing request for job %d: %s", job.JobID, err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errors []string
|
var errors []string
|
||||||
scopedJobStats := make(schema.ScopedJobStats)
|
scopedJobStats := schema.ScopedJobStats{Metrics: make(map[string]schema.ScopedMetricStats)}
|
||||||
|
|
||||||
for i, row := range resBody.Results {
|
for i, row := range resBody.Results {
|
||||||
query := req.Queries[i]
|
query := req.Queries[i]
|
||||||
metric := query.Metric
|
metric := query.Metric
|
||||||
scope := assignedScope[i]
|
scope := assignedScope[i]
|
||||||
|
|
||||||
if _, ok := scopedJobStats[metric]; !ok {
|
if _, ok := scopedJobStats.Metrics[metric]; !ok {
|
||||||
scopedJobStats[metric] = make(map[schema.MetricScope][]*schema.ScopedStats)
|
scopedJobStats.Metrics[metric] = make(schema.ScopedMetricStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := scopedJobStats[metric][scope]; !ok {
|
if _, ok := scopedJobStats.Metrics[metric][scope]; !ok {
|
||||||
scopedJobStats[metric][scope] = make([]*schema.ScopedStats, 0)
|
scopedJobStats.Metrics[metric][scope] = make([]*schema.ScopedStats, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ndx, res := range row {
|
for ndx, res := range row {
|
||||||
@@ -471,7 +471,7 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
|||||||
|
|
||||||
ms.SanitizeStats(&res.Avg, &res.Min, &res.Max)
|
ms.SanitizeStats(&res.Avg, &res.Min, &res.Max)
|
||||||
|
|
||||||
scopedJobStats[metric][scope] = append(scopedJobStats[metric][scope], &schema.ScopedStats{
|
scopedJobStats.Metrics[metric][scope] = append(scopedJobStats.Metrics[metric][scope], &schema.ScopedStats{
|
||||||
Hostname: query.Hostname,
|
Hostname: query.Hostname,
|
||||||
ID: id,
|
ID: id,
|
||||||
Data: &schema.MetricStatistics{
|
Data: &schema.MetricStatistics{
|
||||||
@@ -483,10 +483,10 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// So that one can later check len(scopedJobStats[metric][scope]): Remove from map if empty
|
// So that one can later check len(scopedJobStats[metric][scope]): Remove from map if empty
|
||||||
if len(scopedJobStats[metric][scope]) == 0 {
|
if len(scopedJobStats.Metrics[metric][scope]) == 0 {
|
||||||
delete(scopedJobStats[metric], scope)
|
delete(scopedJobStats.Metrics[metric], scope)
|
||||||
if len(scopedJobStats[metric]) == 0 {
|
if len(scopedJobStats.Metrics[metric]) == 0 {
|
||||||
delete(scopedJobStats, metric)
|
delete(scopedJobStats.Metrics, metric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -691,14 +691,14 @@ func (ccms *CCMetricStore) LoadNodeListData(
|
|||||||
// Init Nested Map Data Structures If Not Found
|
// Init Nested Map Data Structures If Not Found
|
||||||
hostData, ok := data[query.Hostname]
|
hostData, ok := data[query.Hostname]
|
||||||
if !ok {
|
if !ok {
|
||||||
hostData = make(schema.JobData)
|
hostData = schema.JobData{Metrics: make(map[string]schema.ScopedMetrics)}
|
||||||
data[query.Hostname] = hostData
|
data[query.Hostname] = hostData
|
||||||
}
|
}
|
||||||
|
|
||||||
metricData, ok := hostData[metric]
|
metricData, ok := hostData.Metrics[metric]
|
||||||
if !ok {
|
if !ok {
|
||||||
metricData = make(map[schema.MetricScope]*schema.JobMetric)
|
metricData = make(schema.ScopedMetrics)
|
||||||
data[query.Hostname][metric] = metricData
|
hostData.Metrics[metric] = metricData
|
||||||
}
|
}
|
||||||
|
|
||||||
scopeData, ok := metricData[scope]
|
scopeData, ok := metricData[scope]
|
||||||
@@ -708,7 +708,7 @@ func (ccms *CCMetricStore) LoadNodeListData(
|
|||||||
Timestep: res,
|
Timestep: res,
|
||||||
Series: make([]schema.Series, 0),
|
Series: make([]schema.Series, 0),
|
||||||
}
|
}
|
||||||
data[query.Hostname][metric][scope] = scopeData
|
metricData[scope] = scopeData
|
||||||
}
|
}
|
||||||
|
|
||||||
for ndx, res := range row {
|
for ndx, res := range row {
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ func (r *JobRepository) JobsStats(
|
|||||||
//
|
//
|
||||||
// Returns the requested statistic value or 0.0 if not found.
|
// Returns the requested statistic value or 0.0 if not found.
|
||||||
func LoadJobStat(job *schema.Job, metric string, statType string) float64 {
|
func LoadJobStat(job *schema.Job, metric string, statType string) float64 {
|
||||||
if stats, ok := job.Statistics[metric]; ok {
|
if stats, ok := job.Statistics.Metrics[metric]; ok {
|
||||||
switch statType {
|
switch statType {
|
||||||
case "avg":
|
case "avg":
|
||||||
return stats.Avg
|
return stats.Avg
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ type JobClassTagger struct {
|
|||||||
// repo provides access to job database operations
|
// repo provides access to job database operations
|
||||||
repo JobRepository
|
repo JobRepository
|
||||||
// getStatistics retrieves job statistics for analysis
|
// getStatistics retrieves job statistics for analysis
|
||||||
getStatistics func(job *schema.Job) (map[string]schema.JobStatistics, error)
|
getStatistics func(job *schema.Job) (schema.JobStatisticsSet, error)
|
||||||
// getMetricConfig retrieves metric configuration (limits) for a cluster
|
// getMetricConfig retrieves metric configuration (limits) for a cluster
|
||||||
getMetricConfig func(cluster, subCluster string) map[string]*schema.Metric
|
getMetricConfig func(cluster, subCluster string) map[string]*schema.Metric
|
||||||
}
|
}
|
||||||
@@ -361,7 +361,7 @@ func (t *JobClassTagger) Match(job *schema.Job) {
|
|||||||
// add metrics to env
|
// add metrics to env
|
||||||
skipRule := false
|
skipRule := false
|
||||||
for _, m := range ri.metrics {
|
for _, m := range ri.metrics {
|
||||||
stats, ok := jobStats[m]
|
stats, ok := jobStats.Metrics[m]
|
||||||
if !ok {
|
if !ok {
|
||||||
cclog.Debugf("job classification: missing metric '%s' for rule %s on job %d", m, tag, job.JobID)
|
cclog.Debugf("job classification: missing metric '%s' for rule %s on job %d", m, tag, job.JobID)
|
||||||
skipRule = true
|
skipRule = true
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ func TestClassifyJobMatch(t *testing.T) {
|
|||||||
parameters: make(map[string]any),
|
parameters: make(map[string]any),
|
||||||
tagType: "jobClass",
|
tagType: "jobClass",
|
||||||
repo: mockRepo,
|
repo: mockRepo,
|
||||||
getStatistics: func(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
getStatistics: func(job *schema.Job) (schema.JobStatisticsSet, error) {
|
||||||
return map[string]schema.JobStatistics{
|
return schema.JobStatisticsSet{Metrics: map[string]schema.JobStatistics{
|
||||||
"flops_any": {Min: 0, Max: 200, Avg: 150},
|
"flops_any": {Min: 0, Max: 200, Avg: 150},
|
||||||
}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
getMetricConfig: func(cluster, subCluster string) map[string]*schema.Metric {
|
getMetricConfig: func(cluster, subCluster string) map[string]*schema.Metric {
|
||||||
return map[string]*schema.Metric{
|
return map[string]*schema.Metric{
|
||||||
@@ -120,10 +120,10 @@ func TestMatch_NoMatch(t *testing.T) {
|
|||||||
parameters: make(map[string]any),
|
parameters: make(map[string]any),
|
||||||
tagType: "jobClass",
|
tagType: "jobClass",
|
||||||
repo: mockRepo,
|
repo: mockRepo,
|
||||||
getStatistics: func(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
getStatistics: func(job *schema.Job) (schema.JobStatisticsSet, error) {
|
||||||
return map[string]schema.JobStatistics{
|
return schema.JobStatisticsSet{Metrics: map[string]schema.JobStatistics{
|
||||||
"flops_any": {Min: 0, Max: 50, Avg: 20}, // Avg 20 < 100
|
"flops_any": {Min: 0, Max: 50, Avg: 20}, // Avg 20 < 100
|
||||||
}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
getMetricConfig: func(cluster, subCluster string) map[string]*schema.Metric {
|
getMetricConfig: func(cluster, subCluster string) map[string]*schema.Metric {
|
||||||
return map[string]*schema.Metric{
|
return map[string]*schema.Metric{
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func RegisterFootprintWorker() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Statistics = make(map[string]schema.JobStatistics)
|
job.Statistics = schema.JobStatisticsSet{Metrics: make(map[string]schema.JobStatistics, len(allMetrics))}
|
||||||
|
|
||||||
for _, metric := range allMetrics {
|
for _, metric := range allMetrics {
|
||||||
avg, min, max := 0.0, 0.0, 0.0
|
avg, min, max := 0.0, 0.0, 0.0
|
||||||
@@ -98,7 +98,7 @@ func RegisterFootprintWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add values rounded to 2 digits: repo.LoadStats may return unrounded
|
// Add values rounded to 2 digits: repo.LoadStats may return unrounded
|
||||||
job.Statistics[metric] = schema.JobStatistics{
|
job.Statistics.Metrics[metric] = schema.JobStatistics{
|
||||||
Unit: schema.Unit{
|
Unit: schema.Unit{
|
||||||
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
||||||
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ func LoadAveragesFromArchive(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, m := range metrics {
|
for i, m := range metrics {
|
||||||
if stat, ok := metaFile.Statistics[m]; ok {
|
if stat, ok := metaFile.Statistics.Metrics[m]; ok {
|
||||||
data[i] = append(data[i], schema.Float(stat.Avg))
|
data[i] = append(data[i], schema.Float(stat.Avg))
|
||||||
} else {
|
} else {
|
||||||
data[i] = append(data[i], schema.NaN)
|
data[i] = append(data[i], schema.NaN)
|
||||||
@@ -323,7 +323,7 @@ func LoadStatsFromArchive(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range metrics {
|
for _, m := range metrics {
|
||||||
stat, ok := metaFile.Statistics[m]
|
stat, ok := metaFile.Statistics.Metrics[m]
|
||||||
if !ok {
|
if !ok {
|
||||||
data[m] = schema.MetricStatistics{Min: 0.0, Avg: 0.0, Max: 0.0}
|
data[m] = schema.MetricStatistics{Min: 0.0, Avg: 0.0, Max: 0.0}
|
||||||
continue
|
continue
|
||||||
@@ -349,19 +349,19 @@ func LoadScopedStatsFromArchive(
|
|||||||
data, err := ar.LoadJobStats(job)
|
data, err := ar.LoadJobStats(job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while loading job stats from archiveBackend: %s", err.Error())
|
cclog.Errorf("Error while loading job stats from archiveBackend: %s", err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatistics returns all metric statistics for a job.
|
// GetStatistics returns all metric statistics for a job, including the
|
||||||
// Returns a map of metric names to their job-level statistics.
|
// array-valued statistics groups (e.g. filesystems).
|
||||||
func GetStatistics(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
func GetStatistics(job *schema.Job) (schema.JobStatisticsSet, error) {
|
||||||
metaFile, err := ar.LoadJobMeta(job)
|
metaFile, err := ar.LoadJobMeta(job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while loading job metadata from archiveBackend: %s", err.Error())
|
cclog.Errorf("Error while loading job metadata from archiveBackend: %s", err.Error())
|
||||||
return nil, err
|
return schema.JobStatisticsSet{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return metaFile.Statistics, nil
|
return metaFile.Statistics, nil
|
||||||
|
|||||||
@@ -63,8 +63,11 @@ func initClusterConfig() error {
|
|||||||
|
|
||||||
if _, ok := metricLookup[mc.Name]; !ok {
|
if _, ok := metricLookup[mc.Name]; !ok {
|
||||||
metricLookup[mc.Name] = schema.GlobalMetricListItem{
|
metricLookup[mc.Name] = schema.GlobalMetricListItem{
|
||||||
Name: mc.Name, Scope: mc.Scope, Unit: mc.Unit, Footprint: mc.Footprint,
|
Name: mc.Name, Scope: mc.Scope, Unit: mc.Unit, Footprint: mc.Footprint, Tooltip: mc.Tooltip,
|
||||||
}
|
}
|
||||||
|
} else if item := metricLookup[mc.Name]; item.Tooltip == "" && mc.Tooltip != "" {
|
||||||
|
item.Tooltip = mc.Tooltip
|
||||||
|
metricLookup[mc.Name] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
availability := schema.ClusterSupport{Cluster: cluster.Name}
|
availability := schema.ClusterSupport{Cluster: cluster.Name}
|
||||||
@@ -139,8 +142,10 @@ func initClusterConfig() error {
|
|||||||
userItem, ok := userMetricLookup[mc.Name]
|
userItem, ok := userMetricLookup[mc.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
userItem = schema.GlobalMetricListItem{
|
userItem = schema.GlobalMetricListItem{
|
||||||
Name: mc.Name, Scope: mc.Scope, Unit: mc.Unit, Footprint: mc.Footprint,
|
Name: mc.Name, Scope: mc.Scope, Unit: mc.Unit, Footprint: mc.Footprint, Tooltip: mc.Tooltip,
|
||||||
}
|
}
|
||||||
|
} else if userItem.Tooltip == "" && mc.Tooltip != "" {
|
||||||
|
userItem.Tooltip = mc.Tooltip
|
||||||
}
|
}
|
||||||
userItem.Availability = append(userItem.Availability, userAvailability)
|
userItem.Availability = append(userItem.Availability, userAvailability)
|
||||||
userMetricLookup[mc.Name] = userItem
|
userMetricLookup[mc.Name] = userItem
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func loadJobData(filename string, isCompressed bool) (schema.JobData, error) {
|
|||||||
f, err := os.Open(filename)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("fsBackend LoadJobData()- %v", err)
|
cclog.Errorf("fsBackend LoadJobData()- %v", err)
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ func loadJobData(filename string, isCompressed bool) (schema.JobData, error) {
|
|||||||
r, err := gzip.NewReader(f)
|
r, err := gzip.NewReader(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf(" %v", err)
|
cclog.Errorf(" %v", err)
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ func loadJobStats(filename string, isCompressed bool) (schema.ScopedJobStats, er
|
|||||||
f, err := os.Open(filename)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("fsBackend LoadJobStats()- %v", err)
|
cclog.Errorf("fsBackend LoadJobStats()- %v", err)
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -134,13 +134,13 @@ func loadJobStats(filename string, isCompressed bool) (schema.ScopedJobStats, er
|
|||||||
r, err := gzip.NewReader(f)
|
r, err := gzip.NewReader(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf(" %v", err)
|
cclog.Errorf(" %v", err)
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
if config.Keys.Validate {
|
if config.Keys.Validate {
|
||||||
if err := schema.Validate(schema.Data, r); err != nil {
|
if err := schema.Validate(schema.Data, r); err != nil {
|
||||||
return nil, fmt.Errorf("validate job data: %v", err)
|
return schema.ScopedJobStats{}, fmt.Errorf("validate job data: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ func loadJobStats(filename string, isCompressed bool) (schema.ScopedJobStats, er
|
|||||||
} else {
|
} else {
|
||||||
if config.Keys.Validate {
|
if config.Keys.Validate {
|
||||||
if err := schema.Validate(schema.Data, bufio.NewReader(f)); err != nil {
|
if err := schema.Validate(schema.Data, bufio.NewReader(f)); err != nil {
|
||||||
return nil, fmt.Errorf("validate job data: %v", err)
|
return schema.ScopedJobStats{}, fmt.Errorf("validate job data: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DecodeJobStats(bufio.NewReader(f), filename)
|
return DecodeJobStats(bufio.NewReader(f), filename)
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func TestLoadJobData(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, scopes := range data {
|
for _, scopes := range data.Metrics {
|
||||||
// fmt.Printf("Metric name: %s\n", name)
|
// fmt.Printf("Metric name: %s\n", name)
|
||||||
|
|
||||||
if _, exists := scopes[schema.MetricScopeNode]; !exists {
|
if _, exists := scopes[schema.MetricScopeNode]; !exists {
|
||||||
|
|||||||
+53
-33
@@ -27,7 +27,7 @@ func DecodeJobData(r io.Reader, k string) (schema.JobData, error) {
|
|||||||
|
|
||||||
if err, ok := data.(error); ok {
|
if err, ok := data.(error); ok {
|
||||||
cclog.Warn("Error in decoded job data set")
|
cclog.Warn("Error in decoded job data set")
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.(schema.JobData), nil
|
return data.(schema.JobData), nil
|
||||||
@@ -35,39 +35,59 @@ func DecodeJobData(r io.Reader, k string) (schema.JobData, error) {
|
|||||||
|
|
||||||
func DecodeJobStats(r io.Reader, k string) (schema.ScopedJobStats, error) {
|
func DecodeJobStats(r io.Reader, k string) (schema.ScopedJobStats, error) {
|
||||||
jobData, err := DecodeJobData(r, k)
|
jobData, err := DecodeJobData(r, k)
|
||||||
// Convert schema.JobData to schema.ScopedJobStats
|
if err != nil {
|
||||||
if jobData != nil {
|
return schema.ScopedJobStats{}, err
|
||||||
scopedJobStats := make(schema.ScopedJobStats)
|
|
||||||
for metric, metricData := range jobData {
|
|
||||||
if _, ok := scopedJobStats[metric]; !ok {
|
|
||||||
scopedJobStats[metric] = make(map[schema.MetricScope][]*schema.ScopedStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
for scope, jobMetric := range metricData {
|
|
||||||
if _, ok := scopedJobStats[metric][scope]; !ok {
|
|
||||||
scopedJobStats[metric][scope] = make([]*schema.ScopedStats, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, series := range jobMetric.Series {
|
|
||||||
scopedJobStats[metric][scope] = append(scopedJobStats[metric][scope], &schema.ScopedStats{
|
|
||||||
Hostname: series.Hostname,
|
|
||||||
ID: series.ID,
|
|
||||||
Data: &series.Statistics,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// So that one can later check len(scopedJobStats[metric][scope]): Remove from map if empty
|
|
||||||
if len(scopedJobStats[metric][scope]) == 0 {
|
|
||||||
delete(scopedJobStats[metric], scope)
|
|
||||||
if len(scopedJobStats[metric]) == 0 {
|
|
||||||
delete(scopedJobStats, metric)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return scopedJobStats, nil
|
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
|
// Convert schema.JobData to schema.ScopedJobStats
|
||||||
|
scopedJobStats := schema.ScopedJobStats{
|
||||||
|
Metrics: scopedStatsFromMetrics(jobData.Metrics),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range jobData.Groups {
|
||||||
|
statsGroup := schema.ScopedStatsGroup{Key: group.Key}
|
||||||
|
for _, inst := range group.Instances {
|
||||||
|
statsGroup.Instances = append(statsGroup.Instances, schema.ScopedStatsGroupInstance{
|
||||||
|
Name: inst.Name,
|
||||||
|
Type: inst.Type,
|
||||||
|
Metrics: scopedStatsFromMetrics(inst.Metrics),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
scopedJobStats.Groups = append(scopedJobStats.Groups, statsGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scopedJobStats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scopedStatsFromMetrics reduces the full time series of every metric/scope to
|
||||||
|
// the per-series statistics. Scopes without any series are dropped so that
|
||||||
|
// callers can rely on len(stats[metric][scope]) being non-zero when present.
|
||||||
|
func scopedStatsFromMetrics(metrics map[string]schema.ScopedMetrics) map[string]schema.ScopedMetricStats {
|
||||||
|
stats := make(map[string]schema.ScopedMetricStats, len(metrics))
|
||||||
|
for metric, metricData := range metrics {
|
||||||
|
scoped := make(schema.ScopedMetricStats, len(metricData))
|
||||||
|
for scope, jobMetric := range metricData {
|
||||||
|
if len(jobMetric.Series) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
series := make([]*schema.ScopedStats, 0, len(jobMetric.Series))
|
||||||
|
for i := range jobMetric.Series {
|
||||||
|
series = append(series, &schema.ScopedStats{
|
||||||
|
Hostname: jobMetric.Series[i].Hostname,
|
||||||
|
ID: jobMetric.Series[i].ID,
|
||||||
|
Data: &jobMetric.Series[i].Statistics,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
scoped[scope] = series
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(scoped) > 0 {
|
||||||
|
stats[metric] = scoped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeJobMeta(r io.Reader) (*schema.Job, error) {
|
func DecodeJobMeta(r io.Reader) (*schema.Job, error) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func JobToParquetRow(meta *schema.Job, data *schema.JobData) (*ParquetJobRow, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
var statisticsJSON []byte
|
var statisticsJSON []byte
|
||||||
if meta.Statistics != nil {
|
if len(meta.Statistics.Metrics) > 0 || len(meta.Statistics.Groups) > 0 {
|
||||||
statisticsJSON, err = json.Marshal(meta.Statistics)
|
statisticsJSON, err = json.Marshal(meta.Statistics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal statistics: %w", err)
|
return nil, fmt.Errorf("marshal statistics: %w", err)
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ func TestParquetRowToJob(t *testing.T) {
|
|||||||
{Hostname: "node001", HWThreads: []int{0, 1, 2, 3}},
|
{Hostname: "node001", HWThreads: []int{0, 1, 2, 3}},
|
||||||
{Hostname: "node002", HWThreads: []int{4, 5, 6, 7}},
|
{Hostname: "node002", HWThreads: []int{4, 5, 6, 7}},
|
||||||
},
|
},
|
||||||
Statistics: map[string]schema.JobStatistics{
|
Statistics: schema.JobStatisticsSet{Metrics: map[string]schema.JobStatistics{
|
||||||
"cpu_load": {Avg: 50.0, Min: 10.0, Max: 90.0},
|
"cpu_load": {Avg: 50.0, Min: 10.0, Max: 90.0},
|
||||||
},
|
}},
|
||||||
Tags: []*schema.Tag{
|
Tags: []*schema.Tag{
|
||||||
{Type: "test", Name: "tag1"},
|
{Type: "test", Name: "tag1"},
|
||||||
},
|
},
|
||||||
@@ -49,7 +49,7 @@ func TestParquetRowToJob(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &schema.JobData{
|
data := &schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"cpu_load": {
|
"cpu_load": {
|
||||||
schema.MetricScopeNode: &schema.JobMetric{
|
schema.MetricScopeNode: &schema.JobMetric{
|
||||||
Unit: schema.Unit{Base: ""},
|
Unit: schema.Unit{Base: ""},
|
||||||
@@ -62,7 +62,7 @@ func TestParquetRowToJob(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
// Convert to parquet row
|
// Convert to parquet row
|
||||||
row, err := JobToParquetRow(meta, data)
|
row, err := JobToParquetRow(meta, data)
|
||||||
@@ -134,10 +134,10 @@ func TestParquetRowToJob(t *testing.T) {
|
|||||||
t.Errorf("Resources[0].HWThreads len = %d, want 4", len(gotMeta.Resources[0].HWThreads))
|
t.Errorf("Resources[0].HWThreads len = %d, want 4", len(gotMeta.Resources[0].HWThreads))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gotMeta.Statistics) != 1 {
|
if len(gotMeta.Statistics.Metrics) != 1 {
|
||||||
t.Fatalf("Statistics len = %d, want 1", len(gotMeta.Statistics))
|
t.Fatalf("Statistics len = %d, want 1", len(gotMeta.Statistics.Metrics))
|
||||||
}
|
}
|
||||||
if stat, ok := gotMeta.Statistics["cpu_load"]; !ok {
|
if stat, ok := gotMeta.Statistics.Metrics["cpu_load"]; !ok {
|
||||||
t.Error("Statistics missing cpu_load")
|
t.Error("Statistics missing cpu_load")
|
||||||
} else if stat.Avg != 50.0 {
|
} else if stat.Avg != 50.0 {
|
||||||
t.Errorf("Statistics[cpu_load].Avg = %f, want 50.0", stat.Avg)
|
t.Errorf("Statistics[cpu_load].Avg = %f, want 50.0", stat.Avg)
|
||||||
@@ -163,7 +163,7 @@ func TestParquetRowToJob(t *testing.T) {
|
|||||||
if gotData == nil {
|
if gotData == nil {
|
||||||
t.Fatal("JobData is nil")
|
t.Fatal("JobData is nil")
|
||||||
}
|
}
|
||||||
cpuLoad, ok := (*gotData)["cpu_load"]
|
cpuLoad, ok := gotData.Metrics["cpu_load"]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("JobData missing cpu_load")
|
t.Fatal("JobData missing cpu_load")
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ func TestParquetRowToJobNilOptionalFields(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &schema.JobData{
|
data := &schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"cpu_load": {
|
"cpu_load": {
|
||||||
schema.MetricScopeNode: &schema.JobMetric{
|
schema.MetricScopeNode: &schema.JobMetric{
|
||||||
Timestep: 60,
|
Timestep: 60,
|
||||||
@@ -210,7 +210,7 @@ func TestParquetRowToJobNilOptionalFields(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
row, err := JobToParquetRow(meta, data)
|
row, err := JobToParquetRow(meta, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -228,8 +228,8 @@ func TestParquetRowToJobNilOptionalFields(t *testing.T) {
|
|||||||
if gotMeta.Tags != nil {
|
if gotMeta.Tags != nil {
|
||||||
t.Errorf("Tags should be nil, got %v", gotMeta.Tags)
|
t.Errorf("Tags should be nil, got %v", gotMeta.Tags)
|
||||||
}
|
}
|
||||||
if gotMeta.Statistics != nil {
|
if len(gotMeta.Statistics.Metrics) != 0 || len(gotMeta.Statistics.Groups) != 0 {
|
||||||
t.Errorf("Statistics should be nil, got %v", gotMeta.Statistics)
|
t.Errorf("Statistics should be empty, got %v", gotMeta.Statistics)
|
||||||
}
|
}
|
||||||
if gotMeta.MetaData != nil {
|
if gotMeta.MetaData != nil {
|
||||||
t.Errorf("MetaData should be nil, got %v", gotMeta.MetaData)
|
t.Errorf("MetaData should be nil, got %v", gotMeta.MetaData)
|
||||||
@@ -299,7 +299,7 @@ func TestRoundTripThroughParquetFile(t *testing.T) {
|
|||||||
if gotData == nil {
|
if gotData == nil {
|
||||||
t.Fatal("JobData is nil")
|
t.Fatal("JobData is nil")
|
||||||
}
|
}
|
||||||
if _, ok := (*gotData)["cpu_load"]; !ok {
|
if _, ok := gotData.Metrics["cpu_load"]; !ok {
|
||||||
t.Error("JobData missing cpu_load")
|
t.Error("JobData missing cpu_load")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func makeTestJob(jobID int64) (*schema.Job, *schema.JobData) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data := schema.JobData{
|
data := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||||
"cpu_load": {
|
"cpu_load": {
|
||||||
schema.MetricScopeNode: &schema.JobMetric{
|
schema.MetricScopeNode: &schema.JobMetric{
|
||||||
Unit: schema.Unit{Base: ""},
|
Unit: schema.Unit{Base: ""},
|
||||||
@@ -70,7 +70,7 @@ func makeTestJob(jobID int64) (*schema.Job, *schema.JobData) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
return meta, &data
|
return meta, &data
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ func TestJobToParquetRowConversion(t *testing.T) {
|
|||||||
if err := json.Unmarshal(decompressed, &jobData); err != nil {
|
if err := json.Unmarshal(decompressed, &jobData); err != nil {
|
||||||
t.Fatalf("unmarshal metric data: %v", err)
|
t.Fatalf("unmarshal metric data: %v", err)
|
||||||
}
|
}
|
||||||
if _, ok := jobData["cpu_load"]; !ok {
|
if _, ok := jobData.Metrics["cpu_load"]; !ok {
|
||||||
t.Error("metric data missing cpu_load key")
|
t.Error("metric data missing cpu_load key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ func (s3a *S3Archive) LoadJobData(job *schema.Job) (schema.JobData, error) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("S3Archive LoadJobData() > GetObject error: %v", err)
|
cclog.Errorf("S3Archive LoadJobData() > GetObject error: %v", err)
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
|
|
||||||
@@ -349,7 +349,7 @@ func (s3a *S3Archive) LoadJobData(job *schema.Job) (schema.JobData, error) {
|
|||||||
r, err := gzip.NewReader(result.Body)
|
r, err := gzip.NewReader(result.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("S3Archive LoadJobData() > gzip error: %v", err)
|
cclog.Errorf("S3Archive LoadJobData() > gzip error: %v", err)
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
@@ -381,14 +381,14 @@ func (s3a *S3Archive) LoadJobStats(job *schema.Job) (schema.ScopedJobStats, erro
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("S3Archive LoadJobStats() > GetObject error: %v", err)
|
cclog.Errorf("S3Archive LoadJobStats() > GetObject error: %v", err)
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
|
|
||||||
if config.Keys.Validate {
|
if config.Keys.Validate {
|
||||||
b, _ := io.ReadAll(result.Body)
|
b, _ := io.ReadAll(result.Body)
|
||||||
if err := schema.Validate(schema.Data, bytes.NewReader(b)); err != nil {
|
if err := schema.Validate(schema.Data, bytes.NewReader(b)); err != nil {
|
||||||
return nil, fmt.Errorf("validate job data: %v", err)
|
return schema.ScopedJobStats{}, fmt.Errorf("validate job data: %v", err)
|
||||||
}
|
}
|
||||||
return DecodeJobStats(bytes.NewReader(b), key)
|
return DecodeJobStats(bytes.NewReader(b), key)
|
||||||
}
|
}
|
||||||
@@ -400,14 +400,14 @@ func (s3a *S3Archive) LoadJobStats(job *schema.Job) (schema.ScopedJobStats, erro
|
|||||||
r, err := gzip.NewReader(result.Body)
|
r, err := gzip.NewReader(result.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("S3Archive LoadJobStats() > gzip error: %v", err)
|
cclog.Errorf("S3Archive LoadJobStats() > gzip error: %v", err)
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
if config.Keys.Validate {
|
if config.Keys.Validate {
|
||||||
b, _ := io.ReadAll(r)
|
b, _ := io.ReadAll(r)
|
||||||
if err := schema.Validate(schema.Data, bytes.NewReader(b)); err != nil {
|
if err := schema.Validate(schema.Data, bytes.NewReader(b)); err != nil {
|
||||||
return nil, fmt.Errorf("validate job data: %v", err)
|
return schema.ScopedJobStats{}, fmt.Errorf("validate job data: %v", err)
|
||||||
}
|
}
|
||||||
return DecodeJobStats(bytes.NewReader(b), keyGz)
|
return DecodeJobStats(bytes.NewReader(b), keyGz)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ func (sa *SqliteArchive) LoadJobData(job *schema.Job) (schema.JobData, error) {
|
|||||||
job.JobID, job.Cluster, job.StartTime).Scan(&dataBlob, &compressed)
|
job.JobID, job.Cluster, job.StartTime).Scan(&dataBlob, &compressed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("SqliteArchive LoadJobData() > query error: %v", err)
|
cclog.Errorf("SqliteArchive LoadJobData() > query error: %v", err)
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("%s:%d:%d", job.Cluster, job.JobID, job.StartTime)
|
key := fmt.Sprintf("%s:%d:%d", job.Cluster, job.JobID, job.StartTime)
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ func (sa *SqliteArchive) LoadJobData(job *schema.Job) (schema.JobData, error) {
|
|||||||
gzipReader, err := gzip.NewReader(reader)
|
gzipReader, err := gzip.NewReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("SqliteArchive LoadJobData() > gzip error: %v", err)
|
cclog.Errorf("SqliteArchive LoadJobData() > gzip error: %v", err)
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
defer gzipReader.Close()
|
defer gzipReader.Close()
|
||||||
reader = gzipReader
|
reader = gzipReader
|
||||||
@@ -285,7 +285,7 @@ func (sa *SqliteArchive) LoadJobStats(job *schema.Job) (schema.ScopedJobStats, e
|
|||||||
job.JobID, job.Cluster, job.StartTime).Scan(&dataBlob, &compressed)
|
job.JobID, job.Cluster, job.StartTime).Scan(&dataBlob, &compressed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("SqliteArchive LoadJobStats() > query error: %v", err)
|
cclog.Errorf("SqliteArchive LoadJobStats() > query error: %v", err)
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("%s:%d:%d", job.Cluster, job.JobID, job.StartTime)
|
key := fmt.Sprintf("%s:%d:%d", job.Cluster, job.JobID, job.StartTime)
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ func (sa *SqliteArchive) LoadJobStats(job *schema.Job) (schema.ScopedJobStats, e
|
|||||||
gzipReader, err := gzip.NewReader(reader)
|
gzipReader, err := gzip.NewReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("SqliteArchive LoadJobStats() > gzip error: %v", err)
|
cclog.Errorf("SqliteArchive LoadJobStats() > gzip error: %v", err)
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
defer gzipReader.Close()
|
defer gzipReader.Close()
|
||||||
reader = gzipReader
|
reader = gzipReader
|
||||||
@@ -303,7 +303,7 @@ func (sa *SqliteArchive) LoadJobStats(job *schema.Job) (schema.ScopedJobStats, e
|
|||||||
if config.Keys.Validate {
|
if config.Keys.Validate {
|
||||||
data, _ := io.ReadAll(reader)
|
data, _ := io.ReadAll(reader)
|
||||||
if err := schema.Validate(schema.Data, bytes.NewReader(data)); err != nil {
|
if err := schema.Validate(schema.Data, bytes.NewReader(data)); err != nil {
|
||||||
return nil, fmt.Errorf("validate job data: %v", err)
|
return schema.ScopedJobStats{}, fmt.Errorf("validate job data: %v", err)
|
||||||
}
|
}
|
||||||
return DecodeJobStats(bytes.NewReader(data), key)
|
return DecodeJobStats(bytes.NewReader(data), key)
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-29
@@ -89,7 +89,7 @@ func (ccms *InternalMetricStore) LoadData(
|
|||||||
queries, assignedScope, err := buildQueries(job, metrics, scopes, int64(resolution))
|
queries, assignedScope, err := buildQueries(job, metrics, scopes, int64(resolution))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify assignment is correct - log any inconsistencies for debugging
|
// Verify assignment is correct - log any inconsistencies for debugging
|
||||||
@@ -110,11 +110,11 @@ func (ccms *InternalMetricStore) LoadData(
|
|||||||
resBody, err := FetchData(req)
|
resBody, err := FetchData(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while fetching data : %s", err.Error())
|
cclog.Errorf("Error while fetching data : %s", err.Error())
|
||||||
return nil, err
|
return schema.JobData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errors []string
|
var errors []string
|
||||||
jobData := make(schema.JobData)
|
jobData := schema.JobData{Metrics: make(map[string]schema.ScopedMetrics)}
|
||||||
|
|
||||||
// Add safety check for potential index out of range errors
|
// Add safety check for potential index out of range errors
|
||||||
if len(resBody.Results) != len(req.Queries) || len(assignedScope) != len(req.Queries) {
|
if len(resBody.Results) != len(req.Queries) || len(assignedScope) != len(req.Queries) {
|
||||||
@@ -139,8 +139,8 @@ func (ccms *InternalMetricStore) LoadData(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := jobData[metric]; !ok {
|
if _, ok := jobData.Metrics[metric]; !ok {
|
||||||
jobData[metric] = make(map[schema.MetricScope]*schema.JobMetric)
|
jobData.Metrics[metric] = make(schema.ScopedMetrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := mc.Timestep
|
res := mc.Timestep
|
||||||
@@ -148,14 +148,14 @@ func (ccms *InternalMetricStore) LoadData(
|
|||||||
res = int(row[0].Resolution)
|
res = int(row[0].Resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
jobMetric, ok := jobData[metric][scope]
|
jobMetric, ok := jobData.Metrics[metric][scope]
|
||||||
if !ok {
|
if !ok {
|
||||||
jobMetric = &schema.JobMetric{
|
jobMetric = &schema.JobMetric{
|
||||||
Unit: mc.Unit,
|
Unit: mc.Unit,
|
||||||
Timestep: res,
|
Timestep: res,
|
||||||
Series: make([]schema.Series, 0),
|
Series: make([]schema.Series, 0),
|
||||||
}
|
}
|
||||||
jobData[metric][scope] = jobMetric
|
jobData.Metrics[metric][scope] = jobMetric
|
||||||
}
|
}
|
||||||
|
|
||||||
for ndx, res := range row {
|
for ndx, res := range row {
|
||||||
@@ -181,11 +181,11 @@ func (ccms *InternalMetricStore) LoadData(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// So that one can later check len(jobData):
|
// So that one can later check len(jobData.Metrics):
|
||||||
if len(jobMetric.Series) == 0 {
|
if len(jobMetric.Series) == 0 {
|
||||||
delete(jobData[metric], scope)
|
delete(jobData.Metrics[metric], scope)
|
||||||
if len(jobData[metric]) == 0 {
|
if len(jobData.Metrics[metric]) == 0 {
|
||||||
delete(jobData, metric)
|
delete(jobData.Metrics, metric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,7 +407,7 @@ func (ccms *InternalMetricStore) LoadScopedStats(
|
|||||||
queries, assignedScope, err := buildQueries(job, metrics, scopes, 0)
|
queries, assignedScope, err := buildQueries(job, metrics, scopes, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
cclog.Errorf("Error while building queries for jobId %d, Metrics %v, Scopes %v: %s", job.JobID, metrics, scopes, err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := APIQueryRequest{
|
req := APIQueryRequest{
|
||||||
@@ -422,11 +422,11 @@ func (ccms *InternalMetricStore) LoadScopedStats(
|
|||||||
resBody, err := FetchData(req)
|
resBody, err := FetchData(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Errorf("Error while fetching data : %s", err.Error())
|
cclog.Errorf("Error while fetching data : %s", err.Error())
|
||||||
return nil, err
|
return schema.ScopedJobStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errors []string
|
var errors []string
|
||||||
scopedJobStats := make(schema.ScopedJobStats)
|
scopedJobStats := schema.ScopedJobStats{Metrics: make(map[string]schema.ScopedMetricStats)}
|
||||||
|
|
||||||
for i, row := range resBody.Results {
|
for i, row := range resBody.Results {
|
||||||
if len(row) == 0 {
|
if len(row) == 0 {
|
||||||
@@ -437,12 +437,12 @@ func (ccms *InternalMetricStore) LoadScopedStats(
|
|||||||
metric := query.Metric
|
metric := query.Metric
|
||||||
scope := assignedScope[i]
|
scope := assignedScope[i]
|
||||||
|
|
||||||
if _, ok := scopedJobStats[metric]; !ok {
|
if _, ok := scopedJobStats.Metrics[metric]; !ok {
|
||||||
scopedJobStats[metric] = make(map[schema.MetricScope][]*schema.ScopedStats)
|
scopedJobStats.Metrics[metric] = make(schema.ScopedMetricStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := scopedJobStats[metric][scope]; !ok {
|
if _, ok := scopedJobStats.Metrics[metric][scope]; !ok {
|
||||||
scopedJobStats[metric][scope] = make([]*schema.ScopedStats, 0)
|
scopedJobStats.Metrics[metric][scope] = make([]*schema.ScopedStats, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ndx, res := range row {
|
for ndx, res := range row {
|
||||||
@@ -456,7 +456,7 @@ func (ccms *InternalMetricStore) LoadScopedStats(
|
|||||||
|
|
||||||
SanitizeStats(&res.Avg, &res.Min, &res.Max)
|
SanitizeStats(&res.Avg, &res.Min, &res.Max)
|
||||||
|
|
||||||
scopedJobStats[metric][scope] = append(scopedJobStats[metric][scope], &schema.ScopedStats{
|
scopedJobStats.Metrics[metric][scope] = append(scopedJobStats.Metrics[metric][scope], &schema.ScopedStats{
|
||||||
Hostname: query.Hostname,
|
Hostname: query.Hostname,
|
||||||
ID: id,
|
ID: id,
|
||||||
Data: &schema.MetricStatistics{
|
Data: &schema.MetricStatistics{
|
||||||
@@ -467,11 +467,11 @@ func (ccms *InternalMetricStore) LoadScopedStats(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// So that one can later check len(scopedJobStats[metric][scope]): Remove from map if empty
|
// So that one can later check len(scopedJobStats.Metrics[metric][scope]): Remove from map if empty
|
||||||
if len(scopedJobStats[metric][scope]) == 0 {
|
if len(scopedJobStats.Metrics[metric][scope]) == 0 {
|
||||||
delete(scopedJobStats[metric], scope)
|
delete(scopedJobStats.Metrics[metric], scope)
|
||||||
if len(scopedJobStats[metric]) == 0 {
|
if len(scopedJobStats.Metrics[metric]) == 0 {
|
||||||
delete(scopedJobStats, metric)
|
delete(scopedJobStats.Metrics, metric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -697,14 +697,14 @@ func (ccms *InternalMetricStore) LoadNodeListData(
|
|||||||
// Init Nested Map Data Structures If Not Found
|
// Init Nested Map Data Structures If Not Found
|
||||||
hostData, ok := data[query.Hostname]
|
hostData, ok := data[query.Hostname]
|
||||||
if !ok {
|
if !ok {
|
||||||
hostData = make(schema.JobData)
|
hostData = schema.JobData{Metrics: make(map[string]schema.ScopedMetrics)}
|
||||||
data[query.Hostname] = hostData
|
data[query.Hostname] = hostData
|
||||||
}
|
}
|
||||||
|
|
||||||
metricData, ok := hostData[metric]
|
metricData, ok := hostData.Metrics[metric]
|
||||||
if !ok {
|
if !ok {
|
||||||
metricData = make(map[schema.MetricScope]*schema.JobMetric)
|
metricData = make(schema.ScopedMetrics)
|
||||||
data[query.Hostname][metric] = metricData
|
hostData.Metrics[metric] = metricData
|
||||||
}
|
}
|
||||||
|
|
||||||
scopeData, ok := metricData[scope]
|
scopeData, ok := metricData[scope]
|
||||||
@@ -714,7 +714,7 @@ func (ccms *InternalMetricStore) LoadNodeListData(
|
|||||||
Timestep: res,
|
Timestep: res,
|
||||||
Series: make([]schema.Series, 0),
|
Series: make([]schema.Series, 0),
|
||||||
}
|
}
|
||||||
data[query.Hostname][metric][scope] = scopeData
|
metricData[scope] = scopeData
|
||||||
}
|
}
|
||||||
|
|
||||||
for ndx, res := range row {
|
for ndx, res := range row {
|
||||||
|
|||||||
@@ -231,9 +231,9 @@ func TestImportDataIntegrity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify metric data exists
|
// Verify metric data exists
|
||||||
if len(srcData) != len(dstData) {
|
if len(srcData.Metrics) != len(dstData.Metrics) {
|
||||||
t.Errorf("Metric count mismatch for job %d: expected %d, got %d",
|
t.Errorf("Metric count mismatch for job %d: expected %d, got %d",
|
||||||
srcJob.Meta.JobID, len(srcData), len(dstData))
|
srcJob.Meta.JobID, len(srcData.Metrics), len(dstData.Metrics))
|
||||||
}
|
}
|
||||||
|
|
||||||
verifiedJobs++
|
verifiedJobs++
|
||||||
|
|||||||
@@ -70,3 +70,13 @@ footer {
|
|||||||
margin: 0rem 0.8rem;
|
margin: 0rem 0.8rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fix: prevent Sveltestrap collapseIn from leaving the navbar invisible
|
||||||
|
when prefers-reduced-motion causes transition-duration = 0ms and Svelte
|
||||||
|
skips calling tick(), leaving the node stuck in .collapsing (height:0). */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.navbar-collapse.collapsing {
|
||||||
|
height: auto !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
let presetProject = $derived(filterPresets?.project ? filterPresets.project : "");
|
let presetProject = $derived(filterPresets?.project ? filterPresets.project : "");
|
||||||
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
|
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
|
||||||
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
|
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
|
||||||
|
const maxClusters = $derived($initq?.data?.clusters?.length || 0);
|
||||||
|
const maxSubClusters = $derived($initq?.data?.clusters?.find((c) => c.name == selectedCluster)?.subClusters?.length || 0);
|
||||||
let metrics = $derived.by(() => {
|
let metrics = $derived.by(() => {
|
||||||
if (thisInit && ccconfig) {
|
if (thisInit && ccconfig) {
|
||||||
if (selectedCluster) {
|
if (selectedCluster) {
|
||||||
@@ -243,6 +245,8 @@
|
|||||||
presetMetrics={metrics}
|
presetMetrics={metrics}
|
||||||
cluster={selectedCluster}
|
cluster={selectedCluster}
|
||||||
subCluster={selectedSubCluster}
|
subCluster={selectedSubCluster}
|
||||||
|
{maxClusters}
|
||||||
|
{maxSubClusters}
|
||||||
configName="metricConfig_jobListMetrics"
|
configName="metricConfig_jobListMetrics"
|
||||||
footprintSelect
|
footprintSelect
|
||||||
{globalMetrics}
|
{globalMetrics}
|
||||||
|
|||||||
@@ -63,8 +63,9 @@
|
|||||||
let pendingHostnameFilter = $state("");
|
let pendingHostnameFilter = $state("");
|
||||||
let isMetricsSelectionOpen = $state(false);
|
let isMetricsSelectionOpen = $state(false);
|
||||||
|
|
||||||
/* Derived Init Return */
|
/* Derived Init Returns */
|
||||||
const thisInit = $derived($initq?.data ? true : false);
|
const thisInit = $derived($initq?.data ? true : false);
|
||||||
|
const maxSubClusters = $derived($initq?.data?.clusters?.find((c) => c.name == cluster)?.subClusters?.length || 0);
|
||||||
|
|
||||||
/* Derived States */
|
/* Derived States */
|
||||||
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
|
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
|
||||||
@@ -268,6 +269,7 @@
|
|||||||
{cluster}
|
{cluster}
|
||||||
{subCluster}
|
{subCluster}
|
||||||
{globalMetrics}
|
{globalMetrics}
|
||||||
|
maxSubClusters={subCluster? null: maxSubClusters}
|
||||||
configName="nodeList_selectedMetrics"
|
configName="nodeList_selectedMetrics"
|
||||||
applyMetrics={(newMetrics) =>
|
applyMetrics={(newMetrics) =>
|
||||||
selectedMetrics = [...newMetrics]
|
selectedMetrics = [...newMetrics]
|
||||||
|
|||||||
@@ -90,6 +90,8 @@
|
|||||||
const shortDuration = $derived(ccconfig?.jobList_hideShortRunningJobs);
|
const shortDuration = $derived(ccconfig?.jobList_hideShortRunningJobs);
|
||||||
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
|
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
|
||||||
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
|
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
|
||||||
|
const maxClusters = $derived($initq?.data?.clusters?.length || 0);
|
||||||
|
const maxSubClusters = $derived($initq?.data?.clusters?.find((c) => c.name == selectedCluster)?.subClusters?.length || 0);
|
||||||
let metrics = $derived.by(() => {
|
let metrics = $derived.by(() => {
|
||||||
if (thisInit && ccconfig) {
|
if (thisInit && ccconfig) {
|
||||||
if (selectedCluster) {
|
if (selectedCluster) {
|
||||||
@@ -531,6 +533,8 @@
|
|||||||
presetMetrics={metrics}
|
presetMetrics={metrics}
|
||||||
cluster={selectedCluster}
|
cluster={selectedCluster}
|
||||||
subCluster={selectedSubCluster}
|
subCluster={selectedSubCluster}
|
||||||
|
{maxClusters}
|
||||||
|
{maxSubClusters}
|
||||||
configName="metricConfig_jobListMetrics"
|
configName="metricConfig_jobListMetrics"
|
||||||
footprintSelect
|
footprintSelect
|
||||||
{globalMetrics}
|
{globalMetrics}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
ModalFooter,
|
ModalFooter,
|
||||||
Button,
|
Button,
|
||||||
ListGroup,
|
ListGroup,
|
||||||
|
Icon,
|
||||||
|
Tooltip
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||||
|
|
||||||
@@ -33,6 +35,8 @@
|
|||||||
presetMetrics = [],
|
presetMetrics = [],
|
||||||
cluster = null,
|
cluster = null,
|
||||||
subCluster = null,
|
subCluster = null,
|
||||||
|
maxClusters = null,
|
||||||
|
maxSubClusters = null,
|
||||||
footprintSelect = false,
|
footprintSelect = false,
|
||||||
configName,
|
configName,
|
||||||
globalMetrics,
|
globalMetrics,
|
||||||
@@ -86,20 +90,45 @@
|
|||||||
return availableMetrics;
|
return availableMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function printAvailabilityCount(metric, cluster) {
|
||||||
|
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
|
||||||
|
if (avail) {
|
||||||
|
if (!cluster) {
|
||||||
|
return `${avail.length} / ${maxClusters} Cluster`
|
||||||
|
} else {
|
||||||
|
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
|
||||||
|
if (subAvail) {
|
||||||
|
return `${subAvail.length} / ${maxSubClusters} SubCluster`
|
||||||
|
} else {
|
||||||
|
return `0 / ${maxSubClusters} SubCluster`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `0 / ${maxClusters} Cluster`
|
||||||
|
}
|
||||||
|
|
||||||
function printAvailability(metric, cluster) {
|
function printAvailability(metric, cluster) {
|
||||||
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
|
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
|
||||||
if (avail) {
|
if (avail) {
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
return avail.map((av) => av.cluster).join(', ')
|
return avail.map((av) => av.cluster)
|
||||||
} else {
|
} else {
|
||||||
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
|
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
|
||||||
if (subAvail) {
|
if (subAvail) {
|
||||||
return subAvail.join(', ')
|
return subAvail
|
||||||
} else {
|
} else {
|
||||||
return `Not available for ${cluster}`
|
return [`Not available for ${cluster}`]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return [`Not available for ${cluster}`]
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTooltip(metric) {
|
||||||
|
const toolt = globalMetrics.find((gm) => gm.name === metric)?.tooltip
|
||||||
|
if (toolt) {
|
||||||
|
return toolt
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +201,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>Configure columns (Metric availability shown)</ModalHeader>
|
<ModalHeader>Configure columns</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
{#if footprintSelect}
|
{#if footprintSelect}
|
||||||
@@ -213,9 +242,34 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{metric}
|
{metric}
|
||||||
<span style="float: right; text-align: justify;">
|
{#if maxClusters !== null || maxSubClusters !== null}
|
||||||
{printAvailability(metric, cluster)}
|
<span style="float: right;" class="ms-1">
|
||||||
</span>
|
<Button id={`${metric}-avail-info`} outline color="secondary" size="sm" class="ml-2">
|
||||||
|
<b>{ printAvailabilityCount(metric, cluster) }</b>
|
||||||
|
</Button>
|
||||||
|
<Tooltip target={`${metric}-avail-info`} placement="right">
|
||||||
|
<b>Availability</b>
|
||||||
|
<ul style="text-align: left; padding-left: 1.0rem; margin-bottom: 0.25rem;">
|
||||||
|
{#each printAvailability(metric, cluster) as avail}
|
||||||
|
<li>{avail}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{#if printTooltip(metric) !== ""}
|
||||||
|
<span style="float: right;">
|
||||||
|
<Button id={`${metric}-kind-info`} outline color="secondary" size="sm" class="ml-2">
|
||||||
|
<Icon name="info-square" />
|
||||||
|
</Button>
|
||||||
|
<Tooltip target={`${metric}-kind-info`} placement="right">
|
||||||
|
<b>Information</b>
|
||||||
|
<p style="text-align: left; margin-bottom: 0.25rem;">
|
||||||
|
{ printTooltip(metric) }
|
||||||
|
</p>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export function init(extraInitQuery = "") {
|
|||||||
name
|
name
|
||||||
scope
|
scope
|
||||||
footprint
|
footprint
|
||||||
|
tooltip
|
||||||
unit { base, prefix }
|
unit { base, prefix }
|
||||||
availability { cluster, subClusters }
|
availability { cluster, subClusters }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<DropdownToggle nav caret class="dropdown-item py-1 px-2">
|
<DropdownToggle nav caret class="dropdown-item py-1 px-2">
|
||||||
{cn}
|
{cn}
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu>
|
<DropdownMenu style="max-height:75vh; overflow-y: auto;">
|
||||||
<DropdownItem class="py-1 px-2"
|
<DropdownItem class="py-1 px-2"
|
||||||
href={item.href + cn}
|
href={item.href + cn}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user