diff --git a/web/frontend/src/Analysis.root.svelte b/web/frontend/src/Analysis.root.svelte index ada3944..3b7272d 100644 --- a/web/frontend/src/Analysis.root.svelte +++ b/web/frontend/src/Analysis.root.svelte @@ -37,14 +37,12 @@ import ScatterPlot from "./generic/plots/Scatter.svelte"; import RooflineHeatmap from "./generic/plots/RooflineHeatmap.svelte"; - const { query: initq } = init(); - - export let filterPresets; + /* Svelte 5 Props */ + let { filterPresets } = $props(); // By default, look at the jobs of the last 6 hours: if (filterPresets?.startTime == null) { if (filterPresets == null) filterPresets = {}; - let now = new Date(Date.now()); let hourAgo = new Date(now); hourAgo.setHours(hourAgo.getHours() - 6); @@ -54,27 +52,12 @@ }; } - let cluster; - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobFilters = []; - let rooflineMaxY; - let colWidth1, colWidth2; - let numBins = 50; - let maxY = -1; - + /* Const Init */ + const { query: initq } = init(); + const client = getContextClient(); const initialized = getContext("initialized"); const globalMetrics = getContext("globalMetrics"); const ccconfig = getContext("cc-config"); - - let metricsInHistograms = ccconfig.analysis_view_histogramMetrics, - metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics; - - $: metrics = [ - ...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]), - ]; - - $: clusterName = cluster?.name ? cluster.name : cluster; - const sortOptions = [ { key: "totalWalltime", label: "Walltime" }, { key: "totalNodeHours", label: "Node Hours" }, @@ -86,7 +69,22 @@ { key: "project", label: "Project ID" }, ]; - let sortSelection = + /* Var Init */ + let availableMetrics = []; + let metricUnits = {}; + let metricScopes = {}; + let rooflineMaxY; + let cluster; + let colWidth1, colWidth2; + let numBins = 50; + let maxY = -1; + + /* State Init */ + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let jobFilters = $state([]); + let metricsInHistograms = $state(ccconfig.analysis_view_histogramMetrics) + let metricsInScatterplots = $state(ccconfig.analysis_view_scatterPlotMetrics) + let sortSelection = $state( sortOptions.find( (option) => option.key == @@ -94,17 +92,20 @@ ) || sortOptions.find( (option) => option.key == ccconfig.analysis_view_selectedTopCategory, - ); - let groupSelection = - groupOptions.find( - (option) => - option.key == - ccconfig[`analysis_view_selectedTopEntity:${filterPresets.cluster}`], - ) || - groupOptions.find( - (option) => option.key == ccconfig.analysis_view_selectedTopEntity, - ); + ) + ); + let groupSelection = $state( + groupOptions.find( + (option) => + option.key == + ccconfig[`analysis_view_selectedTopEntity:${filterPresets.cluster}`], + ) || + groupOptions.find( + (option) => option.key == ccconfig.analysis_view_selectedTopEntity, + ) + ); + /* Init Function */ getContext("on-init")(({ data }) => { if (data != null) { cluster = data.clusters.find((c) => c.name == filterPresets.cluster); @@ -121,120 +122,144 @@ } }); - const client = getContextClient(); + /* Derived Vars */ + let clusterName = $derived(cluster?.name ? cluster.name : cluster); + let metrics = $derived( + [...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])] + ); - $: statsQuery = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!) { - stats: jobsStatistics(filter: $jobFilters) { - totalJobs - shortJobs - totalWalltime - totalNodeHours - totalCoreHours - totalAccHours - histDuration { - count - value - } - histNumCores { - count - value + let statsQuery = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!) { + stats: jobsStatistics(filter: $jobFilters) { + totalJobs + shortJobs + totalWalltime + totalNodeHours + totalCoreHours + totalAccHours + histDuration { + count + value + } + histNumCores { + count + value + } } } - } - `, - variables: { jobFilters }, - }); + `, + variables: { jobFilters }, + }) + ); - $: topQuery = queryStore({ - client: client, - query: gql` - query ( - $jobFilters: [JobFilter!]! - $paging: PageRequest! - $sortBy: SortByAggregate! - $groupBy: Aggregate! - ) { - topList: jobsStatistics( - filter: $jobFilters - page: $paging - sortBy: $sortBy - groupBy: $groupBy + let topQuery = $derived( + queryStore({ + client: client, + query: gql` + query ( + $jobFilters: [JobFilter!]! + $paging: PageRequest! + $sortBy: SortByAggregate! + $groupBy: Aggregate! ) { - id - name - totalWalltime - totalNodeHours - totalCoreHours - totalAccHours + topList: jobsStatistics( + filter: $jobFilters + page: $paging + sortBy: $sortBy + groupBy: $groupBy + ) { + id + name + totalWalltime + totalNodeHours + totalCoreHours + totalAccHours + } } - } - `, - variables: { - jobFilters, - paging: { itemsPerPage: 10, page: 1 }, - sortBy: sortSelection.key.toUpperCase(), - groupBy: groupSelection.key.toUpperCase(), - }, - }); + `, + variables: { + jobFilters, + paging: { itemsPerPage: 10, page: 1 }, + sortBy: sortSelection.key.toUpperCase(), + groupBy: groupSelection.key.toUpperCase(), + }, + }) + ); // Note: Different footprints than those saved in DB per Job -> Caused by Legacy Naming - $: footprintsQuery = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) { - footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) { - timeWeights { - nodeHours - accHours - coreHours - } - metrics { - metric - data + let footprintsQuery = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) { + footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) { + timeWeights { + nodeHours + accHours + coreHours + } + metrics { + metric + data + } } } - } - `, - variables: { jobFilters, metrics }, - }); - - $: rooflineQuery = queryStore({ - client: client, - query: gql` - query ( - $jobFilters: [JobFilter!]! - $rows: Int! - $cols: Int! - $minX: Float! - $minY: Float! - $maxX: Float! - $maxY: Float! - ) { - rooflineHeatmap( - filter: $jobFilters - rows: $rows - cols: $cols - minX: $minX - minY: $minY - maxX: $maxX - maxY: $maxY - ) - } - `, - variables: { - jobFilters, - rows: 50, - cols: 50, - minX: 0.01, - minY: 1, - maxX: 1000, - maxY, - }, + `, + variables: { jobFilters, metrics }, + }) + ); + + let rooflineQuery = $derived( + queryStore({ + client: client, + query: gql` + query ( + $jobFilters: [JobFilter!]! + $rows: Int! + $cols: Int! + $minX: Float! + $minY: Float! + $maxX: Float! + $maxY: Float! + ) { + rooflineHeatmap( + filter: $jobFilters + rows: $rows + cols: $cols + minX: $minX + minY: $minY + maxX: $maxX + maxY: $maxY + ) + } + `, + variables: { + jobFilters, + rows: 50, + cols: 50, + minX: 0.01, + minY: 1, + maxX: 1000, + maxY, + }, + }) + ); + + /* Reactive Effects */ + $effect(() => { + loadMetrics($initialized) + }); + $effect(() => { + updateEntityConfiguration(groupSelection.key); + }); + $effect(() => { + updateCategoryConfiguration(sortSelection.key); }); + /* Functions */ const updateConfigurationMutation = ({ name, value }) => { return mutationStore({ client: client, @@ -287,9 +312,6 @@ } } - let availableMetrics = []; - let metricUnits = {}; - let metricScopes = {}; function loadMetrics(isInitialized) { if (!isInitialized) return availableMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster.name))] @@ -299,10 +321,7 @@ } } - $: loadMetrics($initialized) - $: updateEntityConfiguration(groupSelection.key); - $: updateCategoryConfiguration(sortSelection.key); - + /* On Mount */ onMount(() => filterComponent.updateFilters()); @@ -329,7 +348,7 @@ {filterPresets} disableClusterSelection={true} startTimeQuickSelect={true} - on:update-filters={({ detail }) => { + applyFilters={(detail) => { jobFilters = detail.filters; }} /> diff --git a/web/frontend/src/List.root.svelte b/web/frontend/src/List.root.svelte index 1a88b36..bcebd14 100644 --- a/web/frontend/src/List.root.svelte +++ b/web/frontend/src/List.root.svelte @@ -31,10 +31,8 @@ } from "./generic/utils.js"; import Filters from "./generic/Filters.svelte"; - const {} = init(); - - export let type; - export let filterPresets; + /* Svelte 5 Props */ + let { type, filterPresets } = $props(); // By default, look at the jobs of the last 30 days: if (filterPresets?.startTime == null) { @@ -51,35 +49,38 @@ "Invalid list type provided!", ); - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobFilters = []; - let nameFilter = ""; - let sorting = { field: "totalJobs", direction: "down" }; - + /* Const Init */ + const {} = init(); const client = getContextClient(); - $: stats = queryStore({ - client: client, - query: gql` - query($jobFilters: [JobFilter!]!) { - rows: jobsStatistics(filter: $jobFilters, groupBy: ${type}) { - id - name - totalJobs - totalWalltime - totalCoreHours - totalAccHours - } - }`, - variables: { jobFilters }, - }); - function changeSorting(event, field) { - let target = event.target; - while (target.tagName != "BUTTON") target = target.parentElement; + /* State Init*/ + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let jobFilters = $state([]); + let nameFilter = $state(""); + let sorting = $state({ field: "totalJobs", direction: "down" }); - let direction = target.children[0].className.includes("up") ? "down" : "up"; - target.children[0].className = `bi-sort-numeric-${direction}`; - sorting = { field, direction }; + /* Derived Vars */ + let stats = $derived( + queryStore({ + client: client, + query: gql` + query($jobFilters: [JobFilter!]!) { + rows: jobsStatistics(filter: $jobFilters, groupBy: ${type}) { + id + name + totalJobs + totalWalltime + totalCoreHours + totalAccHours + } + }`, + variables: { jobFilters }, + }) + ); + + /* Functions */ + function changeSorting(field) { + sorting = { field, direction: sorting?.direction == "down" ? "up" : "down" }; } function sort(stats, sorting, nameFilter) { @@ -87,10 +88,10 @@ ? (a, b) => b.id.localeCompare(a.id) : (a, b) => a.id.localeCompare(b.id) - // "-50": Forces empty strings to the end of the list + // Force empty or undefined strings to the end of the list const nameCmp = sorting.direction == "up" - ? (a, b) => (a.name == '') ? -50 : b.name.localeCompare(a.name) - : (a, b) => (b.name == '') ? -50 : a.name.localeCompare(b.name) + ? (a, b) => !a?.name ? 1 : (!b?.name ? -1 : (b.name.localeCompare(a.name))) + : (a, b) => !a?.name ? 1 : (!b?.name ? -1 : (a.name.localeCompare(b.name))) const intCmp = sorting.direction == "up" ? (a, b) => a[sorting.field] - b[sorting.field] @@ -105,6 +106,7 @@ } } + /* On Mount */ onMount(() => filterComponent.updateFilters()); @@ -129,7 +131,7 @@ {filterPresets} startTimeQuickSelect={true} menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up" - on:update-filters={({ detail }) => { + applyFilters={(detail) => { jobFilters = detail.filters; }} /> @@ -147,9 +149,14 @@ {#if type == "USER"} @@ -158,9 +165,13 @@ {/if} @@ -169,9 +180,14 @@ @@ -179,9 +195,13 @@ @@ -189,9 +209,13 @@ @@ -199,9 +223,13 @@ diff --git a/web/frontend/src/User.root.svelte b/web/frontend/src/User.root.svelte index 0fad6cc..a0f0fe2 100644 --- a/web/frontend/src/User.root.svelte +++ b/web/frontend/src/User.root.svelte @@ -42,71 +42,90 @@ import TextFilter from "./generic/helper/TextFilter.svelte" import Refresher from "./generic/helper/Refresher.svelte"; + /* Svelte 5 Props */ + let { user, filterPresets } = $props(); + + /* Const Init */ const { query: initq } = init(); - const ccconfig = getContext("cc-config"); - - export let user; - export let filterPresets; - - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobList; - let jobFilters = []; - let matchedListJobs = 0; - let sorting = { field: "startTime", type: "col", order: "DESC" }, - isSortingOpen = false; - let metrics = ccconfig.plot_list_selectedMetrics, - isMetricsSelectionOpen = false; - let isHistogramSelectionOpen = false; - let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null; - let showFootprint = filterPresets.cluster - ? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`] - : !!ccconfig.plot_list_showFootprint; - - let numDurationBins = "1h"; - let numMetricBins = 10; - let durationBinOptions = ["1m","10m","1h","6h","12h"]; - let metricBinOptions = [10, 20, 50, 100]; - - $: selectedHistograms = selectedCluster - ? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] ) - : ccconfig['user_view_histogramMetrics'] || []; - const client = getContextClient(); - $: stats = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) { - jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) { - totalJobs - shortJobs - totalWalltime - totalCoreHours - histDuration { - count - value - } - histNumNodes { - count - value - } - histMetrics { - metric - unit - stat - data { - min - max + const durationBinOptions = ["1m","10m","1h","6h","12h"]; + const metricBinOptions = [10, 20, 50, 100]; + + /* State Init */ + // List & Control Vars + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let jobFilters = $state([]); + let jobList = $state(null); + let matchedListJobs = $state(0); + let isSortingOpen = $state(false); + let isMetricsSelectionOpen = $state(false); + let sorting = $state({ field: "startTime", type: "col", order: "DESC" }); + let selectedCluster = $state(filterPresets?.cluster ? filterPresets.cluster : null); + let metrics = $state(filterPresets.cluster + ? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || + ccconfig.plot_list_selectedMetrics + : ccconfig.plot_list_selectedMetrics + ); + let showFootprint = $state(filterPresets.cluster + ? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`] + : !!ccconfig.plot_list_showFootprint + ); + + // Histogram Vars + let isHistogramSelectionOpen = $state(false); + let numDurationBins = $state("1h"); + let numMetricBins = $state(10); + + // Compare Vars (TODO) + // let jobCompare = $state(null); + // let showCompare = $state(false); + // let selectedJobs = $state([]); + // let filterBuffer = $state([]); + // let matchedCompareJobs = $state(0); + + /* Derived Vars */ + let selectedHistograms = $derived(selectedCluster + ? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] ) + : ccconfig['user_view_histogramMetrics'] || []); + + let stats = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) { + jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) { + totalJobs + shortJobs + totalWalltime + totalCoreHours + histDuration { count - bin + value + } + histNumNodes { + count + value + } + histMetrics { + metric + unit + stat + data { + min + max + count + bin + } } } } - } - `, - variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins }, - }); + `, + variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins }, + }) + ); + /* On Mount */ onMount(() => filterComponent.updateFilters()); @@ -129,13 +148,13 @@ - @@ -143,11 +162,11 @@ { + applyFilters={(detail) => { jobFilters = [...detail.filters, { user: { eq: user.username } }]; selectedCluster = jobFilters[0]?.cluster ? jobFilters[0].cluster.eq @@ -173,11 +192,11 @@ filterComponent.updateFilters(detail)} + setFilter={(filter) => filterComponent.updateFilters(filter)} /> - { + { jobList.refreshJobs() jobList.refreshAllMetrics() }} /> @@ -269,7 +288,7 @@ outline color="secondary" class="w-100" - on:click={() => (isHistogramSelectionOpen = true)} + onclick={() => (isHistogramSelectionOpen = true)} > Select Histograms diff --git a/web/frontend/src/generic/filters/Cluster.svelte b/web/frontend/src/generic/filters/Cluster.svelte index 8112b62..894ce0c 100644 --- a/web/frontend/src/generic/filters/Cluster.svelte +++ b/web/frontend/src/generic/filters/Cluster.svelte @@ -103,15 +103,17 @@ setFilter({ cluster: pendingCluster, partition: pendingPartition }); }}>Close & Apply - + {#if !disableClusterSelection} + + {/if}