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-08-30 15:15:53 +02:00
import { queryStore , gql , getContextClient , mutationStore } from '@urql/svelte'
2023-08-11 09:41:39 +02:00
import { Row , Col , Spinner , Card , Table , Icon } from 'sveltestrap'
2022-06-22 11:20:57 +02:00
import Filters from './filters/Filters.svelte'
import PlotSelection from './PlotSelection.svelte'
2023-08-11 13:34:30 +02:00
import Histogram from './plots/Histogram.svelte'
2023-08-11 09:41:39 +02:00
import Pie, { colors } from './plots/Pie.svelte'
2023-08-11 13:34:30 +02:00
import { binsFromFootprint } from './utils.js'
2022-06-22 11:20:57 +02:00
import ScatterPlot from './plots/Scatter.svelte'
import PlotTable from './PlotTable.svelte'
2023-09-05 10:01:34 +02:00
import RooflineHeatmap from './plots/RooflineHeatmap.svelte'
2022-06-22 11:20:57 +02:00
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()])]
2023-08-29 17:38:17 +02:00
const sortOptions = [
{ key : 'totalWalltime' , label : 'Walltime' } ,
{ key : 'totalNodeHours' , label : 'Node Hours' } ,
{ key : 'totalCoreHours' , label : 'Core Hours' } ,
{ key : 'totalAccHours' , label : 'Accelerator Hours' }
]
const groupOptions = [
2023-08-30 15:15:53 +02:00
{ key : 'user' , label : 'User Name' } ,
{ key : 'project' , label : 'Project ID' }
2023-08-29 17:38:17 +02:00
]
2023-08-30 15:15:53 +02:00
let sortSelection = sortOptions.find((option) => option.key == ccconfig[`analysis_view_selectedTopCategory:${ filterPresets . cluster } `]) || 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)
2023-08-29 17:38:17 +02:00
2022-06-22 11:20:57 +02:00
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
2023-08-29 17:38:17 +02:00
totalNodeHours
2023-05-12 11:19:37 +02:00
totalCoreHours
2023-08-29 17:38:17 +02:00
totalAccHours
2023-05-12 11:19:37 +02:00
histDuration { count , value }
2023-08-29 17:38:17 +02:00
histNumCores { count , value }
2023-05-12 11:19:37 +02:00
}
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-08-25 17:38:25 +02:00
$: topQuery = queryStore({
client: client,
query: gql`
2023-08-29 17:38:17 +02:00
query($jobFilters: [JobFilter!]!, $paging: PageRequest!, $sortBy: SortByAggregate!, $groupBy: Aggregate!) {
topList: jobsStatistics(filter: $jobFilters, page: $paging, sortBy: $sortBy, groupBy: $groupBy) {
2023-08-25 17:38:25 +02:00
id
2023-08-29 17:38:17 +02:00
totalWalltime
totalNodeHours
2023-08-25 17:38:25 +02:00
totalCoreHours
2023-08-29 17:38:17 +02:00
totalAccHours
2023-08-25 17:38:25 +02:00
}
}
`,
2023-08-29 17:38:17 +02:00
variables: { jobFilters , paging : { itemsPerPage : 10 , page : 1 }, sortBy : sortSelection.key.toUpperCase (), groupBy : groupSelection.key.toUpperCase () }
2023-08-25 17:38:25 +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-08-24 11:52:36 +02:00
timeWeights { nodeHours , accHours , coreHours } ,
2023-05-12 11:19:37 +02:00
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-08-30 15:15:53 +02:00
const updateConfigurationMutation = ({ name , value } ) => {
return mutationStore({
client: client,
query: gql`
mutation ($name: String!, $value: String!) {
updateConfiguration(name: $name, value: $value)
}
`,
variables: { name , value }
});
}
function updateEntityConfiguration(select) {
if (ccconfig[`analysis_view_selectedTopEntity:${ filterPresets . cluster } `] != select) {
updateConfigurationMutation({ name : `analysis_view_selectedTopEntity:$ { filterPresets . cluster } `, value: JSON.stringify(select) } )
.subscribe(res => {
if (res.fetching === false && !res.error) {
// console.log(`analysis_view_selectedTopEntity:${ filterPresets . cluster } ` + ' -> Updated!')
} else if (res.fetching === false && res.error) {
throw res.error
}
})
} else {
// console.log('No Mutation Required: Entity')
}
};
function updateCategoryConfiguration(select) {
if (ccconfig[`analysis_view_selectedTopCategory:${ filterPresets . cluster } `] != select) {
updateConfigurationMutation({ name : `analysis_view_selectedTopCategory:$ { filterPresets . cluster } `, value: JSON.stringify(select) } )
.subscribe(res => {
if (res.fetching === false && !res.error) {
// console.log(`analysis_view_selectedTopCategory:${ filterPresets . cluster } ` + ' -> Updated!')
} else if (res.fetching === false && res.error) {
throw res.error
}
})
} else {
// console.log('No Mutation Required: Category')
}
};
$: updateEntityConfiguration(groupSelection.key)
$: updateCategoryConfiguration(sortSelection.key)
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 >
2023-08-29 17:38:17 +02:00
< tr >
< th scope = "col" > Total Node Hours< / th >
< td > { $statsQuery . data . stats [ 0 ]. totalNodeHours } </ td >
< / tr >
2023-08-10 18:06:19 +02:00
< tr >
< th scope = "col" > Total Core Hours< / th >
< td > { $statsQuery . data . stats [ 0 ]. totalCoreHours } </ td >
< / tr >
2023-08-29 17:38:17 +02:00
< tr >
< th scope = "col" > Total Accelerator Hours< / th >
< td > { $statsQuery . data . stats [ 0 ]. totalAccHours } </ td >
< / tr >
2023-08-10 18:06:19 +02:00
< / Table >
< / Col >
< Col >
< div bind:clientWidth = { colWidth1 } >
2023-08-29 17:38:17 +02:00
< h5 > Top
< select class = "p-0" bind:value = { groupSelection } >
{ #each groupOptions as option }
< option value = { option } >
2023-08-30 15:15:53 +02:00
{ option . key . charAt ( 0 ). toUpperCase () + option . key . slice ( 1 )} s
2023-08-29 17:38:17 +02:00
< / option >
{ /each }
< / select >
< / h5 >
{ #key $topQuery . data }
{ #if $topQuery . fetching }
< Spinner / >
{ :else if $topQuery . error }
< Card body color = "danger" > { $topQuery . error . message } </ Card >
{ : else }
< Pie
size={ colWidth1 }
sliceLabel={ sortSelection . label }
quantities={ $topQuery . data . topList . map (( t ) => t [ sortSelection . key ])}
entities={ $topQuery . data . topList . map (( t ) => t . id )}
/>
{ /if }
2023-08-10 18:06:19 +02:00
{ /key }
< / div >
< / Col >
< Col >
2023-08-29 17:38:17 +02:00
{ #key $topQuery . data }
{ #if $topQuery . fetching }
< Spinner / >
{ :else if $topQuery . error }
< Card body color = "danger" > { $topQuery . error . message } </ Card >
{ : else }
< Table >
< tr class = "mb-2" >
< th > Legend< / th >
< th > { groupSelection . label } </ th >
< th >
< select class = "p-0" bind:value = { sortSelection } >
{ #each sortOptions as option }
< option value = { option } >
{ option . label }
< / option >
{ /each }
< / select >
< / th >
< / tr >
{ #each $topQuery . data . topList as te , i }
< tr >
< td >< Icon name = "circle-fill" style = "color: { colors [ i ]} ;" /></ td >
2023-09-01 10:13:26 +02:00
{ #if groupSelection . key == 'user' }
2023-08-29 17:38:17 +02:00
< th scope = "col" >< a href = "/monitoring/user/ { te . id } ?cluster= { cluster . name } " > { te . id } </ a ></ th >
{ : else }
< th scope = "col" >< a href = "/monitoring/jobs/?cluster= { cluster . name } &project= { te . id } &projectMatch=eq" > { te . id } </ a ></ th >
{ /if }
< td > { te [ sortSelection . key ]} </ td >
< / tr >
{ /each }
< / Table >
{ /if }
{ /key }
2023-08-10 18:06:19 +02:00
< / Col >
< / Row >
< Row cols = { 3 } class="mb-2" >
< Col >
2022-06-22 11:20:57 +02:00
{ #if $rooflineQuery . fetching }
< Spinner / >
{ :else if $rooflineQuery . error }
< Card body color = "danger" > { $rooflineQuery . error . message } </ Card >
{ :else if $rooflineQuery . data && cluster }
2023-08-10 18:06:19 +02:00
< div bind:clientWidth = { colWidth2 } >
2022-06-22 11:20:57 +02:00
{ #key $rooflineQuery . data }
2023-09-05 10:01:34 +02:00
< RooflineHeatmap
2023-08-10 18:06:19 +02:00
width={ colWidth2 } height={ 300 }
2022-06-22 11:20:57 +02:00
tiles={ $rooflineQuery . data . rooflineHeatmap }
cluster={ cluster . subClusters . length == 1 ? cluster . subClusters [ 0 ] : null }
maxY={ rooflineMaxY } />
{ /key }
2023-08-10 18:06:19 +02:00
< / div >
2022-06-22 11:20:57 +02:00
{ /if }
2023-08-10 18:06:19 +02:00
< / Col >
< Col >
< div bind:clientWidth = { colWidth3 } >
2022-06-22 11:20:57 +02:00
{ #key $statsQuery . data . stats [ 0 ]. histDuration }
2023-08-11 13:34:30 +02:00
< Histogram
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 } >
2023-08-29 17:38:17 +02:00
{ #key $statsQuery . data . stats [ 0 ]. histNumCores }
2023-08-11 13:34:30 +02:00
< Histogram
2023-08-10 18:06:19 +02:00
width={ colWidth4 } height={ 300 }
2023-08-29 17:38:17 +02:00
data={ convert2uplot ( $statsQuery . data . stats [ 0 ]. histNumCores )}
title="Number of Cores Distribution"
xlabel="Allocated Cores"
xunit="Cores"
2023-08-09 12:42:25 +02:00
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 >
2023-08-24 11:52:36 +02:00
These histograms show the distribution of the averages of all jobs matching the filters. Each job/average is weighted by its node hours by default
(Accelerator hours for native accelerator scope metrics, coreHours for native core scope metrics).
Note that some metrics could be disabled for specific subclusters as per metricConfig 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 (
2023-08-24 11:52:36 +02:00
$footprintsQuery.data.footprints.timeWeights,
metricConfig(cluster.name, metric)?.scope,
2022-06-22 11:20:57 +02:00
$footprintsQuery.data.footprints.metrics.find(f => f.metric == metric).data, numBins) }))}
itemsPerRow={ ccconfig . plot_view_plotsPerRow } >
< Histogram
2023-08-09 12:42:25 +02:00
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 } '"
2023-08-24 11:52:36 +02:00
xlabel={ `$ { item . metric } bin maximum [ $ {( 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 : '')}`}
2023-08-24 11:52:36 +02:00
ylabel="Normalized Hours"
2023-08-09 12:42:25 +02:00
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-08-24 11:52:36 +02:00
Note that some metrics could be disabled for specific subclusters as per metricConfig 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-08-11 13:34:30 +02:00
renderFor="analysis"
2022-06-22 11:20:57 +02:00
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 : '')}]`}
2023-08-24 11:52:36 +02:00
X={ item . f1 } Y={ item . f2 } S={ $footprintsQuery . data . footprints . timeWeights . nodeHours } />
2022-06-22 11:20:57 +02:00
< / 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 >