mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-25 21:09:05 +01:00
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:
parent
1771883754
commit
59c749a164
@ -167,7 +167,7 @@ type TimeWeights {
|
||||
}
|
||||
|
||||
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 {
|
||||
host: String!
|
||||
|
@ -1767,7 +1767,7 @@ type TimeWeights {
|
||||
}
|
||||
|
||||
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 {
|
||||
host: String!
|
||||
|
@ -196,30 +196,30 @@ func (e Aggregate) MarshalGQL(w io.Writer) {
|
||||
type SortByAggregate string
|
||||
|
||||
const (
|
||||
SortByAggregateWalltime SortByAggregate = "WALLTIME"
|
||||
SortByAggregateTotaljobs SortByAggregate = "TOTALJOBS"
|
||||
SortByAggregateTotalnodes SortByAggregate = "TOTALNODES"
|
||||
SortByAggregateNodehours SortByAggregate = "NODEHOURS"
|
||||
SortByAggregateTotalcores SortByAggregate = "TOTALCORES"
|
||||
SortByAggregateCorehours SortByAggregate = "COREHOURS"
|
||||
SortByAggregateTotalaccs SortByAggregate = "TOTALACCS"
|
||||
SortByAggregateAcchours SortByAggregate = "ACCHOURS"
|
||||
SortByAggregateTotalwalltime SortByAggregate = "TOTALWALLTIME"
|
||||
SortByAggregateTotaljobs SortByAggregate = "TOTALJOBS"
|
||||
SortByAggregateTotalnodes SortByAggregate = "TOTALNODES"
|
||||
SortByAggregateTotalnodehours SortByAggregate = "TOTALNODEHOURS"
|
||||
SortByAggregateTotalcores SortByAggregate = "TOTALCORES"
|
||||
SortByAggregateTotalcorehours SortByAggregate = "TOTALCOREHOURS"
|
||||
SortByAggregateTotalaccs SortByAggregate = "TOTALACCS"
|
||||
SortByAggregateTotalacchours SortByAggregate = "TOTALACCHOURS"
|
||||
)
|
||||
|
||||
var AllSortByAggregate = []SortByAggregate{
|
||||
SortByAggregateWalltime,
|
||||
SortByAggregateTotalwalltime,
|
||||
SortByAggregateTotaljobs,
|
||||
SortByAggregateTotalnodes,
|
||||
SortByAggregateNodehours,
|
||||
SortByAggregateTotalnodehours,
|
||||
SortByAggregateTotalcores,
|
||||
SortByAggregateCorehours,
|
||||
SortByAggregateTotalcorehours,
|
||||
SortByAggregateTotalaccs,
|
||||
SortByAggregateAcchours,
|
||||
SortByAggregateTotalacchours,
|
||||
}
|
||||
|
||||
func (e SortByAggregate) IsValid() bool {
|
||||
switch e {
|
||||
case SortByAggregateWalltime, SortByAggregateTotaljobs, SortByAggregateTotalnodes, SortByAggregateNodehours, SortByAggregateTotalcores, SortByAggregateCorehours, SortByAggregateTotalaccs, SortByAggregateAcchours:
|
||||
case SortByAggregateTotalwalltime, SortByAggregateTotaljobs, SortByAggregateTotalnodes, SortByAggregateTotalnodehours, SortByAggregateTotalcores, SortByAggregateTotalcorehours, SortByAggregateTotalaccs, SortByAggregateTotalacchours:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -248,8 +248,8 @@ func (r *queryResolver) JobsStatistics(ctx context.Context, filter []*model.JobF
|
||||
var err error
|
||||
var stats []*model.JobsStatistics
|
||||
|
||||
if requireField(ctx, "totalJobs") || requireField(ctx, "totalNodes") || requireField(ctx, "totalCores") || requireField(ctx, "totalAccs") ||
|
||||
requireField(ctx, "totalNodeHours") || requireField(ctx, "totalCoreHours") || requireField(ctx, "totalAccHours") {
|
||||
if requireField(ctx, "totalJobs") || requireField(ctx, "totalWalltime") || requireField(ctx, "totalNodes") || requireField(ctx, "totalCores") ||
|
||||
requireField(ctx, "totalAccs") || requireField(ctx, "totalNodeHours") || requireField(ctx, "totalCoreHours") || requireField(ctx, "totalAccHours") {
|
||||
if groupBy == nil {
|
||||
stats, err = r.Repo.JobsStats(ctx, filter)
|
||||
} else {
|
||||
|
@ -24,14 +24,14 @@ var groupBy2column = map[model.Aggregate]string{
|
||||
}
|
||||
|
||||
var sortBy2column = map[model.SortByAggregate]string{
|
||||
model.SortByAggregateTotaljobs: "totalJobs",
|
||||
model.SortByAggregateWalltime: "totalWalltime",
|
||||
model.SortByAggregateTotalnodes: "totalNodes",
|
||||
model.SortByAggregateNodehours: "totalNodeHours",
|
||||
model.SortByAggregateTotalcores: "totalCores",
|
||||
model.SortByAggregateCorehours: "totalCoreHours",
|
||||
model.SortByAggregateTotalaccs: "totalAccs",
|
||||
model.SortByAggregateAcchours: "totalAccHours",
|
||||
model.SortByAggregateTotaljobs: "totalJobs",
|
||||
model.SortByAggregateTotalwalltime: "totalWalltime",
|
||||
model.SortByAggregateTotalnodes: "totalNodes",
|
||||
model.SortByAggregateTotalnodehours: "totalNodeHours",
|
||||
model.SortByAggregateTotalcores: "totalCores",
|
||||
model.SortByAggregateTotalcorehours: "totalCoreHours",
|
||||
model.SortByAggregateTotalaccs: "totalAccs",
|
||||
model.SortByAggregateTotalacchours: "totalAccHours",
|
||||
}
|
||||
|
||||
func (r *JobRepository) buildCountQuery(
|
||||
@ -169,12 +169,16 @@ func (r *JobRepository) JobsStatsGrouped(
|
||||
}
|
||||
|
||||
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 {
|
||||
totalJobs = int(jobs.Int64)
|
||||
}
|
||||
|
||||
if walltime.Valid {
|
||||
totalWalltime = int(walltime.Int64)
|
||||
}
|
||||
|
||||
if nodes.Valid {
|
||||
totalNodes = int(nodes.Int64)
|
||||
}
|
||||
@ -202,7 +206,7 @@ func (r *JobRepository) JobsStatsGrouped(
|
||||
ID: id.String,
|
||||
Name: name,
|
||||
TotalJobs: totalJobs,
|
||||
TotalWalltime: int(walltime.Int64),
|
||||
TotalWalltime: totalWalltime,
|
||||
TotalNodes: totalNodes,
|
||||
TotalNodeHours: totalNodeHours,
|
||||
TotalCores: totalCores,
|
||||
|
@ -42,6 +42,19 @@
|
||||
|
||||
$: 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 }) => {
|
||||
if (data != null) {
|
||||
cluster = data.clusters.find(c => c.name == filterPresets.cluster)
|
||||
@ -62,28 +75,31 @@
|
||||
totalJobs
|
||||
shortJobs
|
||||
totalWalltime
|
||||
totalNodeHours
|
||||
totalCoreHours
|
||||
totalAccHours
|
||||
histDuration { count, value }
|
||||
histNumNodes { count, value }
|
||||
histNumCores { count, value }
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { jobFilters }
|
||||
})
|
||||
|
||||
const paging = { itemsPerPage: 5, page: 1 }; // Top 5
|
||||
// const sorting = { field: "totalCoreHours", order: "DESC" };
|
||||
$: topQuery = queryStore({
|
||||
client: client,
|
||||
query: gql`
|
||||
query($jobFilters: [JobFilter!]!, $paging: PageRequest!) {
|
||||
topUser: jobsStatistics(filter: $jobFilters, page: $paging, sortBy: COREHOURS, groupBy: USER) {
|
||||
query($jobFilters: [JobFilter!]!, $paging: PageRequest!, $sortBy: SortByAggregate!, $groupBy: Aggregate!) {
|
||||
topList: jobsStatistics(filter: $jobFilters, page: $paging, sortBy: $sortBy, groupBy: $groupBy) {
|
||||
id
|
||||
totalWalltime
|
||||
totalNodeHours
|
||||
totalCoreHours
|
||||
totalAccHours
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { jobFilters, paging }
|
||||
variables: { jobFilters, paging: { itemsPerPage: 10, page: 1 }, sortBy: sortSelection.key.toUpperCase(), groupBy: groupSelection.key.toUpperCase() }
|
||||
})
|
||||
|
||||
$: footprintsQuery = queryStore({
|
||||
@ -164,36 +180,82 @@
|
||||
<th scope="col">Total Walltime</th>
|
||||
<td>{$statsQuery.data.stats[0].totalWalltime}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">Total Node Hours</th>
|
||||
<td>{$statsQuery.data.stats[0].totalNodeHours}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">Total Core Hours</th>
|
||||
<td>{$statsQuery.data.stats[0].totalCoreHours}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">Total Accelerator Hours</th>
|
||||
<td>{$statsQuery.data.stats[0].totalAccHours}</td>
|
||||
</tr>
|
||||
</Table>
|
||||
</Col>
|
||||
<Col>
|
||||
<div bind:clientWidth={colWidth1}>
|
||||
<h5>Top Users</h5>
|
||||
{#key $statsQuery.data.topUsers}
|
||||
<Pie
|
||||
size={colWidth1}
|
||||
sliceLabel='Core Hours'
|
||||
quantities={$topQuery.data.topUser.map((tu) => tu.totalCoreHours)}
|
||||
entities={$topQuery.data.topUser.map((tu) => tu.id)}
|
||||
/>
|
||||
<h5>Top
|
||||
<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
|
||||
size={colWidth1}
|
||||
sliceLabel={sortSelection.label}
|
||||
quantities={$topQuery.data.topList.map((t) => t[sortSelection.key])}
|
||||
entities={$topQuery.data.topList.map((t) => t.id)}
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
<Table>
|
||||
<tr class="mb-2"><th>Legend</th><th>User Name</th><th>Core Hours</th></tr>
|
||||
{#each $topQuery.data.topUser as { id, totalCoreHours }, i}
|
||||
<tr>
|
||||
<td><Icon name="circle-fill" style="color: {colors[i]};"/></td>
|
||||
<th scope="col"><a href="/monitoring/user/{id}?cluster={cluster.name}">{id}</a></th>
|
||||
<td>{totalCoreHours}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table>
|
||||
{#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>
|
||||
{#if groupSelection.key == 'User'}
|
||||
<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}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row cols={3} class="mb-2">
|
||||
@ -230,13 +292,13 @@
|
||||
</Col>
|
||||
<Col>
|
||||
<div bind:clientWidth={colWidth4}>
|
||||
{#key $statsQuery.data.stats[0].histNumNodes}
|
||||
{#key $statsQuery.data.stats[0].histNumCores}
|
||||
<Histogram
|
||||
width={colWidth4} height={300}
|
||||
data={convert2uplot($statsQuery.data.stats[0].histNumNodes)}
|
||||
title="Number of Nodes Distribution"
|
||||
xlabel="Allocated Nodes"
|
||||
xunit="Nodes"
|
||||
data={convert2uplot($statsQuery.data.stats[0].histNumCores)}
|
||||
title="Number of Cores Distribution"
|
||||
xlabel="Allocated Cores"
|
||||
xunit="Cores"
|
||||
ylabel="Number of Jobs"
|
||||
yunit="Jobs"/>
|
||||
{/key}
|
||||
|
Loading…
Reference in New Issue
Block a user