Implement node filter in frontend, fix backend

- Add running job count and link to list to single node view
This commit is contained in:
Christoph Kluge 2023-06-30 12:01:27 +02:00
parent b5a7249ad5
commit c04aea89c9
4 changed files with 85 additions and 16 deletions

View File

@ -184,6 +184,9 @@ func buildFilterPresets(query url.Values) map[string]interface{} {
}
}
}
if query.Get("node") != "" {
filterPresets["node"] = query.Get("node")
}
if query.Get("numNodes") != "" {
parts := strings.Split(query.Get("numNodes"), "-")
if len(parts) == 2 {

View File

@ -23,7 +23,7 @@
const ccconfig = getContext('cc-config')
const clusters = getContext('clusters')
const client = getContextClient();
const query = gql`query($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
const nodeMetricsQuery = gql`query($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
host
subCluster
@ -42,9 +42,9 @@
}
}`;
$: nodesQuery = queryStore({
$: nodeMetricsData = queryStore({
client: client,
query: query,
query: nodeMetricsQuery,
variables: {
cluster: cluster,
nodes: [hostname],
@ -53,8 +53,44 @@
}
});
let itemsPerPage = ccconfig.plot_list_jobsPerPage;
let page = 1;
let paging = { itemsPerPage, page };
let sorting = { field: "startTime", order: "DESC" };
$: filter = [
{cluster: { eq: cluster }},
{node: { eq: hostname }},
{state: 'running'},
{startTime: {
from: from.toISOString(),
to: to.toISOString()
}}
];
const nodeJobsQuery = gql`
query (
$filter: [JobFilter!]!
$sorting: OrderByInput!
$paging: PageRequest!
) {
jobs(filter: $filter, order: $sorting, page: $paging) {
# items {
# id
# jobId
# }
count
}
}
`;
$: nodeJobsData = queryStore({
client: client,
query: nodeJobsQuery,
variables: { paging, sorting, filter }
});
let metricUnits = {}
$: if ($nodesQuery.data) {
$: if ($nodeMetricsData.data) {
let thisCluster = clusters.find(c => c.name == cluster)
if (thisCluster) {
for (let metric of thisCluster.metricConfig) {
@ -67,7 +103,7 @@
}
}
// $: console.log($nodesQuery?.data?.nodeMetrics[0].metrics)
const dateToUnixEpoch = rfc3339 => Math.floor(Date.parse(rfc3339) / 1000)
</script>
<Row>
@ -82,6 +118,18 @@
<InputGroupText>{hostname} ({cluster})</InputGroupText>
</InputGroup>
</Col>
<Col>
{#if $nodeJobsData.fetching }
<Spinner/>
{:else}
{#if $nodeJobsData.data}
Currently running jobs on this node: { $nodeJobsData.data.jobs.count }
[ <a href="/monitoring/jobs/?cluster={cluster}&state=running&startTime={dateToUnixEpoch(from)}-{dateToUnixEpoch(to)}&node={hostname}" target="_blank">View in Job List</a> ]
{:else}
No currently running jobs.
{/if}
{/if}
</Col>
<Col>
<TimeSelection
bind:from={from}
@ -92,9 +140,9 @@
<br/>
<Row>
<Col>
{#if $nodesQuery.error}
<Card body color="danger">{$nodesQuery.error.message}</Card>
{:else if $nodesQuery.fetching || $initq.fetching}
{#if $nodeMetricsData.error}
<Card body color="danger">{$nodeMetricsData.error.message}</Card>
{:else if $nodeMetricsData.fetching || $initq.fetching}
<Spinner/>
{:else}
<PlotTable
@ -102,18 +150,18 @@
let:width
renderFor="node"
itemsPerRow={ccconfig.plot_view_plotsPerRow}
items={$nodesQuery.data.nodeMetrics[0].metrics
.map(m => ({ ...m, disabled: checkMetricDisabled(m.name, cluster, $nodesQuery.data.nodeMetrics[0].subCluster)}))
items={$nodeMetricsData.data.nodeMetrics[0].metrics
.map(m => ({ ...m, disabled: checkMetricDisabled(m.name, cluster, $nodeMetricsData.data.nodeMetrics[0].subCluster)}))
.sort((a, b) => a.name.localeCompare(b.name))}>
<h4 style="text-align: center; padding-top:15px;">{item.name} {metricUnits[item.name]}</h4>
{#if item.disabled === false && item.metric}
<MetricPlot
width={width} height={300} metric={item.name} timestep={item.metric.timestep}
cluster={clusters.find(c => c.name == cluster)} subCluster={$nodesQuery.data.nodeMetrics[0].subCluster}
cluster={clusters.find(c => c.name == cluster)} subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster}
series={item.metric.series} />
{:else if item.disabled === true && item.metric}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info">Metric disabled for subcluster <code>{item.name}:{$nodesQuery.data.nodeMetrics[0].subCluster}</code></Card>
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info">Metric disabled for subcluster <code>{item.name}:{$nodeMetricsData.data.nodeMetrics[0].subCluster}</code></Card>
{:else}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="warning">No dataset returned for <code>{item.name}</code></Card>
{/if}

View File

@ -47,6 +47,7 @@
project: filterPresets.project || '',
jobName: filterPresets.jobName || '',
node: filterPresets.node || null,
numNodes: filterPresets.numNodes || { from: null, to: null },
numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
numAccelerators: filterPresets.numAccelerators || { from: null, to: null },
@ -74,6 +75,8 @@
let items = []
if (filters.cluster)
items.push({ cluster: { eq: filters.cluster } })
if (filters.node)
items.push({ node: { contains: filters.node } })
if (filters.partition)
items.push({ partition: { eq: filters.partition } })
if (filters.states.length != allJobStates.length)
@ -114,6 +117,8 @@
let opts = []
if (filters.cluster)
opts.push(`cluster=${filters.cluster}`)
if (filters.node)
opts.push(`node=${filters.node}`)
if (filters.partition)
opts.push(`partition=${filters.partition}`)
if (filters.states.length != allJobStates.length)
@ -272,6 +277,12 @@
</Info>
{/if}
{#if filters.node != null }
<Info icon="hdd-stack" on:click={() => (isResourcesOpen = true)}>
Node: {filters.node}
</Info>
{/if}
{#if filters.stats.length > 0}
<Info icon="bar-chart" on:click={() => (isStatsOpen = true)}>
{filters.stats.map(stat => `${stat.text}: ${stat.from} - ${stat.to}`).join(', ')}
@ -318,6 +329,7 @@
bind:numNodes={filters.numNodes}
bind:numHWThreads={filters.numHWThreads}
bind:numAccelerators={filters.numAccelerators}
bind:namedNode={filters.node}
bind:isNodesModified={isNodesModified}
bind:isHwthreadsModified={isHwthreadsModified}
bind:isAccsModified={isAccsModified}

View File

@ -16,8 +16,9 @@
export let isNodesModified = false
export let isHwthreadsModified = false
export let isAccsModified = false
export let namedNode = null
let pendingNumNodes = numNodes, pendingNumHWThreads = numHWThreads, pendingNumAccelerators = numAccelerators
let pendingNumNodes = numNodes, pendingNumHWThreads = numHWThreads, pendingNumAccelerators = numAccelerators, pendingNamedNode = namedNode
const findMaxNumAccels = clusters => clusters.reduce((max, cluster) => Math.max(max,
cluster.subClusters.reduce((max, sc) => Math.max(max, sc.topology.accelerators?.length || 0), 0)), 0)
@ -76,7 +77,9 @@
Select number of utilized Resources
</ModalHeader>
<ModalBody>
<h6>Number of Nodes</h6>
<h6>Named Node</h6>
<input type="text" class="form-control" bind:value={pendingNamedNode}>
<h6 style="margin-top: 1rem;">Number of Nodes</h6>
<DoubleRangeSlider
on:change={({ detail }) => {
pendingNumNodes = { from: detail[0], to: detail[1] }
@ -117,7 +120,8 @@
numNodes ={ from: pendingNumNodes.from, to: pendingNumNodes.to }
numHWThreads = { from: pendingNumHWThreads.from, to: pendingNumHWThreads.to }
numAccelerators = { from: pendingNumAccelerators.from, to: pendingNumAccelerators.to }
dispatch('update', { numNodes, numHWThreads, numAccelerators })
namedNode = pendingNamedNode
dispatch('update', { numNodes, numHWThreads, numAccelerators, namedNode })
}}>
Close & Apply
</Button>
@ -126,13 +130,15 @@
pendingNumNodes = { from: null, to: null }
pendingNumHWThreads = { from: null, to: null }
pendingNumAccelerators = { from: null, to: null }
pendingNamedNode = null
numNodes = { from: pendingNumNodes.from, to: pendingNumNodes.to }
numHWThreads = { from: pendingNumHWThreads.from, to: pendingNumHWThreads.to }
numAccelerators = { from: pendingNumAccelerators.from, to: pendingNumAccelerators.to }
isNodesModified = false
isHwthreadsModified = false
isAccsModified = false
dispatch('update', { numNodes, numHWThreads, numAccelerators })
namedNode = pendingNamedNode
dispatch('update', { numNodes, numHWThreads, numAccelerators, namedNode})
}}>Reset</Button>
<Button on:click={() => (isOpen = false)}>Close</Button>
</ModalFooter>