mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29: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 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!
|
||||||
|
@ -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!
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user