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:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user