mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-07-27 00:37:14 +02:00
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>
136 lines
4.5 KiB
Go
136 lines
4.5 KiB
Go
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
|
// All rights reserved. This file is part of cc-backend.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package archiver
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
|
|
"github.com/ClusterCockpit/cc-backend/internal/metricdispatch"
|
|
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
|
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
|
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
|
)
|
|
|
|
// ArchiveJob archives a completed job's metric data to the configured archive backend.
|
|
//
|
|
// This function performs the following operations:
|
|
// 1. Loads all metric data for the job from the metric data repository
|
|
// 2. Calculates job-level statistics (avg, min, max) for each metric
|
|
// 3. Stores the job metadata and metric data to the archive backend
|
|
//
|
|
// Metric data is retrieved at the highest available resolution (typically 60s)
|
|
// for the following scopes:
|
|
// - Node scope (always)
|
|
// - Core scope (for jobs with ≤8 nodes, to reduce data volume)
|
|
// - Accelerator scope (if job used accelerators)
|
|
//
|
|
// The function respects context cancellation. If ctx is cancelled (e.g., during
|
|
// shutdown timeout), the operation will be interrupted and return an error.
|
|
//
|
|
// Parameters:
|
|
// - job: The job to archive (must be a completed job)
|
|
// - ctx: Context for cancellation and timeout control
|
|
//
|
|
// Returns:
|
|
// - *schema.Job with populated Statistics field
|
|
// - error if data loading or archiving fails
|
|
//
|
|
// If config.Keys.DisableArchive is true, only job statistics are calculated
|
|
// and returned (no data is written to archive backend).
|
|
func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.Job, error) {
|
|
allMetrics := make([]string, 0)
|
|
metricConfigs := archive.GetCluster(job.Cluster).MetricConfig
|
|
for _, mc := range metricConfigs {
|
|
allMetrics = append(allMetrics, mc.Name)
|
|
}
|
|
|
|
scopes := []schema.MetricScope{schema.MetricScopeNode}
|
|
// FIXME: Add a config option for this
|
|
if job.NumNodes <= 8 {
|
|
// This will add the native scope if core scope is not available
|
|
scopes = append(scopes, schema.MetricScopeCore)
|
|
}
|
|
|
|
if job.NumAcc > 0 {
|
|
scopes = append(scopes, schema.MetricScopeAccelerator)
|
|
}
|
|
|
|
jobData, err := metricdispatch.LoadData(job, allMetrics, scopes, ctx, 0) // 0 Resulotion-Value retrieves highest res (60s)
|
|
if err != nil {
|
|
cclog.Error("Error wile loading job data for archiving")
|
|
return nil, err
|
|
}
|
|
|
|
job.Statistics = schema.JobStatisticsSet{Metrics: make(map[string]schema.JobStatistics)}
|
|
|
|
for metric, data := range jobData.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)
|
|
}
|
|
|
|
// Round AVG Result to 2 Digits
|
|
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,
|
|
},
|
|
Avg: (math.Round((avg/float64(job.NumNodes))*100) / 100),
|
|
Min: min,
|
|
Max: max,
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|