mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-07-27 00:37:14 +02:00
Adopt cc-lib hierarchical JobData/Statistics structs
cc-lib changed JobData, ScopedJobStats and Job.Statistics from flat maps to structs (a .Metrics map plus array-valued Groups) to represent filesystem (and future interconnect) metric groups. Migrate all construction, indexing and iteration to the new API across the archive backends, metricstore query path, metric dispatcher, archiver, importer, tagger, taskmanager, repository and API layers. Semantics: DecodeJobStats now projects JobData.Groups into ScopedJobStats.Groups, and the archiver derives per-filesystem node-scope statistics into Job.Statistics.Groups. deepCopy, resampling and the metric/scope filter are group-aware. The metricstore internal storage (buffers, selector tree, checkpoint/parquet) is unchanged; all conversion stays at the LoadData/archive-codec seam. Note: requires the corresponding cc-lib release; bump the cc-lib dependency version once tagged (a local go.mod replace was used during development and is intentionally not committed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -196,7 +196,7 @@ 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": map[schema.MetricScope]*schema.JobMetric{
|
||||||
schema.MetricScopeNode: {
|
schema.MetricScopeNode: {
|
||||||
Unit: schema.Unit{Base: "load"},
|
Unit: schema.Unit{Base: "load"},
|
||||||
@@ -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,7 +497,7 @@ 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": map[schema.MetricScope]*schema.JobMetric{
|
||||||
schema.MetricScopeNode: {
|
schema.MetricScopeNode: {
|
||||||
Unit: schema.Unit{Base: "load"},
|
Unit: schema.Unit{Base: "load"},
|
||||||
@@ -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,7 +531,7 @@ 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": map[schema.MetricScope]*schema.JobMetric{
|
||||||
schema.MetricScopeNode: {
|
schema.MetricScopeNode: {
|
||||||
Unit: schema.Unit{Base: "load"},
|
Unit: schema.Unit{Base: "load"},
|
||||||
@@ -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,11 +65,11 @@ 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)}
|
||||||
|
|
||||||
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[schema.MetricScopeNode]
|
||||||
if !ok {
|
if !ok {
|
||||||
// This should never happen ?
|
// This should never happen ?
|
||||||
continue
|
continue
|
||||||
@@ -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,
|
||||||
@@ -93,5 +93,43 @@ func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.Job, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute the same node-scope statistics for each array-valued metric group
|
||||||
|
// instance (e.g. per-filesystem) and attach it as a group in the statistics
|
||||||
|
// set so it round-trips into the job-meta "statistics" JSON.
|
||||||
|
for _, group := range jobData.Groups {
|
||||||
|
for _, inst := range group.Instances {
|
||||||
|
instStats := make(map[string]schema.JobStatistics)
|
||||||
|
for metric, data := range inst.Metrics {
|
||||||
|
avg, min, max := 0.0, math.MaxFloat32, -math.MaxFloat32
|
||||||
|
nodeData, ok := data[schema.MetricScopeNode]
|
||||||
|
if !ok {
|
||||||
|
// This should never happen ?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, series := range nodeData.Series {
|
||||||
|
avg += series.Statistics.Avg
|
||||||
|
min = math.Min(min, series.Statistics.Min)
|
||||||
|
max = math.Max(max, series.Statistics.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer the unit from a MetricConfig if one exists, otherwise
|
||||||
|
// fall back to the unit already present on the JobMetric.
|
||||||
|
unit := nodeData.Unit
|
||||||
|
if mc := archive.GetMetricConfig(job.Cluster, metric); mc != nil {
|
||||||
|
unit = schema.Unit{Prefix: mc.Unit.Prefix, Base: mc.Unit.Base}
|
||||||
|
}
|
||||||
|
|
||||||
|
instStats[metric] = schema.JobStatistics{
|
||||||
|
Unit: unit,
|
||||||
|
Avg: (math.Round((avg/float64(job.NumNodes))*100) / 100),
|
||||||
|
Min: min,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
job.Statistics.AddGroupInstance(group.Key, inst.Name, inst.Type, instStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return job, archive.GetHandle().ImportJob(job, &jobData)
|
return job, archive.GetHandle().ImportJob(job, &jobData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -522,7 +522,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,
|
||||||
@@ -575,7 +575,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)
|
||||||
@@ -926,7 +926,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 {
|
||||||
|
|||||||
@@ -115,7 +115,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 {
|
||||||
@@ -138,33 +138,48 @@ func LoadData(job *schema.Job,
|
|||||||
|
|
||||||
// Resample archived data using Largest Triangle Three Bucket algorithm to reduce data points
|
// Resample archived data using Largest Triangle Three Bucket algorithm to reduce data points
|
||||||
// to the requested resolution, improving transfer performance and client-side rendering.
|
// to the requested resolution, improving transfer performance and client-side rendering.
|
||||||
for _, v := range jd {
|
resampleScopes := func(scopeMap schema.ScopedMetrics) error {
|
||||||
for _, v_ := range v {
|
for _, v_ := range scopeMap {
|
||||||
timestep := int64(0)
|
timestep := int64(0)
|
||||||
for i := 0; i < len(v_.Series); i += 1 {
|
for i := 0; i < len(v_.Series); i += 1 {
|
||||||
v_.Series[i].Data, timestep, err = resampler.LargestTriangleThreeBucket(v_.Series[i].Data, int64(v_.Timestep), int64(resolution))
|
v_.Series[i].Data, timestep, err = resampler.LargestTriangleThreeBucket(v_.Series[i].Data, int64(v_.Timestep), int64(resolution))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, 0, 0
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v_.Timestep = int(timestep)
|
v_.Timestep = int(timestep)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, v := range jd.Metrics {
|
||||||
|
if err := resampleScopes(v); err != nil {
|
||||||
|
return err, 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, group := range jd.Groups {
|
||||||
|
for _, inst := range group.Instances {
|
||||||
|
for _, v := range inst.Metrics {
|
||||||
|
if err := resampleScopes(v); err != nil {
|
||||||
|
return err, 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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), 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
|
||||||
@@ -176,7 +191,7 @@ func LoadData(job *schema.Job,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res[metric] = perscope
|
res.Metrics[metric] = perscope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jd = res
|
jd = res
|
||||||
@@ -193,7 +208,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
|
||||||
@@ -223,7 +238,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
|
||||||
@@ -290,14 +305,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
|
||||||
@@ -444,7 +459,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
|
||||||
@@ -465,19 +480,47 @@ 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: make(map[string]schema.ScopedMetrics, len(source.Metrics))}
|
||||||
|
|
||||||
for metricName, scopeMap := range source {
|
for metricName, scopeMap := range source.Metrics {
|
||||||
result[metricName] = make(map[schema.MetricScope]*schema.JobMetric, len(scopeMap))
|
result.Metrics[metricName] = copyScopedMetrics(scopeMap)
|
||||||
|
}
|
||||||
|
|
||||||
for scope, jobMetric := range scopeMap {
|
// Deep-copy array-valued metric groups (e.g. filesystems) so their series are
|
||||||
result[metricName][scope] = copyJobMetric(jobMetric)
|
// not shared with the cached archive data during resampling.
|
||||||
|
if len(source.Groups) > 0 {
|
||||||
|
result.Groups = make([]schema.MetricGroup, len(source.Groups))
|
||||||
|
for gi, group := range source.Groups {
|
||||||
|
dstGroup := schema.MetricGroup{
|
||||||
|
Key: group.Key,
|
||||||
|
Instances: make([]schema.MetricGroupInstance, len(group.Instances)),
|
||||||
|
}
|
||||||
|
for ii, inst := range group.Instances {
|
||||||
|
dstInst := schema.MetricGroupInstance{
|
||||||
|
Name: inst.Name,
|
||||||
|
Type: inst.Type,
|
||||||
|
Metrics: make(map[string]schema.ScopedMetrics, len(inst.Metrics)),
|
||||||
|
}
|
||||||
|
for metricName, scopeMap := range inst.Metrics {
|
||||||
|
dstInst.Metrics[metricName] = copyScopedMetrics(scopeMap)
|
||||||
|
}
|
||||||
|
dstGroup.Instances[ii] = dstInst
|
||||||
|
}
|
||||||
|
result.Groups[gi] = dstGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyScopedMetrics(scopeMap schema.ScopedMetrics) schema.ScopedMetrics {
|
||||||
|
dst := make(schema.ScopedMetrics, len(scopeMap))
|
||||||
|
for scope, jobMetric := range scopeMap {
|
||||||
|
dst[scope] = copyJobMetric(jobMetric)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
func copyJobMetric(src *schema.JobMetric) *schema.JobMetric {
|
func copyJobMetric(src *schema.JobMetric) *schema.JobMetric {
|
||||||
dst := &schema.JobMetric{
|
dst := &schema.JobMetric{
|
||||||
Timestep: src.Timestep,
|
Timestep: src.Timestep,
|
||||||
|
|||||||
@@ -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{
|
||||||
@@ -482,11 +482,11 @@ 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.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,14 +690,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
|
data[query.Hostname].Metrics[metric] = metricData
|
||||||
}
|
}
|
||||||
|
|
||||||
scopeData, ok := metricData[scope]
|
scopeData, ok := metricData[scope]
|
||||||
@@ -707,7 +707,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
|
data[query.Hostname].Metrics[metric][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)}
|
||||||
|
|
||||||
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.
|
||||||
// Returns a map of metric names to their job-level statistics.
|
// Returns the job-level statistics set.
|
||||||
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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+55
-29
@@ -27,47 +27,73 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeJobStats(r io.Reader, k string) (schema.ScopedJobStats, error) {
|
// scopedStatsFromMetrics converts a flat metric->scope->JobMetric map into the
|
||||||
jobData, err := DecodeJobData(r, k)
|
// corresponding metric->scope->[]*ScopedStats map. It preserves the "remove
|
||||||
// Convert schema.JobData to schema.ScopedJobStats
|
// empty" cleanup behaviour: a scope with no series is dropped, and a metric
|
||||||
if jobData != nil {
|
// with no remaining scopes is dropped.
|
||||||
scopedJobStats := make(schema.ScopedJobStats)
|
func scopedStatsFromMetrics(metrics map[string]schema.ScopedMetrics) map[string]schema.ScopedMetricStats {
|
||||||
for metric, metricData := range jobData {
|
result := make(map[string]schema.ScopedMetricStats)
|
||||||
if _, ok := scopedJobStats[metric]; !ok {
|
for metric, metricData := range metrics {
|
||||||
scopedJobStats[metric] = make(map[schema.MetricScope][]*schema.ScopedStats)
|
if _, ok := result[metric]; !ok {
|
||||||
|
result[metric] = make(schema.ScopedMetricStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
for scope, jobMetric := range metricData {
|
||||||
|
if _, ok := result[metric][scope]; !ok {
|
||||||
|
result[metric][scope] = make([]*schema.ScopedStats, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for scope, jobMetric := range metricData {
|
for _, series := range jobMetric.Series {
|
||||||
if _, ok := scopedJobStats[metric][scope]; !ok {
|
result[metric][scope] = append(result[metric][scope], &schema.ScopedStats{
|
||||||
scopedJobStats[metric][scope] = make([]*schema.ScopedStats, 0)
|
Hostname: series.Hostname,
|
||||||
}
|
ID: series.ID,
|
||||||
|
Data: &series.Statistics,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for _, series := range jobMetric.Series {
|
// So that one can later check len(result[metric][scope]): Remove from map if empty
|
||||||
scopedJobStats[metric][scope] = append(scopedJobStats[metric][scope], &schema.ScopedStats{
|
if len(result[metric][scope]) == 0 {
|
||||||
Hostname: series.Hostname,
|
delete(result[metric], scope)
|
||||||
ID: series.ID,
|
if len(result[metric]) == 0 {
|
||||||
Data: &series.Statistics,
|
delete(result, metric)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeJobStats(r io.Reader, k string) (schema.ScopedJobStats, error) {
|
||||||
|
jobData, err := DecodeJobData(r, k)
|
||||||
|
if err != nil {
|
||||||
|
return schema.ScopedJobStats{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert schema.JobData to schema.ScopedJobStats
|
||||||
|
scopedJobStats := schema.ScopedJobStats{
|
||||||
|
Metrics: scopedStatsFromMetrics(jobData.Metrics),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project array-valued metric groups (e.g. filesystems) analogously.
|
||||||
|
for _, group := range jobData.Groups {
|
||||||
|
scopedGroup := schema.ScopedStatsGroup{Key: group.Key}
|
||||||
|
for _, inst := range group.Instances {
|
||||||
|
scopedGroup.Instances = append(scopedGroup.Instances, schema.ScopedStatsGroupInstance{
|
||||||
|
Name: inst.Name,
|
||||||
|
Type: inst.Type,
|
||||||
|
Metrics: scopedStatsFromMetrics(inst.Metrics),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
scopedJobStats.Groups = append(scopedJobStats.Groups, scopedGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scopedJobStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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 meta.Statistics.Metrics != nil {
|
||||||
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,7 +228,7 @@ 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 gotMeta.Statistics.Metrics != nil {
|
||||||
t.Errorf("Statistics should be nil, got %v", gotMeta.Statistics)
|
t.Errorf("Statistics should be nil, got %v", gotMeta.Statistics)
|
||||||
}
|
}
|
||||||
if gotMeta.MetaData != nil {
|
if gotMeta.MetaData != nil {
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-28
@@ -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 {
|
||||||
@@ -183,9 +183,9 @@ func (ccms *InternalMetricStore) 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -695,14 +695,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
|
data[query.Hostname].Metrics[metric] = metricData
|
||||||
}
|
}
|
||||||
|
|
||||||
scopeData, ok := metricData[scope]
|
scopeData, ok := metricData[scope]
|
||||||
@@ -712,7 +712,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
|
data[query.Hostname].Metrics[metric][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++
|
||||||
|
|||||||
Reference in New Issue
Block a user