mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-03-21 07:17:30 +01:00
Replace explicit resampling config with policy based approach
Entire-Checkpoint: f69e38210bb1
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
Card,
|
||||
CardTitle,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { getContext } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
@@ -25,6 +26,9 @@
|
||||
displayMessage = $bindable(),
|
||||
updateSetting
|
||||
} = $props();
|
||||
|
||||
const resampleConfig = getContext("resampling");
|
||||
const resamplingEnabled = !!resampleConfig;
|
||||
</script>
|
||||
|
||||
<Row cols={3} class="p-2 g-2">
|
||||
@@ -219,4 +223,92 @@
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{#if resamplingEnabled}
|
||||
<!-- RESAMPLE POLICY -->
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<form
|
||||
id="resample-policy-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#resample-policy-form",
|
||||
target: "rsp",
|
||||
})}
|
||||
>
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Resample Policy</div>
|
||||
{#if displayMessage && message.target == "rsp"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plotConfiguration_resamplePolicy" />
|
||||
<div class="mb-3">
|
||||
{#each [["", "Default"], ["low", "Low"], ["medium", "Medium"], ["high", "High"]] as [val, label]}
|
||||
<div>
|
||||
<input type="radio" id="rsp-{val || 'default'}" name="value" value={JSON.stringify(val)}
|
||||
checked={(!config.plotConfiguration_resamplePolicy && val === "") || config.plotConfiguration_resamplePolicy === val} />
|
||||
<label for="rsp-{val || 'default'}">{label}</label>
|
||||
</div>
|
||||
{/each}
|
||||
<div id="resamplePolicyHelp" class="form-text">
|
||||
Controls how many data points are shown in metric plots. Low = fast overview (~200 points), Medium = balanced (~500), High = maximum detail (~1000).
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<!-- RESAMPLE ALGORITHM -->
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<form
|
||||
id="resample-algo-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#resample-algo-form",
|
||||
target: "rsa",
|
||||
})}
|
||||
>
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Resample Algorithm</div>
|
||||
{#if displayMessage && message.target == "rsa"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plotConfiguration_resampleAlgo" />
|
||||
<div class="mb-3">
|
||||
{#each [["", "Default"], ["lttb", "LTTB"], ["average", "Average"], ["simple", "Simple"]] as [val, label]}
|
||||
<div>
|
||||
<input type="radio" id="rsa-{val || 'default'}" name="value" value={JSON.stringify(val)}
|
||||
checked={(!config.plotConfiguration_resampleAlgo && val === "") || config.plotConfiguration_resampleAlgo === val} />
|
||||
<label for="rsa-{val || 'default'}">{label}</label>
|
||||
</div>
|
||||
{/each}
|
||||
<div id="resampleAlgoHelp" class="form-text">
|
||||
Algorithm used when downsampling time-series data. LTTB preserves visual shape, Average smooths data, Simple picks every Nth point.
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
@@ -73,9 +73,10 @@
|
||||
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) : null);
|
||||
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 resampleTargetPoints = $derived(resampleConfig?.targetPoints ? Number(resampleConfig.targetPoints) : null);
|
||||
const useStatsSeries = $derived(!!statisticsSeries); // Display Stats Series By Default if Exists
|
||||
const thresholds = $derived(findJobAggregationThresholds(
|
||||
subClusterTopology,
|
||||
@@ -515,24 +516,29 @@
|
||||
if (resampleConfig && !forNode && key === 'x') {
|
||||
const numX = (u.series[0].idxs[1] - u.series[0].idxs[0])
|
||||
if (numX <= resampleTrigger && timestep !== resampleMinimum) {
|
||||
/* Get closest zoom level; prevents multiple iterative zoom requests for big zoom-steps (e.g. 600 -> 300 -> 120 -> 60) */
|
||||
// Which resolution to theoretically request to achieve 30 or more visible data points:
|
||||
const target = (numX * timestep) / resampleTrigger
|
||||
// Which configured resolution actually matches the closest to theoretical target:
|
||||
const closest = resampleResolutions.reduce(function(prev, curr) {
|
||||
return (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
// Prevents non-required dispatches
|
||||
if (timestep !== closest) {
|
||||
// console.log('Dispatch: Zoom with Res from / to', timestep, closest)
|
||||
if (newRes && timestep !== newRes) {
|
||||
onZoom({
|
||||
newRes: closest,
|
||||
newRes: newRes,
|
||||
lastZoomState: u?.scales,
|
||||
lastThreshold: thresholds?.normal
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// console.log('Dispatch: Zoom Update States')
|
||||
onZoom({
|
||||
lastZoomState: u?.scales,
|
||||
lastThreshold: thresholds?.normal
|
||||
|
||||
Reference in New Issue
Block a user