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:
@@ -196,8 +196,8 @@ func cleanup() {
|
||||
func TestRestApi(t *testing.T) {
|
||||
restapi := setup(t)
|
||||
t.Cleanup(cleanup)
|
||||
testData := schema.JobData{
|
||||
"load_one": map[schema.MetricScope]*schema.JobMetric{
|
||||
testData := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||
"load_one": {
|
||||
schema.MetricScopeNode: {
|
||||
Unit: schema.Unit{Base: "load"},
|
||||
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) {
|
||||
return testData, nil
|
||||
@@ -497,8 +497,8 @@ func TestStopJobWithReusedJobId(t *testing.T) {
|
||||
restapi := setup(t)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
testData := schema.JobData{
|
||||
"load_one": map[schema.MetricScope]*schema.JobMetric{
|
||||
testData := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||
"load_one": {
|
||||
schema.MetricScopeNode: {
|
||||
Unit: schema.Unit{Base: "load"},
|
||||
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) {
|
||||
return testData, nil
|
||||
|
||||
+1
-1
@@ -412,7 +412,7 @@ func (api *RestAPI) getJobByID(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
res := []*JobMetricWithName{}
|
||||
for name, md := range data {
|
||||
for name, md := range data.Metrics {
|
||||
for scope, metric := range md {
|
||||
res = append(res, &JobMetricWithName{
|
||||
Name: name,
|
||||
|
||||
@@ -531,8 +531,8 @@ func TestNatsHandleStopJob(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
testData := schema.JobData{
|
||||
"load_one": map[schema.MetricScope]*schema.JobMetric{
|
||||
testData := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||
"load_one": {
|
||||
schema.MetricScopeNode: {
|
||||
Unit: schema.Unit{Base: "load"},
|
||||
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) {
|
||||
return testData, nil
|
||||
|
||||
@@ -65,9 +65,9 @@ func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.Job, error) {
|
||||
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
|
||||
nodeData, ok := data["node"]
|
||||
if !ok {
|
||||
@@ -82,7 +82,7 @@ func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.Job, error) {
|
||||
}
|
||||
|
||||
// Round AVG Result to 2 Digits
|
||||
job.Statistics[metric] = schema.JobStatistics{
|
||||
job.Statistics.Metrics[metric] = schema.JobStatistics{
|
||||
Unit: schema.Unit{
|
||||
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
||||
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
||||
|
||||
@@ -100,6 +100,7 @@ type ComplexityRoot struct {
|
||||
Footprint func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Scope func(childComplexity int) int
|
||||
Tooltip func(childComplexity int) int
|
||||
Unit func(childComplexity int) int
|
||||
}
|
||||
|
||||
@@ -219,6 +220,7 @@ type ComplexityRoot struct {
|
||||
Scope func(childComplexity int) int
|
||||
SubClusters func(childComplexity int) int
|
||||
Timestep func(childComplexity int) int
|
||||
Tooltip 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
|
||||
case "GlobalMetricListItem.tooltip":
|
||||
if e.ComplexityRoot.GlobalMetricListItem.Tooltip == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.GlobalMetricListItem.Tooltip(childComplexity), true
|
||||
case "GlobalMetricListItem.unit":
|
||||
if e.ComplexityRoot.GlobalMetricListItem.Unit == nil {
|
||||
break
|
||||
@@ -1225,6 +1233,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
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":
|
||||
if e.ComplexityRoot.MetricConfig.Unit == nil {
|
||||
break
|
||||
@@ -2426,6 +2440,7 @@ type MetricConfig {
|
||||
alert: Float!
|
||||
lowerIsBetter: Boolean
|
||||
subClusters: [SubClusterConfig!]!
|
||||
tooltip: String
|
||||
}
|
||||
|
||||
type Tag {
|
||||
@@ -2589,6 +2604,7 @@ type GlobalMetricListItem {
|
||||
unit: Unit!
|
||||
scope: MetricScope!
|
||||
footprint: String
|
||||
tooltip: String
|
||||
availability: [ClusterSupport!]!
|
||||
}
|
||||
|
||||
@@ -2973,6 +2989,8 @@ func (ec *executionContext) childFields_GlobalMetricListItem(ctx context.Context
|
||||
return ec.fieldContext_GlobalMetricListItem_scope(ctx, field)
|
||||
case "footprint":
|
||||
return ec.fieldContext_GlobalMetricListItem_footprint(ctx, field)
|
||||
case "tooltip":
|
||||
return ec.fieldContext_GlobalMetricListItem_tooltip(ctx, field)
|
||||
case "availability":
|
||||
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)
|
||||
case "subClusters":
|
||||
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)
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
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) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -7377,6 +7420,29 @@ func (ec *executionContext) fieldContext_MetricConfig_subClusters(_ context.Cont
|
||||
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) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -13311,6 +13377,11 @@ func (ec *executionContext) _GlobalMetricListItem(ctx context.Context, sel ast.S
|
||||
if out.Values[i] == graphql.RequiredNull {
|
||||
out.Invalids++
|
||||
}
|
||||
case "tooltip":
|
||||
out.Values[i] = ec._GlobalMetricListItem_tooltip(ctx, field, obj)
|
||||
if out.Values[i] == graphql.RequiredNull {
|
||||
out.Invalids++
|
||||
}
|
||||
case "availability":
|
||||
out.Values[i] = ec._GlobalMetricListItem_availability(ctx, field, obj)
|
||||
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 {
|
||||
out.Invalids++
|
||||
}
|
||||
case "tooltip":
|
||||
out.Values[i] = ec._MetricConfig_tooltip(ctx, field, obj)
|
||||
if out.Values[i] == graphql.RequiredNull {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
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{}
|
||||
for name, md := range data {
|
||||
for name, md := range data.Metrics {
|
||||
for scope, metric := range md {
|
||||
res = append(res, &model.JobMetricWithName{
|
||||
Name: name,
|
||||
@@ -581,7 +581,7 @@ func (r *queryResolver) ScopedJobStats(ctx context.Context, id string, metrics [
|
||||
}
|
||||
|
||||
res := make([]*model.NamedStatsWithScope, 0)
|
||||
for name, scoped := range data {
|
||||
for name, scoped := range data.Metrics {
|
||||
for scope, stats := range scoped {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
for metric, scopedMetrics := range data[hostname] {
|
||||
for metric, scopedMetrics := range data[hostname].Metrics {
|
||||
for scope, scopedMetric := range scopedMetrics {
|
||||
host.Metrics = append(host.Metrics, &model.JobMetricWithName{
|
||||
Name: metric,
|
||||
|
||||
@@ -61,7 +61,7 @@ func (r *queryResolver) rooflineHeatmap(
|
||||
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 {
|
||||
cclog.Warnf("rooflineHeatmap(): 'flops_any' or 'mem_bw' missing for job %d", *job.ID)
|
||||
continue
|
||||
|
||||
@@ -290,7 +290,7 @@ func SanityChecks(job *schema.Job) error {
|
||||
//
|
||||
// TODO: Either implement the metric normalization or remove this dead code.
|
||||
func checkJobData(d *schema.JobData) error {
|
||||
for _, scopes := range *d {
|
||||
for _, scopes := range d.Metrics {
|
||||
// var newUnit schema.Unit
|
||||
// TODO Add node scope if missing
|
||||
for _, metric := range scopes {
|
||||
|
||||
@@ -117,7 +117,7 @@ func LoadData(job *schema.Job,
|
||||
|
||||
jd, err = ms.LoadData(job, metrics, scopes, ctx, resolution)
|
||||
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",
|
||||
job.JobID, job.User, job.Project, job.Cluster, job.SubCluster, err.Error())
|
||||
} else {
|
||||
@@ -144,7 +144,7 @@ func LoadData(job *schema.Job,
|
||||
if rfErr != nil {
|
||||
return rfErr, 0, 0
|
||||
}
|
||||
for _, v := range jd {
|
||||
for _, v := range jd.Metrics {
|
||||
for _, v_ := range v {
|
||||
timestep := int64(0)
|
||||
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.
|
||||
if metrics != nil || scopes != nil {
|
||||
if metrics == nil {
|
||||
metrics = make([]string, 0, len(jd))
|
||||
for k := range jd {
|
||||
metrics = make([]string, 0, len(jd.Metrics))
|
||||
for k := range jd.Metrics {
|
||||
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 {
|
||||
if perscope, ok := jd[metric]; ok {
|
||||
if perscope, ok := jd.Metrics[metric]; ok {
|
||||
if len(perscope) > 1 {
|
||||
subset := make(map[schema.MetricScope]*schema.JobMetric)
|
||||
subset := make(schema.ScopedMetrics)
|
||||
for _, scope := range scopes {
|
||||
if jm, ok := perscope[scope]; ok {
|
||||
subset[scope] = jm
|
||||
@@ -182,7 +185,7 @@ func LoadData(job *schema.Job,
|
||||
}
|
||||
}
|
||||
|
||||
res[metric] = perscope
|
||||
res.Metrics[metric] = perscope
|
||||
}
|
||||
}
|
||||
jd = res
|
||||
@@ -199,7 +202,7 @@ func LoadData(job *schema.Job,
|
||||
// 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.
|
||||
const maxSeriesSize int = 8
|
||||
for _, scopes := range jd {
|
||||
for _, scopes := range jd.Metrics {
|
||||
for _, jm := range scopes {
|
||||
if jm.StatisticsSeries != nil || len(jm.Series) < maxSeriesSize {
|
||||
continue
|
||||
@@ -229,7 +232,7 @@ func LoadData(job *schema.Job,
|
||||
|
||||
if err, ok := data.(error); ok {
|
||||
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
|
||||
@@ -296,14 +299,14 @@ func LoadScopedJobStats(
|
||||
if err != nil {
|
||||
cclog.Errorf("failed to access metricDataRepo for cluster %s-%s: %s",
|
||||
job.Cluster, job.SubCluster, err.Error())
|
||||
return nil, err
|
||||
return schema.ScopedJobStats{}, err
|
||||
}
|
||||
|
||||
scopedStats, err := ms.LoadScopedStats(job, metrics, scopes, ctx)
|
||||
if err != nil {
|
||||
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())
|
||||
return nil, err
|
||||
return schema.ScopedJobStats{}, err
|
||||
}
|
||||
|
||||
// Round Resulting Stat Values
|
||||
@@ -451,7 +454,7 @@ func LoadNodeListData(
|
||||
// Statistics are calculated as min/median/max.
|
||||
const maxSeriesSize int = 8
|
||||
for _, jd := range data {
|
||||
for _, scopes := range jd {
|
||||
for _, scopes := range jd.Metrics {
|
||||
for _, jm := range scopes {
|
||||
if jm.StatisticsSeries != nil || len(jm.Series) < maxSeriesSize {
|
||||
continue
|
||||
@@ -472,14 +475,32 @@ func LoadNodeListData(
|
||||
// archived data (e.g., during resampling). This ensures the cached archive data remains
|
||||
// immutable while allowing per-request transformations.
|
||||
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 {
|
||||
result[metricName] = make(map[schema.MetricScope]*schema.JobMetric, len(scopeMap))
|
||||
|
||||
scopes := make(schema.ScopedMetrics, len(scopeMap))
|
||||
for scope, jobMetric := range scopeMap {
|
||||
result[metricName][scope] = copyJobMetric(jobMetric)
|
||||
scopes[scope] = copyJobMetric(jobMetric)
|
||||
}
|
||||
result[metricName] = scopes
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
func TestDeepCopy(t *testing.T) {
|
||||
nodeId := "0"
|
||||
original := schema.JobData{
|
||||
original := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||
"cpu_load": {
|
||||
schema.MetricScopeNode: &schema.JobMetric{
|
||||
Timestep: 60,
|
||||
@@ -42,42 +42,42 @@ func TestDeepCopy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
copied := deepCopy(original)
|
||||
|
||||
original["cpu_load"][schema.MetricScopeNode].Series[0].Data[0] = 999.0
|
||||
original["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].Series[0].Data[0] = 999.0
|
||||
original.Metrics["cpu_load"][schema.MetricScopeNode].StatisticsSeries.Min[0] = 888.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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
copied["cpu_load"][schema.MetricScopeNode].Series[0].Hostname)
|
||||
copied.Metrics["cpu_load"][schema.MetricScopeNode].Series[0].Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepCopyNilStatisticsSeries(t *testing.T) {
|
||||
original := schema.JobData{
|
||||
original := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||
"mem_used": {
|
||||
schema.MetricScopeNode: &schema.JobMetric{
|
||||
Timestep: 60,
|
||||
@@ -90,18 +90,18 @@ func TestDeepCopyNilStatisticsSeries(t *testing.T) {
|
||||
StatisticsSeries: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
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",
|
||||
copied["mem_used"][schema.MetricScopeNode].StatisticsSeries)
|
||||
copied.Metrics["mem_used"][schema.MetricScopeNode].StatisticsSeries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepCopyEmptyPercentiles(t *testing.T) {
|
||||
original := schema.JobData{
|
||||
original := schema.JobData{Metrics: map[string]schema.ScopedMetrics{
|
||||
"cpu_load": {
|
||||
schema.MetricScopeNode: &schema.JobMetric{
|
||||
Timestep: 60,
|
||||
@@ -115,11 +115,11 @@ func TestDeepCopyEmptyPercentiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func (ccms *CCMetricStore) LoadData(
|
||||
queries, assignedScope, err := ccms.buildQueries(job, metrics, scopes, resolution)
|
||||
if err != nil {
|
||||
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
|
||||
@@ -256,11 +256,11 @@ func (ccms *CCMetricStore) LoadData(
|
||||
resBody, err := ccms.doRequest(ctx, &req)
|
||||
if err != nil {
|
||||
cclog.Errorf("Error while performing request for job %d: %s", job.JobID, err.Error())
|
||||
return nil, err
|
||||
return schema.JobData{}, err
|
||||
}
|
||||
|
||||
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
|
||||
if len(resBody.Results) != len(req.Queries) || len(assignedScope) != len(req.Queries) {
|
||||
@@ -285,8 +285,8 @@ func (ccms *CCMetricStore) LoadData(
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := jobData[metric]; !ok {
|
||||
jobData[metric] = make(map[schema.MetricScope]*schema.JobMetric)
|
||||
if _, ok := jobData.Metrics[metric]; !ok {
|
||||
jobData.Metrics[metric] = make(schema.ScopedMetrics)
|
||||
}
|
||||
|
||||
res := mc.Timestep
|
||||
@@ -294,14 +294,14 @@ func (ccms *CCMetricStore) LoadData(
|
||||
res = row[0].Resolution
|
||||
}
|
||||
|
||||
jobMetric, ok := jobData[metric][scope]
|
||||
jobMetric, ok := jobData.Metrics[metric][scope]
|
||||
if !ok {
|
||||
jobMetric = &schema.JobMetric{
|
||||
Unit: mc.Unit,
|
||||
Timestep: res,
|
||||
Series: make([]schema.Series, 0),
|
||||
}
|
||||
jobData[metric][scope] = jobMetric
|
||||
jobData.Metrics[metric][scope] = jobMetric
|
||||
}
|
||||
|
||||
for ndx, res := range row {
|
||||
@@ -329,9 +329,9 @@ func (ccms *CCMetricStore) LoadData(
|
||||
|
||||
// So that one can later check len(jobData):
|
||||
if len(jobMetric.Series) == 0 {
|
||||
delete(jobData[metric], scope)
|
||||
if len(jobData[metric]) == 0 {
|
||||
delete(jobData, metric)
|
||||
delete(jobData.Metrics[metric], scope)
|
||||
if len(jobData.Metrics[metric]) == 0 {
|
||||
delete(jobData.Metrics, metric)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,7 +426,7 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
||||
queries, assignedScope, err := ccms.buildQueries(job, metrics, scopes, 0)
|
||||
if err != nil {
|
||||
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{
|
||||
@@ -441,23 +441,23 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
||||
resBody, err := ccms.doRequest(ctx, &req)
|
||||
if err != nil {
|
||||
cclog.Errorf("Error while performing request for job %d: %s", job.JobID, err.Error())
|
||||
return nil, err
|
||||
return schema.ScopedJobStats{}, err
|
||||
}
|
||||
|
||||
var errors []string
|
||||
scopedJobStats := make(schema.ScopedJobStats)
|
||||
scopedJobStats := schema.ScopedJobStats{Metrics: make(map[string]schema.ScopedMetricStats)}
|
||||
|
||||
for i, row := range resBody.Results {
|
||||
query := req.Queries[i]
|
||||
metric := query.Metric
|
||||
scope := assignedScope[i]
|
||||
|
||||
if _, ok := scopedJobStats[metric]; !ok {
|
||||
scopedJobStats[metric] = make(map[schema.MetricScope][]*schema.ScopedStats)
|
||||
if _, ok := scopedJobStats.Metrics[metric]; !ok {
|
||||
scopedJobStats.Metrics[metric] = make(schema.ScopedMetricStats)
|
||||
}
|
||||
|
||||
if _, ok := scopedJobStats[metric][scope]; !ok {
|
||||
scopedJobStats[metric][scope] = make([]*schema.ScopedStats, 0)
|
||||
if _, ok := scopedJobStats.Metrics[metric][scope]; !ok {
|
||||
scopedJobStats.Metrics[metric][scope] = make([]*schema.ScopedStats, 0)
|
||||
}
|
||||
|
||||
for ndx, res := range row {
|
||||
@@ -471,7 +471,7 @@ func (ccms *CCMetricStore) LoadScopedStats(
|
||||
|
||||
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,
|
||||
ID: id,
|
||||
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
|
||||
if len(scopedJobStats[metric][scope]) == 0 {
|
||||
delete(scopedJobStats[metric], scope)
|
||||
if len(scopedJobStats[metric]) == 0 {
|
||||
delete(scopedJobStats, metric)
|
||||
if len(scopedJobStats.Metrics[metric][scope]) == 0 {
|
||||
delete(scopedJobStats.Metrics[metric], scope)
|
||||
if len(scopedJobStats.Metrics[metric]) == 0 {
|
||||
delete(scopedJobStats.Metrics, metric)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -691,14 +691,14 @@ func (ccms *CCMetricStore) LoadNodeListData(
|
||||
// Init Nested Map Data Structures If Not Found
|
||||
hostData, ok := data[query.Hostname]
|
||||
if !ok {
|
||||
hostData = make(schema.JobData)
|
||||
hostData = schema.JobData{Metrics: make(map[string]schema.ScopedMetrics)}
|
||||
data[query.Hostname] = hostData
|
||||
}
|
||||
|
||||
metricData, ok := hostData[metric]
|
||||
metricData, ok := hostData.Metrics[metric]
|
||||
if !ok {
|
||||
metricData = make(map[schema.MetricScope]*schema.JobMetric)
|
||||
data[query.Hostname][metric] = metricData
|
||||
metricData = make(schema.ScopedMetrics)
|
||||
hostData.Metrics[metric] = metricData
|
||||
}
|
||||
|
||||
scopeData, ok := metricData[scope]
|
||||
@@ -708,7 +708,7 @@ func (ccms *CCMetricStore) LoadNodeListData(
|
||||
Timestep: res,
|
||||
Series: make([]schema.Series, 0),
|
||||
}
|
||||
data[query.Hostname][metric][scope] = scopeData
|
||||
metricData[scope] = scopeData
|
||||
}
|
||||
|
||||
for ndx, res := range row {
|
||||
|
||||
@@ -403,7 +403,7 @@ func (r *JobRepository) JobsStats(
|
||||
//
|
||||
// Returns the requested statistic value or 0.0 if not found.
|
||||
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 {
|
||||
case "avg":
|
||||
return stats.Avg
|
||||
|
||||
@@ -107,7 +107,7 @@ type JobClassTagger struct {
|
||||
// repo provides access to job database operations
|
||||
repo JobRepository
|
||||
// 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 func(cluster, subCluster string) map[string]*schema.Metric
|
||||
}
|
||||
@@ -361,7 +361,7 @@ func (t *JobClassTagger) Match(job *schema.Job) {
|
||||
// add metrics to env
|
||||
skipRule := false
|
||||
for _, m := range ri.metrics {
|
||||
stats, ok := jobStats[m]
|
||||
stats, ok := jobStats.Metrics[m]
|
||||
if !ok {
|
||||
cclog.Debugf("job classification: missing metric '%s' for rule %s on job %d", m, tag, job.JobID)
|
||||
skipRule = true
|
||||
|
||||
@@ -64,10 +64,10 @@ func TestClassifyJobMatch(t *testing.T) {
|
||||
parameters: make(map[string]any),
|
||||
tagType: "jobClass",
|
||||
repo: mockRepo,
|
||||
getStatistics: func(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
||||
return map[string]schema.JobStatistics{
|
||||
getStatistics: func(job *schema.Job) (schema.JobStatisticsSet, error) {
|
||||
return schema.JobStatisticsSet{Metrics: map[string]schema.JobStatistics{
|
||||
"flops_any": {Min: 0, Max: 200, Avg: 150},
|
||||
}, nil
|
||||
}}, nil
|
||||
},
|
||||
getMetricConfig: func(cluster, subCluster string) map[string]*schema.Metric {
|
||||
return map[string]*schema.Metric{
|
||||
@@ -120,10 +120,10 @@ func TestMatch_NoMatch(t *testing.T) {
|
||||
parameters: make(map[string]any),
|
||||
tagType: "jobClass",
|
||||
repo: mockRepo,
|
||||
getStatistics: func(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
||||
return map[string]schema.JobStatistics{
|
||||
getStatistics: func(job *schema.Job) (schema.JobStatisticsSet, error) {
|
||||
return schema.JobStatisticsSet{Metrics: map[string]schema.JobStatistics{
|
||||
"flops_any": {Min: 0, Max: 50, Avg: 20}, // Avg 20 < 100
|
||||
}, nil
|
||||
}}, nil
|
||||
},
|
||||
getMetricConfig: func(cluster, subCluster string) map[string]*schema.Metric {
|
||||
return map[string]*schema.Metric{
|
||||
|
||||
@@ -80,7 +80,7 @@ func RegisterFootprintWorker() {
|
||||
continue
|
||||
}
|
||||
|
||||
job.Statistics = make(map[string]schema.JobStatistics)
|
||||
job.Statistics = schema.JobStatisticsSet{Metrics: make(map[string]schema.JobStatistics, len(allMetrics))}
|
||||
|
||||
for _, metric := range allMetrics {
|
||||
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
|
||||
job.Statistics[metric] = schema.JobStatistics{
|
||||
job.Statistics.Metrics[metric] = schema.JobStatistics{
|
||||
Unit: schema.Unit{
|
||||
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
||||
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
||||
|
||||
Reference in New Issue
Block a user