feat: add select to analysis view pie chart

- 'Walltime' as generic default value for top list
-  Change from nodes distribution to cores distribution
This commit is contained in:
Christoph Kluge 2023-08-29 17:38:17 +02:00
parent 1771883754
commit 59c749a164
6 changed files with 122 additions and 56 deletions

View File

@ -167,7 +167,7 @@ type TimeWeights {
} }
enum Aggregate { USER, PROJECT, CLUSTER } enum Aggregate { USER, PROJECT, CLUSTER }
enum SortByAggregate { WALLTIME, TOTALJOBS, TOTALNODES, NODEHOURS, TOTALCORES, COREHOURS, TOTALACCS, ACCHOURS } enum SortByAggregate { TOTALWALLTIME, TOTALJOBS, TOTALNODES, TOTALNODEHOURS, TOTALCORES, TOTALCOREHOURS, TOTALACCS, TOTALACCHOURS }
type NodeMetrics { type NodeMetrics {
host: String! host: String!

View File

@ -1767,7 +1767,7 @@ type TimeWeights {
} }
enum Aggregate { USER, PROJECT, CLUSTER } enum Aggregate { USER, PROJECT, CLUSTER }
enum SortByAggregate { WALLTIME, TOTALJOBS, TOTALNODES, NODEHOURS, TOTALCORES, COREHOURS, TOTALACCS, ACCHOURS } enum SortByAggregate { TOTALWALLTIME, TOTALJOBS, TOTALNODES, TOTALNODEHOURS, TOTALCORES, TOTALCOREHOURS, TOTALACCS, TOTALACCHOURS }
type NodeMetrics { type NodeMetrics {
host: String! host: String!

View File

@ -196,30 +196,30 @@ func (e Aggregate) MarshalGQL(w io.Writer) {
type SortByAggregate string type SortByAggregate string
const ( const (
SortByAggregateWalltime SortByAggregate = "WALLTIME" SortByAggregateTotalwalltime SortByAggregate = "TOTALWALLTIME"
SortByAggregateTotaljobs SortByAggregate = "TOTALJOBS" SortByAggregateTotaljobs SortByAggregate = "TOTALJOBS"
SortByAggregateTotalnodes SortByAggregate = "TOTALNODES" SortByAggregateTotalnodes SortByAggregate = "TOTALNODES"
SortByAggregateNodehours SortByAggregate = "NODEHOURS" SortByAggregateTotalnodehours SortByAggregate = "TOTALNODEHOURS"
SortByAggregateTotalcores SortByAggregate = "TOTALCORES" SortByAggregateTotalcores SortByAggregate = "TOTALCORES"
SortByAggregateCorehours SortByAggregate = "COREHOURS" SortByAggregateTotalcorehours SortByAggregate = "TOTALCOREHOURS"
SortByAggregateTotalaccs SortByAggregate = "TOTALACCS" SortByAggregateTotalaccs SortByAggregate = "TOTALACCS"
SortByAggregateAcchours SortByAggregate = "ACCHOURS" SortByAggregateTotalacchours SortByAggregate = "TOTALACCHOURS"
) )
var AllSortByAggregate = []SortByAggregate{ var AllSortByAggregate = []SortByAggregate{
SortByAggregateWalltime, SortByAggregateTotalwalltime,
SortByAggregateTotaljobs, SortByAggregateTotaljobs,
SortByAggregateTotalnodes, SortByAggregateTotalnodes,
SortByAggregateNodehours, SortByAggregateTotalnodehours,
SortByAggregateTotalcores, SortByAggregateTotalcores,
SortByAggregateCorehours, SortByAggregateTotalcorehours,
SortByAggregateTotalaccs, SortByAggregateTotalaccs,
SortByAggregateAcchours, SortByAggregateTotalacchours,
} }
func (e SortByAggregate) IsValid() bool { func (e SortByAggregate) IsValid() bool {
switch e { switch e {
case SortByAggregateWalltime, SortByAggregateTotaljobs, SortByAggregateTotalnodes, SortByAggregateNodehours, SortByAggregateTotalcores, SortByAggregateCorehours, SortByAggregateTotalaccs, SortByAggregateAcchours: case SortByAggregateTotalwalltime, SortByAggregateTotaljobs, SortByAggregateTotalnodes, SortByAggregateTotalnodehours, SortByAggregateTotalcores, SortByAggregateTotalcorehours, SortByAggregateTotalaccs, SortByAggregateTotalacchours:
return true return true
} }
return false return false

View File

@ -248,8 +248,8 @@ func (r *queryResolver) JobsStatistics(ctx context.Context, filter []*model.JobF
var err error var err error
var stats []*model.JobsStatistics var stats []*model.JobsStatistics
if requireField(ctx, "totalJobs") || requireField(ctx, "totalNodes") || requireField(ctx, "totalCores") || requireField(ctx, "totalAccs") || if requireField(ctx, "totalJobs") || requireField(ctx, "totalWalltime") || requireField(ctx, "totalNodes") || requireField(ctx, "totalCores") ||
requireField(ctx, "totalNodeHours") || requireField(ctx, "totalCoreHours") || requireField(ctx, "totalAccHours") { requireField(ctx, "totalAccs") || requireField(ctx, "totalNodeHours") || requireField(ctx, "totalCoreHours") || requireField(ctx, "totalAccHours") {
if groupBy == nil { if groupBy == nil {
stats, err = r.Repo.JobsStats(ctx, filter) stats, err = r.Repo.JobsStats(ctx, filter)
} else { } else {

View File

@ -25,13 +25,13 @@ var groupBy2column = map[model.Aggregate]string{
var sortBy2column = map[model.SortByAggregate]string{ var sortBy2column = map[model.SortByAggregate]string{
model.SortByAggregateTotaljobs: "totalJobs", model.SortByAggregateTotaljobs: "totalJobs",
model.SortByAggregateWalltime: "totalWalltime", model.SortByAggregateTotalwalltime: "totalWalltime",
model.SortByAggregateTotalnodes: "totalNodes", model.SortByAggregateTotalnodes: "totalNodes",
model.SortByAggregateNodehours: "totalNodeHours", model.SortByAggregateTotalnodehours: "totalNodeHours",
model.SortByAggregateTotalcores: "totalCores", model.SortByAggregateTotalcores: "totalCores",
model.SortByAggregateCorehours: "totalCoreHours", model.SortByAggregateTotalcorehours: "totalCoreHours",
model.SortByAggregateTotalaccs: "totalAccs", model.SortByAggregateTotalaccs: "totalAccs",
model.SortByAggregateAcchours: "totalAccHours", model.SortByAggregateTotalacchours: "totalAccHours",
} }
func (r *JobRepository) buildCountQuery( func (r *JobRepository) buildCountQuery(
@ -169,12 +169,16 @@ func (r *JobRepository) JobsStatsGrouped(
} }
if id.Valid { if id.Valid {
var totalJobs, totalNodes, totalNodeHours, totalCores, totalCoreHours, totalAccs, totalAccHours int var totalJobs, totalWalltime, totalNodes, totalNodeHours, totalCores, totalCoreHours, totalAccs, totalAccHours int
if jobs.Valid { if jobs.Valid {
totalJobs = int(jobs.Int64) totalJobs = int(jobs.Int64)
} }
if walltime.Valid {
totalWalltime = int(walltime.Int64)
}
if nodes.Valid { if nodes.Valid {
totalNodes = int(nodes.Int64) totalNodes = int(nodes.Int64)
} }
@ -202,7 +206,7 @@ func (r *JobRepository) JobsStatsGrouped(
ID: id.String, ID: id.String,
Name: name, Name: name,
TotalJobs: totalJobs, TotalJobs: totalJobs,
TotalWalltime: int(walltime.Int64), TotalWalltime: totalWalltime,
TotalNodes: totalNodes, TotalNodes: totalNodes,
TotalNodeHours: totalNodeHours, TotalNodeHours: totalNodeHours,
TotalCores: totalCores, TotalCores: totalCores,

View File

@ -42,6 +42,19 @@
$: metrics = [...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])] $: metrics = [...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])]
const sortOptions = [
{key: 'totalWalltime', label: 'Walltime'},
{key: 'totalNodeHours', label: 'Node Hours'},
{key: 'totalCoreHours', label: 'Core Hours'},
{key: 'totalAccHours', label: 'Accelerator Hours'}
]
const groupOptions = [
{key: 'User', label: 'User Name'},
{key: 'Project', label: 'Project ID'}
]
let sortSelection = sortOptions[0] // Default: Walltime
let groupSelection = groupOptions[0] // Default: Users
getContext('on-init')(({ data }) => { getContext('on-init')(({ data }) => {
if (data != null) { if (data != null) {
cluster = data.clusters.find(c => c.name == filterPresets.cluster) cluster = data.clusters.find(c => c.name == filterPresets.cluster)
@ -62,28 +75,31 @@
totalJobs totalJobs
shortJobs shortJobs
totalWalltime totalWalltime
totalNodeHours
totalCoreHours totalCoreHours
totalAccHours
histDuration { count, value } histDuration { count, value }
histNumNodes { count, value } histNumCores { count, value }
} }
} }
`, `,
variables: { jobFilters } variables: { jobFilters }
}) })
const paging = { itemsPerPage: 5, page: 1 }; // Top 5
// const sorting = { field: "totalCoreHours", order: "DESC" };
$: topQuery = queryStore({ $: topQuery = queryStore({
client: client, client: client,
query: gql` query: gql`
query($jobFilters: [JobFilter!]!, $paging: PageRequest!) { query($jobFilters: [JobFilter!]!, $paging: PageRequest!, $sortBy: SortByAggregate!, $groupBy: Aggregate!) {
topUser: jobsStatistics(filter: $jobFilters, page: $paging, sortBy: COREHOURS, groupBy: USER) { topList: jobsStatistics(filter: $jobFilters, page: $paging, sortBy: $sortBy, groupBy: $groupBy) {
id id
totalWalltime
totalNodeHours
totalCoreHours totalCoreHours
totalAccHours
} }
} }
`, `,
variables: { jobFilters, paging } variables: { jobFilters, paging: { itemsPerPage: 10, page: 1 }, sortBy: sortSelection.key.toUpperCase(), groupBy: groupSelection.key.toUpperCase() }
}) })
$: footprintsQuery = queryStore({ $: footprintsQuery = queryStore({
@ -164,36 +180,82 @@
<th scope="col">Total Walltime</th> <th scope="col">Total Walltime</th>
<td>{$statsQuery.data.stats[0].totalWalltime}</td> <td>{$statsQuery.data.stats[0].totalWalltime}</td>
</tr> </tr>
<tr>
<th scope="col">Total Node Hours</th>
<td>{$statsQuery.data.stats[0].totalNodeHours}</td>
</tr>
<tr> <tr>
<th scope="col">Total Core Hours</th> <th scope="col">Total Core Hours</th>
<td>{$statsQuery.data.stats[0].totalCoreHours}</td> <td>{$statsQuery.data.stats[0].totalCoreHours}</td>
</tr> </tr>
<tr>
<th scope="col">Total Accelerator Hours</th>
<td>{$statsQuery.data.stats[0].totalAccHours}</td>
</tr>
</Table> </Table>
</Col> </Col>
<Col> <Col>
<div bind:clientWidth={colWidth1}> <div bind:clientWidth={colWidth1}>
<h5>Top Users</h5> <h5>Top
{#key $statsQuery.data.topUsers} <select class="p-0" bind:value={groupSelection}>
{#each groupOptions as option}
<option value={option}>
{option.key}s
</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 <Pie
size={colWidth1} size={colWidth1}
sliceLabel='Core Hours' sliceLabel={sortSelection.label}
quantities={$topQuery.data.topUser.map((tu) => tu.totalCoreHours)} quantities={$topQuery.data.topList.map((t) => t[sortSelection.key])}
entities={$topQuery.data.topUser.map((tu) => tu.id)} entities={$topQuery.data.topList.map((t) => t.id)}
/> />
{/if}
{/key} {/key}
</div> </div>
</Col> </Col>
<Col> <Col>
{#key $topQuery.data}
{#if $topQuery.fetching}
<Spinner/>
{:else if $topQuery.error}
<Card body color="danger">{$topQuery.error.message}</Card>
{:else}
<Table> <Table>
<tr class="mb-2"><th>Legend</th><th>User Name</th><th>Core Hours</th></tr> <tr class="mb-2">
{#each $topQuery.data.topUser as { id, totalCoreHours }, i} <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> <tr>
<td><Icon name="circle-fill" style="color: {colors[i]};"/></td> <td><Icon name="circle-fill" style="color: {colors[i]};"/></td>
<th scope="col"><a href="/monitoring/user/{id}?cluster={cluster.name}">{id}</a></th> {#if groupSelection.key == 'User'}
<td>{totalCoreHours}</td> <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> </tr>
{/each} {/each}
</Table> </Table>
{/if}
{/key}
</Col> </Col>
</Row> </Row>
<Row cols={3} class="mb-2"> <Row cols={3} class="mb-2">
@ -230,13 +292,13 @@
</Col> </Col>
<Col> <Col>
<div bind:clientWidth={colWidth4}> <div bind:clientWidth={colWidth4}>
{#key $statsQuery.data.stats[0].histNumNodes} {#key $statsQuery.data.stats[0].histNumCores}
<Histogram <Histogram
width={colWidth4} height={300} width={colWidth4} height={300}
data={convert2uplot($statsQuery.data.stats[0].histNumNodes)} data={convert2uplot($statsQuery.data.stats[0].histNumCores)}
title="Number of Nodes Distribution" title="Number of Cores Distribution"
xlabel="Allocated Nodes" xlabel="Allocated Cores"
xunit="Nodes" xunit="Cores"
ylabel="Number of Jobs" ylabel="Number of Jobs"
yunit="Jobs"/> yunit="Jobs"/>
{/key} {/key}