2022-06-22 11:20:57 +02:00
< script >
2023-08-09 12:42:25 +02:00
import { init , convert2uplot } from './utils.js'
2022-06-22 11:20:57 +02:00
import { getContext , onMount } from 'svelte'
2023-05-03 16:41:17 +02:00
import { queryStore , gql , getContextClient } from '@urql/svelte'
2022-06-22 11:20:57 +02:00
import { Row , Col , Spinner , Card , Table } from 'sveltestrap'
import Filters from './filters/Filters.svelte'
import PlotSelection from './PlotSelection.svelte'
2023-08-09 12:42:25 +02:00
import Histogramuplot from './plots/Histogramuplot.svelte'
2023-08-10 18:06:19 +02:00
import Pie from './plots/Pie.svelte'
import { binsFromFootprint } from './plots/Histogram.svelte'
2022-06-22 11:20:57 +02:00
import ScatterPlot from './plots/Scatter.svelte'
import PlotTable from './PlotTable.svelte'
import Roofline from './plots/Roofline.svelte'
const { query : initq } = init()
export let filterPresets
// 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)
filterPresets.startTime = { from : hourAgo . toISOString (), to : now.toISOString () }
}
2023-06-06 16:58:08 +02:00
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;
2023-08-10 18:06:19 +02:00
let colWidth1, colWidth2, colWidth3, colWidth4;
2023-06-06 16:58:08 +02:00
let numBins = 50;
let maxY = -1;
2023-03-30 15:21:35 +02:00
const ccconfig = getContext('cc-config')
const metricConfig = getContext('metrics')
2022-06-22 11:20:57 +02:00
let metricsInHistograms = ccconfig.analysis_view_histogramMetrics,
metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics
$: metrics = [...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])]
getContext('on-init')(({ data } ) => {
if (data != null) {
cluster = data.clusters.find(c => c.name == filterPresets.cluster)
console.assert(cluster != null, `This cluster could not be found: ${ filterPresets . cluster } `)
2023-04-12 10:57:32 +02:00
rooflineMaxY = cluster.subClusters.reduce((max, part) => Math.max(max, part.flopRateSimd.value), 0)
2023-05-09 15:01:56 +02:00
maxY = rooflineMaxY
2022-06-22 11:20:57 +02:00
}
})
2023-05-09 15:01:56 +02:00
const client = getContextClient();
$: statsQuery = queryStore({
client: client,
2023-05-03 16:41:17 +02:00
query: gql`
2023-06-06 16:58:08 +02:00
query($jobFilters: [JobFilter!]!) {
stats: jobsStatistics(filter: $jobFilters) {
2023-05-12 11:19:37 +02:00
totalJobs
shortJobs
totalWalltime
totalCoreHours
histDuration { count , value }
histNumNodes { count , value }
}
2023-06-09 12:42:01 +02:00
topUsers: jobsCount(filter: $jobFilters, groupBy: USER, weight: NODE_HOURS, limit: 5) { name , count }
2022-06-22 11:20:57 +02:00
}
2023-05-12 11:19:37 +02:00
`,
2023-06-06 16:58:08 +02:00
variables: { jobFilters }
2023-05-03 16:41:17 +02:00
})
2022-06-22 11:20:57 +02:00
2023-05-09 15:01:56 +02:00
$: footprintsQuery = queryStore({
client: client,
2023-05-03 16:41:17 +02:00
query: gql`
2023-06-06 16:58:08 +02:00
query($jobFilters: [JobFilter!]!, $metrics: [String!]!) {
footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) {
2023-05-12 11:19:37 +02:00
nodehours,
metrics { metric , data }
}
}`,
2023-06-06 16:58:08 +02:00
variables: { jobFilters , metrics }
2023-05-03 16:41:17 +02:00
})
2022-06-22 11:20:57 +02:00
2023-05-09 15:01:56 +02:00
$: rooflineQuery = queryStore({
client: client,
2023-05-03 16:41:17 +02:00
query: gql`
2023-06-06 16:58:08 +02:00
query($jobFilters: [JobFilter!]!, $rows: Int!, $cols: Int!,
2023-05-12 11:19:37 +02:00
$minX: Float!, $minY: Float!, $maxX: Float!, $maxY: Float!) {
2023-06-06 16:58:08 +02:00
rooflineHeatmap(filter: $jobFilters, rows: $rows, cols: $cols,
2023-05-12 11:19:37 +02:00
minX: $minX, minY: $minY, maxX: $maxX, maxY: $maxY)
}
`,
2023-06-06 16:58:08 +02:00
variables: { jobFilters , rows : 50 , cols : 50 , minX : 0.01 , minY : 1. , maxX : 1000. , maxY }
2023-05-03 16:41:17 +02:00
})
2022-06-22 11:20:57 +02:00
2023-06-06 16:58:08 +02:00
onMount(() => filterComponent.update())
2022-06-22 11:20:57 +02:00
< / script >
< Row >
{ #if $initq . fetching || $statsQuery . fetching || $footprintsQuery . fetching }
< Col xs = "auto" >
< Spinner / >
< / Col >
{ /if }
< Col xs = "auto" >
{ #if $initq . error }
< Card body color = "danger" > { $initq . error . message } </ Card >
{ :else if cluster }
< PlotSelection
availableMetrics={ cluster . metricConfig . map ( mc => mc . name )}
bind:metricsInHistograms={ metricsInHistograms }
bind:metricsInScatterplots={ metricsInScatterplots } />
{ /if }
< / Col >
< Col xs = "auto" >
< Filters
2023-06-06 16:58:08 +02:00
bind:this={ filterComponent }
2022-06-22 11:20:57 +02:00
filterPresets={ filterPresets }
disableClusterSelection={ true }
startTimeQuickSelect={ true }
on:update={({ detail }) => {
2023-06-06 16:58:08 +02:00
jobFilters = detail.filters;
2022-06-22 11:20:57 +02:00
}} />
< / Col >
< / Row >
< br / >
{ #if $statsQuery . error }
< Row >
< Col >
< Card body color = "danger" > { $statsQuery . error . message } </ Card >
< / Col >
< / Row >
{ :else if $statsQuery . data }
2023-08-10 18:06:19 +02:00
< Row cols = { 3 } class="mb-4" >
< Col >
< Table >
< tr >
< th scope = "col" > Total Jobs< / th >
< td > { $statsQuery . data . stats [ 0 ]. totalJobs } </ td >
< / tr >
< tr >
< th scope = "col" > Short Jobs< / th >
< td > { $statsQuery . data . stats [ 0 ]. shortJobs } </ td >
< / tr >
< tr >
< th scope = "col" > Total Walltime< / th >
< td > { $statsQuery . data . stats [ 0 ]. totalWalltime } </ td >
< / tr >
< tr >
< th scope = "col" > Total Core Hours< / th >
< td > { $statsQuery . data . stats [ 0 ]. totalCoreHours } </ td >
< / tr >
< / Table >
< / Col >
< Col >
< div bind:clientWidth = { colWidth1 } >
< h5 > Top Users< / h5 >
{ #key $statsQuery . data . topUsers }
< Pie
size={ colWidth1 }
sliceLabel='Hours'
quantities={ $statsQuery . data . topUsers . sort (( a , b ) => b . count - a . count ). map (( tu ) => tu . count )}
entities={ $statsQuery . data . topUsers . sort (( a , b ) => b . count - a . count ). map (( tu ) => tu . name )}
/>
{ /key }
< / div >
< / Col >
< Col >
< Table >
< tr class = "mb-2" > < th > User Name< / th > < th > Node Hours< / th > < / tr >
{ #each $statsQuery . data . topUsers . sort (( a , b ) => b . count - a . count ) as { name , count }}
2022-06-22 11:20:57 +02:00
< tr >
2023-08-10 18:06:19 +02:00
< th scope = "col" >< a href = "/monitoring/user/ { name } ?cluster= { cluster . name } " > { name } </ a ></ th >
< td > { count } </ td >
2022-06-22 11:20:57 +02:00
< / tr >
2023-08-10 18:06:19 +02:00
{ /each }
< / Table >
< / Col >
< / Row >
< Row cols = { 3 } class="mb-2" >
< Col >
{ #if $rooflineQuery . fetching }
< Spinner / >
{ :else if $rooflineQuery . error }
< Card body color = "danger" > { $rooflineQuery . error . message } </ Card >
{ :else if $rooflineQuery . data && cluster }
< div bind:clientWidth = { colWidth2 } >
{ #key $rooflineQuery . data }
< Roofline
width={ colWidth2 } height={ 300 }
tiles={ $rooflineQuery . data . rooflineHeatmap }
cluster={ cluster . subClusters . length == 1 ? cluster . subClusters [ 0 ] : null }
maxY={ rooflineMaxY } />
2022-06-22 11:20:57 +02:00
{ /key }
2023-08-10 18:06:19 +02:00
< / div >
{ /if }
< / Col >
< Col >
< div bind:clientWidth = { colWidth3 } >
2022-06-22 11:20:57 +02:00
{ #key $statsQuery . data . stats [ 0 ]. histDuration }
2023-08-09 12:42:25 +02:00
< Histogramuplot
2023-08-10 18:06:19 +02:00
width={ colWidth3 } height={ 300 }
2023-08-09 12:42:25 +02:00
data={ convert2uplot ( $statsQuery . data . stats [ 0 ]. histDuration )}
title="Duration Distribution"
xlabel="Current Runtimes"
xunit="Hours"
ylabel="Number of Jobs"
yunit="Jobs"/>
2022-06-22 11:20:57 +02:00
{ /key }
2023-08-10 18:06:19 +02:00
< / div >
< / Col >
< Col >
< div bind:clientWidth = { colWidth4 } >
2022-06-22 11:20:57 +02:00
{ #key $statsQuery . data . stats [ 0 ]. histNumNodes }
2023-08-09 12:42:25 +02:00
< Histogramuplot
2023-08-10 18:06:19 +02:00
width={ colWidth4 } height={ 300 }
2023-08-09 12:42:25 +02:00
data={ convert2uplot ( $statsQuery . data . stats [ 0 ]. histNumNodes )}
title="Number of Nodes Distribution"
xlabel="Allocated Nodes"
xunit="Nodes"
ylabel="Number of Jobs"
yunit="Jobs"/>
2022-06-22 11:20:57 +02:00
{ /key }
2023-08-10 18:06:19 +02:00
< / div >
< / Col >
2022-06-22 11:20:57 +02:00
< / Row >
{ /if }
2023-08-10 18:06:19 +02:00
< hr class = "my-6" / >
2022-06-22 11:20:57 +02:00
{ #if $footprintsQuery . error }
< Row >
< Col >
< Card body color = "danger" > { $footprintsQuery . error . message } </ Card >
< / Col >
< / Row >
{ :else if $footprintsQuery . data && $initq . data }
< Row >
< Col >
< Card body >
These histograms show the distribution of the averages of all jobs matching the filters. Each job/average is weighted by its node hours.
2023-03-31 17:18:16 +02:00
Note that some metrics could be disabled for specific subclusters as per metriConfig and thus could affect shown average values.
2022-06-22 11:20:57 +02:00
< / Card >
< br / >
< / Col >
< / Row >
< Row >
< Col >
< PlotTable
let:item
let:width
2023-06-19 16:11:16 +02:00
renderFor="analysis"
2022-06-22 11:20:57 +02:00
items={ metricsInHistograms . map ( metric => ({ metric , ... binsFromFootprint (
$footprintsQuery.data.footprints.nodehours,
$footprintsQuery.data.footprints.metrics.find(f => f.metric == metric).data, numBins) }))}
itemsPerRow={ ccconfig . plot_view_plotsPerRow } >
2023-08-09 12:42:25 +02:00
< Histogramuplot
data={ convert2uplot ( item . bins )}
2022-06-22 11:20:57 +02:00
width={ width } height={ 250 }
2023-08-09 12:42:25 +02:00
title="Average Distribution of '{ item . metric } '"
xlabel={ `$ { item . metric } average [ $ {( metricConfig ( cluster . name , item . metric ) ? . unit ? . prefix ? metricConfig ( cluster . name , item . metric ) ? . unit ? . prefix : '' ) +
2023-03-30 15:21:35 +02:00
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base : '')}]`}
2023-08-09 12:42:25 +02:00
xunit={ `$ {( metricConfig ( cluster . name , item . metric ) ? . unit ? . prefix ? metricConfig ( cluster . name , item . metric ) ? . unit ? . prefix : '' ) +
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base : '')}`}
ylabel="Node Hours"
yunit="Hours"/>
2022-06-22 11:20:57 +02:00
< / PlotTable >
< / Col >
< / Row >
< br / >
< Row >
< Col >
< Card body >
Each circle represents one job. The size of a circle is proportional to its node hours. Darker circles mean multiple jobs have the same averages for the respective metrics.
2023-03-31 17:18:16 +02:00
Note that some metrics could be disabled for specific subclusters as per metriConfig and thus could affect shown average values.
2022-06-22 11:20:57 +02:00
< / Card >
< br / >
< / Col >
< / Row >
< Row >
< Col >
< PlotTable
let:item
let:width
items={ metricsInScatterplots . map (([ m1 , m2 ]) => ({
m1, f1: $footprintsQuery.data.footprints.metrics.find(f => f.metric == m1).data,
m2, f2: $footprintsQuery.data.footprints.metrics.find(f => f.metric == m2).data }))}
itemsPerRow={ ccconfig . plot_view_plotsPerRow } >
< ScatterPlot
width={ width } height={ 250 } color={ "rgba(0, 102, 204, 0.33)" }
2023-03-30 15:21:35 +02:00
xLabel={ `$ { item . m1 } [ $ {( metricConfig ( cluster . name , item . m1 ) ? . unit ? . prefix ? metricConfig ( cluster . name , item . m1 ) ? . unit ? . prefix : '' ) +
(metricConfig(cluster.name, item.m1)?.unit?.base ? metricConfig(cluster.name, item.m1)?.unit?.base : '')}]`}
yLabel={ `$ { item . m2 } [ $ {( metricConfig ( cluster . name , item . m2 ) ? . unit ? . prefix ? metricConfig ( cluster . name , item . m2 ) ? . unit ? . prefix : '' ) +
(metricConfig(cluster.name, item.m2)?.unit?.base ? metricConfig(cluster.name, item.m2)?.unit?.base : '')}]`}
2022-06-22 11:20:57 +02:00
X={ item . f1 } Y={ item . f2 } S={ $footprintsQuery . data . footprints . nodehours } />
< / PlotTable >
< / Col >
< / Row >
{ /if }
2023-03-30 15:21:35 +02:00
< style >
2023-08-10 18:06:19 +02:00
h5 {
2023-03-30 15:21:35 +02:00
text-align: center;
}
< / style >