diff --git a/web/frontend/src/Analysis.root.svelte b/web/frontend/src/Analysis.root.svelte index 83c6754..afad314 100644 --- a/web/frontend/src/Analysis.root.svelte +++ b/web/frontend/src/Analysis.root.svelte @@ -562,8 +562,20 @@ + + {#snippet histoGridContent(item)} + + {/snippet} + ({ metric, ...binsFromFootprint( @@ -576,17 +588,8 @@ ), }))} itemsPerRow={ccconfig.plot_view_plotsPerRow} - > - - + gridContent={histoGridContent} + />
@@ -604,9 +607,19 @@ + {#snippet metricsGridContent(item)} + + {/snippet} + ({ m1, f1: $footprintsQuery.data.footprints.metrics.find( @@ -618,18 +631,8 @@ ).data, }))} itemsPerRow={ccconfig.plot_view_plotsPerRow} - > - - + gridContent={metricsGridContent} + /> {/if} diff --git a/web/frontend/src/Job.root.svelte b/web/frontend/src/Job.root.svelte index 427c9ae..1de0171 100644 --- a/web/frontend/src/Job.root.svelte +++ b/web/frontend/src/Job.root.svelte @@ -9,10 +9,11 @@ --> {#each items as item} - + + {@render gridContent(item)} {/each} diff --git a/web/frontend/src/generic/joblist/JobInfo.svelte b/web/frontend/src/generic/joblist/JobInfo.svelte index 5886c61..bb8b90b 100644 --- a/web/frontend/src/generic/joblist/JobInfo.svelte +++ b/web/frontend/src/generic/joblist/JobInfo.svelte @@ -20,7 +20,7 @@ username = null, authlevel= null, roles = null, - isSelected = null, + isSelected = $bindable(), showSelect = false, } = $props(); @@ -89,10 +89,8 @@ }}> {#if isSelected} - {:else if isSelected == false} - - {:else} - + {:else } + {/if} import { queryStore, gql, getContextClient } from "@urql/svelte"; - import { getContext, createEventDispatcher } from "svelte"; + import { getContext } from "svelte"; import { Card, Spinner } from "@sveltestrap/sveltestrap"; import { maxScope, checkMetricDisabled } from "../utils.js"; import JobInfo from "./JobInfo.svelte"; import MetricPlot from "../plots/MetricPlot.svelte"; import JobFootprint from "../helper/JobFootprint.svelte"; - export let job; - export let metrics; - export let plotWidth; - export let plotHeight = 275; - export let showFootprint; - export let triggerMetricRefresh = false; - export let previousSelect = false; + /* Svelte 5 Props */ + let { + triggerMetricRefresh = false, + job, + metrics, + plotWidth, + plotHeight = 275, + showFootprint, + previousSelect = false, + selectJob, + unselectJob + } = $props(); - const dispatch = createEventDispatcher(); - const resampleConfig = getContext("resampling") || null; - const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0; - - let { id } = job; - let scopes = job.numNodes == 1 - ? job.numAcc >= 1 + /* Const Init */ + const client = getContextClient(); + const jobId = job.id; + const cluster = getContext("clusters"); + const scopes = (job.numNodes == 1) + ? (job.numAcc >= 1) ? ["core", "accelerator"] : ["core"] : ["node"]; - let selectedResolution = resampleDefault; - let zoomStates = {}; - let thresholdStates = {}; - - $: isSelected = previousSelect || null; - - const cluster = getContext("clusters").find((c) => c.name == job.cluster); - const client = getContextClient(); + const resampleConfig = getContext("resampling") || null; + const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0; const query = gql` query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!, $selectedResolution: Int) { jobMetrics(id: $id, metrics: $metrics, scopes: $scopes, resolution: $selectedResolution) { @@ -77,52 +75,59 @@ } `; - function handleZoom(detail, metric) { - if ( // States have to differ, causes deathloop if just set - (zoomStates[metric]?.x?.min !== detail?.lastZoomState?.x?.min) && - (zoomStates[metric]?.y?.max !== detail?.lastZoomState?.y?.max) - ) { - zoomStates[metric] = {...detail.lastZoomState} + /* State Init */ + let selectedResolution = $state(resampleDefault); + let zoomStates = $state({}); + let thresholdStates = $state({}); + + /* Derived */ + let isSelected = $derived(previousSelect); + let metricsQuery = $derived(queryStore({ + client: client, + query: query, + variables: { id: jobId, metrics, scopes, selectedResolution }, + }) + ); + + /* Effects */ + $effect(() => { + if (job.state === 'running' && triggerMetricRefresh === true) { + refreshMetrics(); } + }); - if ( // States have to differ, causes deathloop if just set - detail?.lastThreshold && - thresholdStates[metric] !== detail.lastThreshold - ) { // Handle to correctly reset on summed metric scope change - thresholdStates[metric] = detail.lastThreshold; - } + $effect(() => { + if (isSelected == true && previousSelect == false) { + selectJob(jobId) + } else if (isSelected == false && previousSelect == true) { + unselectJob(jobId) + } + }); - if (detail?.newRes) { // Triggers GQL - selectedResolution = detail.newRes + /* Functions */ + function handleZoom(detail, metric) { + // Buffer last zoom state to allow seamless zoom on rerender + // console.log('Update zoomState for/with:', metric, {...detail.lastZoomState}) + zoomStates[metric] = detail?.lastZoomState ? {...detail.lastZoomState} : null; + // Handle to correctly reset on summed metric scope change + // console.log('Update thresholdState for/with:', metric, detail.lastThreshold) + thresholdStates[metric] = detail?.lastThreshold ? detail.lastThreshold : null; + // Triggers GQL + if (detail?.newRes) { + // console.log('Update selectedResolution for/with:', metric, detail.newRes) + selectedResolution = detail.newRes; } } - $: metricsQuery = queryStore({ - client: client, - query: query, - variables: { id, metrics, scopes, selectedResolution }, - }); - function refreshMetrics() { metricsQuery = queryStore({ client: client, query: query, - variables: { id, metrics, scopes, selectedResolution }, + variables: { id: jobId, metrics, scopes, selectedResolution }, // requestPolicy: 'network-only' // use default cache-first for refresh }); } - $: if (job.state === 'running' && triggerMetricRefresh === true) { - refreshMetrics(); - } - - $: if (isSelected == true && previousSelect == false) { - dispatch("select-job", job.id) - } else if (isSelected == false && previousSelect == true) { - dispatch("unselect-job", job.id) - } - - // Helper const selectScope = (jobMetrics) => jobMetrics.reduce( (a, b) => @@ -146,6 +151,7 @@ .map((jobMetric) => { if (jobMetric.data) { return { + name: jobMetric.data.name, disabled: checkMetricDisabled( jobMetric.data.name, job.cluster, @@ -157,7 +163,6 @@ return jobMetric; } }); - @@ -191,19 +196,19 @@ /> {/if} - {#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)} + {#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric?.name || i)} {#if metric.disabled == false && metric.data} { handleZoom(detail, metric.data.name) }} + on:zoom={({detail}) => handleZoom(detail, metric.data.name)} height={plotHeight} timestep={metric.data.metric.timestep} scope={metric.data.scope} series={metric.data.metric.series} statisticsSeries={metric.data.metric.statisticsSeries} metric={metric.data.name} - {cluster} + cluster={cluster.find((c) => c.name == job.cluster)} subCluster={job.subCluster} isShared={job.exclusive != 1} numhwthreads={job.numHWThreads} diff --git a/web/frontend/src/generic/plots/MetricPlot.svelte b/web/frontend/src/generic/plots/MetricPlot.svelte index 56e819d..50bb3b1 100644 --- a/web/frontend/src/generic/plots/MetricPlot.svelte +++ b/web/frontend/src/generic/plots/MetricPlot.svelte @@ -440,7 +440,7 @@ /* IF Zoom Enabled */ if (resampleConfig) { u.over.addEventListener("dblclick", (e) => { - // console.log('Dispatch Reset') + // console.log('Dispatch: Zoom Reset') dispatch('zoom', { lastZoomState: { x: { time: false }, @@ -506,7 +506,7 @@ }); // Prevents non-required dispatches if (timestep !== closest) { - // console.log('Dispatch Zoom with Res from / to', timestep, closest) + // console.log('Dispatch: Zoom with Res from / to', timestep, closest) dispatch('zoom', { newRes: closest, lastZoomState: u?.scales, @@ -514,6 +514,7 @@ }); } } else { + // console.log('Dispatch: Zoom Update States') dispatch('zoom', { lastZoomState: u?.scales, lastThreshold: thresholds?.normal diff --git a/web/frontend/src/generic/plots/Scatter.svelte b/web/frontend/src/generic/plots/Scatter.svelte index 514223b..76c1a17 100644 --- a/web/frontend/src/generic/plots/Scatter.svelte +++ b/web/frontend/src/generic/plots/Scatter.svelte @@ -182,6 +182,6 @@ -
- +
+
diff --git a/web/frontend/src/generic/select/DoubleRangeSlider.svelte b/web/frontend/src/generic/select/DoubleRangeSlider.svelte index 0022673..7554bdf 100644 --- a/web/frontend/src/generic/select/DoubleRangeSlider.svelte +++ b/web/frontend/src/generic/select/DoubleRangeSlider.svelte @@ -18,6 +18,7 @@ Changes #2: Rewritten for Svelte 5, removed bodyHandler --> @@ -175,13 +148,13 @@ {metricName} ({unit}) @@ -194,37 +167,37 @@ {/if} -{#key series} - {#if $metricData?.fetching == true} +{#key selectedSeries} + {#if $metricData.fetching} - {:else if error != null} - {error.message} - {:else if series != null && !patternMatches} + {:else if $metricData.error} + {$metricData.error.message} + {:else if selectedSeries != null && !patternMatches} { handleZoom(detail) }} + on:zoom={({detail}) => handleZoom(detail)} cluster={job.cluster} subCluster={job.subCluster} - timestep={data.timestep} + timestep={selectedData.timestep} scope={selectedScope} metric={metricName} numaccs={job.numAcc} numhwthreads={job.numHWThreads} - {series} + series={selectedSeries} {isShared} {zoomState} {thresholdState} /> {:else if statsSeries[selectedScopeIndex] != null && patternMatches} { handleZoom(detail) }} + on:zoom={({detail}) => handleZoom(detail)} cluster={job.cluster} subCluster={job.subCluster} - timestep={data.timestep} + timestep={selectedData.timestep} scope={selectedScope} metric={metricName} numaccs={job.numAcc} numhwthreads={job.numHWThreads} - {series} + series={selectedSeries} {isShared} {zoomState} {thresholdState} diff --git a/web/frontend/src/systems/NodeList.svelte b/web/frontend/src/systems/NodeList.svelte index 0764394..cf8d870 100644 --- a/web/frontend/src/systems/NodeList.svelte +++ b/web/frontend/src/systems/NodeList.svelte @@ -108,7 +108,7 @@ })); /* Effects */ - $effect(() => { + $effect(() => { if (!usePaging) { window.addEventListener('scroll', () => { let { diff --git a/web/frontend/src/systems/nodelist/NodeInfo.svelte b/web/frontend/src/systems/nodelist/NodeInfo.svelte index 1230ce7..9fba9f4 100644 --- a/web/frontend/src/systems/nodelist/NodeInfo.svelte +++ b/web/frontend/src/systems/nodelist/NodeInfo.svelte @@ -16,28 +16,37 @@ CardBody, Input, InputGroup, - InputGroupText, } from "@sveltestrap/sveltestrap"; + InputGroupText, + } from "@sveltestrap/sveltestrap"; import { scramble, - scrambleNames, } from "../../generic/utils.js"; + scrambleNames, + } from "../../generic/utils.js"; - export let cluster; - export let subCluster - export let hostname; - export let dataHealth; - export let nodeJobsData = null; + /* Svelte 5 Props */ + let { + cluster, + subCluster, + hostname, + dataHealth, + nodeJobsData = null, + } = $props(); + /* Const Init */ // Not at least one returned, selected metric: NodeHealth warning const healthWarn = !dataHealth.includes(true); // At least one non-returned selected metric: Metric config error? const metricWarn = dataHealth.includes(false); - let userList; - let projectList; - $: if (nodeJobsData) { - userList = Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.user) : j.user))).sort((a, b) => a.localeCompare(b)); - projectList = Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.project) : j.project))).sort((a, b) => a.localeCompare(b)); - } + /* Derived */ + const userList = $derived(nodeJobsData + ? Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.user) : j.user))).sort((a, b) => a.localeCompare(b)) + : [] + ); + const projectList = $derived(nodeJobsData + ? Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.project) : j.project))).sort((a, b) => a.localeCompare(b)) + : []); + diff --git a/web/frontend/src/systems/nodelist/NodeListRow.svelte b/web/frontend/src/systems/nodelist/NodeListRow.svelte index 7adb282..571ca63 100644 --- a/web/frontend/src/systems/nodelist/NodeListRow.svelte +++ b/web/frontend/src/systems/nodelist/NodeListRow.svelte @@ -18,10 +18,14 @@ import MetricPlot from "../../generic/plots/MetricPlot.svelte"; import NodeInfo from "./NodeInfo.svelte"; - export let cluster; - export let nodeData; - export let selectedMetrics; + /* Svelte 5 Props */ + let { + cluster, + nodeData, + selectedMetrics, + } = $props(); + /* Const Init */ const client = getContextClient(); const paging = { itemsPerPage: 50, page: 1 }; const sorting = { field: "startTime", type: "col", order: "DESC" }; @@ -30,7 +34,6 @@ { node: { contains: nodeData.host } }, { state: ["running"] }, ]; - const nodeJobsQuery = gql` query ( $filter: [JobFilter!]! @@ -53,13 +56,19 @@ } `; - $: nodeJobsData = queryStore({ - client: client, - query: nodeJobsQuery, - variables: { paging, sorting, filter }, - }); + /* Derived */ + const nodeJobsData = $derived(queryStore({ + client: client, + query: nodeJobsQuery, + variables: { paging, sorting, filter }, + }) + ); - // Helper + let extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null); + let refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(nodeData.metrics) : null); + let dataHealth = $derived(refinedData.filter((rd) => rd.disabled === false).map((enabled) => (enabled.data.metric.series.length > 0))); + + /* Functions */ const selectScope = (nodeMetrics) => nodeMetrics.reduce( (a, b) => @@ -89,15 +98,8 @@ } }); - let refinedData; - let dataHealth; - $: if (nodeData?.metrics) { - refinedData = sortAndSelectScope(nodeData?.metrics) - dataHealth = refinedData.filter((rd) => rd.disabled === false).map((enabled) => (enabled.data.metric.series.length > 0)) - } - - let extendedLegendData = null; - $: if ($nodeJobsData?.data) { + function buildExtendedLegend() { + let pendingExtendedLegendData = null // Build Extended for allocated nodes [Commented: Only Build extended Legend For Shared Nodes] if ($nodeJobsData.data.jobs.count >= 1) { // "&& !$nodeJobsData.data.jobs.items[0].exclusive)" const accSet = Array.from(new Set($nodeJobsData.data.jobs.items @@ -107,11 +109,11 @@ ) )).flat(2) - extendedLegendData = {} + pendingExtendedLegendData = {}; for (const accId of accSet) { const matchJob = $nodeJobsData.data.jobs.items.find((i) => i.resources.find((r) => r.accelerators.includes(accId))) const matchUser = matchJob?.user ? matchJob.user : null - extendedLegendData[accId] = { + pendingExtendedLegendData[accId] = { user: (scrambleNames && matchUser) ? scramble(matchUser) : (matchUser ? matchUser : '-'), @@ -120,6 +122,7 @@ } // Theoretically extendable for hwthreadIDs } + return pendingExtendedLegendData; }