From 769da61570c9e28dca421de348ab982f723400e9 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Thu, 27 Aug 2026 12:17:34 +0200 Subject: [PATCH] fix(frontend): drop dead resample trigger/resolutions config config.ResampleConfig stopped emitting the trigger and resolutions fields, but MetricPlot still read them. resampleResolutions and resampleMinimum were therefore always null, which made the array-based resolution branch in the setScale hook unreachable and the timestep !== resampleMinimum guard always true. Keep only the policy-based branch and derive the zoom trigger straight from targetPoints. configs/config-large.json still carried the removed minimum-points, trigger and resolutions keys, which DisallowUnknownFields now rejects, so starting against it aborted. Replace them with the current keys and add a test that loads the file, so the example configs cannot drift out of the schema unnoticed again. Co-Authored-By: Claude Opus 5 (1M context) --- configs/config-large.json | 5 ++-- internal/config/config_test.go | 19 +++++++++++++ .../src/generic/plots/MetricPlot.svelte | 27 +++++++------------ web/web.go | 2 +- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/configs/config-large.json b/configs/config-large.json index f57b32d1..79ba02a3 100644 --- a/configs/config-large.json +++ b/configs/config-large.json @@ -4,9 +4,8 @@ "short-running-jobs-duration": 300, "emission-constant": 317, "resampling": { - "minimum-points": 600, - "trigger": 30, - "resolutions": [600, 300, 120, 60] + "default-policy": "medium", + "default-algo": "average" }, "api-subjects": { "subject-job-event": "cc.job.event", diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 14eb570f..a2a3fb3d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -41,6 +41,25 @@ func TestInitMinimal(t *testing.T) { } } +// config-large.json carried the removed resampling keys (minimum-points, +// trigger, resolutions), which DisallowUnknownFields rejects. +func TestInitLarge(t *testing.T) { + fp := "../../configs/config-large.json" + ccconf.Init(fp) + if cfg := ccconf.GetPackageConfig("main"); cfg != nil { + Init(cfg) + } else { + cclog.Abort("Main configuration must be present") + } + + if Keys.EnableResampling == nil { + t.Fatal("resampling config missing") + } + if Keys.EnableResampling.DefaultAlgo != "average" { + t.Errorf("wrong default algo\ngot: %s \nwant: average", Keys.EnableResampling.DefaultAlgo) + } +} + func TestTargetPointsForPolicy(t *testing.T) { tests := []struct { policy string diff --git a/web/frontend/src/generic/plots/MetricPlot.svelte b/web/frontend/src/generic/plots/MetricPlot.svelte index e937f32f..63e31950 100644 --- a/web/frontend/src/generic/plots/MetricPlot.svelte +++ b/web/frontend/src/generic/plots/MetricPlot.svelte @@ -73,10 +73,11 @@ const subClusterTopology = $derived(getContext("getHardwareTopology")(cluster, subCluster)); const metricConfig = $derived(getContext("getMetricConfig")(cluster, subCluster, metric)); const usesMeanStatsSeries = $derived((statisticsSeries?.mean && statisticsSeries.mean.length != 0)); - const resampleTrigger = $derived(resampleConfig?.trigger ? Number(resampleConfig.trigger) : (resampleConfig?.targetPoints ? Math.floor(resampleConfig.targetPoints / 4) : null)); - const resampleResolutions = $derived(resampleConfig?.resolutions ? [...resampleConfig.resolutions] : null); - const resampleMinimum = $derived(resampleConfig?.resolutions ? Math.min(...resampleConfig.resolutions) : null); + const nativeTimestep = $derived(metricConfig?.timestep || timestep); const resampleTargetPoints = $derived(resampleConfig?.targetPoints ? Number(resampleConfig.targetPoints) : null); + // Zoom in far enough that fewer than a quarter of the target point count is + // visible, and a finer resolution is requested from the backend. + const resampleTrigger = $derived(resampleTargetPoints ? Math.floor(resampleTargetPoints / 4) : null); const useStatsSeries = $derived(!!statisticsSeries); // Display Stats Series By Default if Exists const thresholds = $derived(findJobAggregationThresholds( subClusterTopology, @@ -515,21 +516,11 @@ (u, key) => { // If ZoomResample is Configured && Not System/Node View if (resampleConfig && !forNode && key === 'x') { const numX = (u.series[0].idxs[1] - u.series[0].idxs[0]) - if (numX <= resampleTrigger && timestep !== resampleMinimum) { - let newRes; - if (resampleTargetPoints && !resampleResolutions) { - // Policy-based: compute resolution dynamically from visible window - const visibleDuration = (u.scales.x.max - u.scales.x.min); - const nativeTimestep = metricConfig?.timestep || timestep; - newRes = Math.ceil(visibleDuration / resampleTargetPoints / nativeTimestep) * nativeTimestep; - if (newRes < nativeTimestep) newRes = nativeTimestep; - } else if (resampleResolutions) { - // Array-based: find closest configured resolution - const target = (numX * timestep) / resampleTrigger; - newRes = resampleResolutions.reduce(function(prev, curr) { - return (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev); - }); - } + if (resampleTargetPoints && numX <= resampleTrigger) { + // Compute the resolution for the visible window + const visibleDuration = (u.scales.x.max - u.scales.x.min); + let newRes = Math.ceil(visibleDuration / resampleTargetPoints / nativeTimestep) * nativeTimestep; + if (newRes < nativeTimestep) newRes = nativeTimestep; // Prevents non-required dispatches if (newRes && timestep !== newRes) { onZoom({ diff --git a/web/web.go b/web/web.go index 685256f9..4e695386 100644 --- a/web/web.go +++ b/web/web.go @@ -294,7 +294,7 @@ type Page struct { FilterPresets map[string]any // For pages with the Filter component, this can be used to set initial filters. Infos map[string]any // For generic use (e.g. username for /monitoring/user/, job id for /monitoring/job/) Config map[string]any // UI settings for the currently logged in user (e.g. line width, ...) - Resampling *config.ResampleConfig // If not nil, defines resampling trigger and resolutions + Resampling *config.ResampleConfig // If not nil, defines the target point count for zoom resampling Redirect string // The originally requested URL, for intermediate login handling FooterLinks FooterLinks // Resolved legal links for the site footer }