mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-09-07 01:03:00 +02:00
Merge branch 'dev' into review_logging
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
Card,
|
||||
Table,
|
||||
Icon,
|
||||
Tooltip
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import {
|
||||
init,
|
||||
@@ -70,6 +71,8 @@
|
||||
...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]),
|
||||
];
|
||||
|
||||
$: clusterName = cluster?.name ? cluster.name : cluster;
|
||||
|
||||
const sortOptions = [
|
||||
{ key: "totalWalltime", label: "Walltime" },
|
||||
{ key: "totalNodeHours", label: "Node Hours" },
|
||||
@@ -159,6 +162,7 @@
|
||||
groupBy: $groupBy
|
||||
) {
|
||||
id
|
||||
name
|
||||
totalWalltime
|
||||
totalNodeHours
|
||||
totalCoreHours
|
||||
@@ -422,15 +426,22 @@
|
||||
<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}"
|
||||
<th scope="col" id="topName-{te.id}"
|
||||
><a href="/monitoring/user/{te.id}?cluster={clusterName}"
|
||||
>{te.id}</a
|
||||
></th
|
||||
>
|
||||
{#if te?.name}
|
||||
<Tooltip
|
||||
target={`topName-${te.id}`}
|
||||
placement="left"
|
||||
>{te.name}</Tooltip
|
||||
>
|
||||
{/if}
|
||||
{:else}
|
||||
<th scope="col"
|
||||
><a
|
||||
href="/monitoring/jobs/?cluster={cluster}&project={te.id}&projectMatch=eq"
|
||||
href="/monitoring/jobs/?cluster={clusterName}&project={te.id}&projectMatch=eq"
|
||||
>{te.id}</a
|
||||
></th
|
||||
>
|
||||
|
@@ -58,7 +58,8 @@
|
||||
let plots = {},
|
||||
statsTable
|
||||
|
||||
let missingMetrics = [],
|
||||
let availableMetrics = new Set(),
|
||||
missingMetrics = [],
|
||||
missingHosts = [],
|
||||
somethingMissing = false;
|
||||
|
||||
@@ -128,7 +129,12 @@
|
||||
|
||||
const pendingMetrics = [
|
||||
...(ccconfig[`job_view_selectedMetrics:${job.cluster}`] ||
|
||||
ccconfig[`job_view_selectedMetrics`]
|
||||
$initq.data.globalMetrics.reduce((names, gm) => {
|
||||
if (gm.availability.find((av) => av.cluster === job.cluster)) {
|
||||
names.push(gm.name);
|
||||
}
|
||||
return names;
|
||||
}, [])
|
||||
),
|
||||
...(ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}`] ||
|
||||
ccconfig[`job_view_nodestats_selectedMetrics`]
|
||||
@@ -293,7 +299,7 @@
|
||||
{#if $initq.data}
|
||||
<Col xs="auto">
|
||||
<Button outline on:click={() => (isMetricsSelectionOpen = true)} color="primary">
|
||||
Select Metrics
|
||||
Select Metrics (Selected {selectedMetrics.length} of {availableMetrics.size} available)
|
||||
</Button>
|
||||
</Col>
|
||||
{/if}
|
||||
@@ -431,6 +437,7 @@
|
||||
configName="job_view_selectedMetrics"
|
||||
bind:metrics={selectedMetrics}
|
||||
bind:isOpen={isMetricsSelectionOpen}
|
||||
bind:allMetrics={availableMetrics}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
@@ -19,6 +19,7 @@
|
||||
Progress,
|
||||
Icon,
|
||||
Button,
|
||||
Tooltip
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import {
|
||||
queryStore,
|
||||
@@ -75,9 +76,9 @@
|
||||
);
|
||||
|
||||
let isHistogramSelectionOpen = false;
|
||||
$: metricsInHistograms = cluster
|
||||
? ccconfig[`user_view_histogramMetrics:${cluster}`] || []
|
||||
: ccconfig.user_view_histogramMetrics || [];
|
||||
$: selectedHistograms = cluster
|
||||
? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||
: ccconfig['user_view_histogramMetrics'] || [];
|
||||
|
||||
const client = getContextClient();
|
||||
// Note: nodeMetrics are requested on configured $timestep resolution
|
||||
@@ -90,7 +91,7 @@
|
||||
$metrics: [String!]
|
||||
$from: Time!
|
||||
$to: Time!
|
||||
$metricsInHistograms: [String!]
|
||||
$selectedHistograms: [String!]
|
||||
) {
|
||||
nodeMetrics(
|
||||
cluster: $cluster
|
||||
@@ -116,7 +117,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) {
|
||||
stats: jobsStatistics(filter: $filter, metrics: $selectedHistograms) {
|
||||
histDuration {
|
||||
count
|
||||
value
|
||||
@@ -157,7 +158,7 @@
|
||||
from: from.toISOString(),
|
||||
to: to.toISOString(),
|
||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
||||
metricsInHistograms: metricsInHistograms,
|
||||
selectedHistograms: selectedHistograms,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -177,6 +178,7 @@
|
||||
groupBy: USER
|
||||
) {
|
||||
id
|
||||
name
|
||||
totalJobs
|
||||
totalNodes
|
||||
totalCores
|
||||
@@ -515,12 +517,19 @@
|
||||
{#each $topUserQuery.data.topUser as tu, i}
|
||||
<tr>
|
||||
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
|
||||
<th scope="col"
|
||||
<th scope="col" id="topName-{tu.id}"
|
||||
><a
|
||||
href="/monitoring/user/{tu.id}?cluster={cluster}&state=running"
|
||||
>{tu.id}</a
|
||||
></th
|
||||
>
|
||||
{#if tu?.name}
|
||||
<Tooltip
|
||||
target={`topName-${tu.id}`}
|
||||
placement="left"
|
||||
>{tu.name}</Tooltip
|
||||
>
|
||||
{/if}
|
||||
<td>{tu[topUserSelection.key]}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -652,7 +661,7 @@
|
||||
|
||||
<!-- Selectable Stats as Histograms : Average Values of Running Jobs -->
|
||||
|
||||
{#if metricsInHistograms}
|
||||
{#if selectedHistograms}
|
||||
{#key $mainQuery.data.stats[0].histMetrics}
|
||||
<PlotGrid
|
||||
let:item
|
||||
@@ -675,6 +684,6 @@
|
||||
|
||||
<HistogramSelection
|
||||
bind:cluster
|
||||
bind:metricsInHistograms
|
||||
bind:selectedHistograms
|
||||
bind:isOpen={isHistogramSelectionOpen}
|
||||
/>
|
||||
|
@@ -68,16 +68,16 @@
|
||||
let durationBinOptions = ["1m","10m","1h","6h","12h"];
|
||||
let metricBinOptions = [10, 20, 50, 100];
|
||||
|
||||
$: metricsInHistograms = selectedCluster
|
||||
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || []
|
||||
: ccconfig.user_view_histogramMetrics || [];
|
||||
$: selectedHistograms = selectedCluster
|
||||
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||
: ccconfig['user_view_histogramMetrics'] || [];
|
||||
|
||||
const client = getContextClient();
|
||||
$: stats = queryStore({
|
||||
client: client,
|
||||
query: gql`
|
||||
query ($jobFilters: [JobFilter!]!, $metricsInHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
|
||||
jobsStatistics(filter: $jobFilters, metrics: $metricsInHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) {
|
||||
query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
|
||||
jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) {
|
||||
totalJobs
|
||||
shortJobs
|
||||
totalWalltime
|
||||
@@ -104,7 +104,7 @@
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { jobFilters, metricsInHistograms, numDurationBins, numMetricBins },
|
||||
variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins },
|
||||
});
|
||||
|
||||
onMount(() => filterComponent.updateFilters());
|
||||
@@ -290,7 +290,7 @@
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
{#if metricsInHistograms?.length > 0}
|
||||
{#if selectedHistograms?.length > 0}
|
||||
{#if $stats.error}
|
||||
<Row>
|
||||
<Col>
|
||||
@@ -357,6 +357,6 @@
|
||||
|
||||
<HistogramSelection
|
||||
bind:cluster={selectedCluster}
|
||||
bind:metricsInHistograms
|
||||
bind:selectedHistograms
|
||||
bind:isOpen={isHistogramSelectionOpen}
|
||||
/>
|
||||
|
@@ -43,26 +43,31 @@
|
||||
<ModalBody>
|
||||
{#if $initialized}
|
||||
<h4>Cluster</h4>
|
||||
<ListGroup>
|
||||
<ListGroupItem
|
||||
disabled={disableClusterSelection}
|
||||
active={pendingCluster == null}
|
||||
on:click={() => ((pendingCluster = null), (pendingPartition = null))}
|
||||
>
|
||||
Any Cluster
|
||||
</ListGroupItem>
|
||||
{#each clusters as cluster}
|
||||
{#if disableClusterSelection}
|
||||
<Button color="info" class="w-100 mb-2" disabled><b>Info: Cluster Selection Disabled in This View</b></Button>
|
||||
<Button outline color="primary" class="w-100 mb-2" disabled><b>Selected Cluster: {cluster}</b></Button>
|
||||
{:else}
|
||||
<ListGroup>
|
||||
<ListGroupItem
|
||||
disabled={disableClusterSelection}
|
||||
active={pendingCluster == cluster.name}
|
||||
on:click={() => (
|
||||
(pendingCluster = cluster.name), (pendingPartition = null)
|
||||
)}
|
||||
active={pendingCluster == null}
|
||||
on:click={() => ((pendingCluster = null), (pendingPartition = null))}
|
||||
>
|
||||
{cluster.name}
|
||||
Any Cluster
|
||||
</ListGroupItem>
|
||||
{/each}
|
||||
</ListGroup>
|
||||
{#each clusters as cluster}
|
||||
<ListGroupItem
|
||||
disabled={disableClusterSelection}
|
||||
active={pendingCluster == cluster.name}
|
||||
on:click={() => (
|
||||
(pendingCluster = cluster.name), (pendingPartition = null)
|
||||
)}
|
||||
>
|
||||
{cluster.name}
|
||||
</ListGroupItem>
|
||||
{/each}
|
||||
</ListGroup>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if $initialized && pendingCluster != null}
|
||||
<br />
|
||||
|
@@ -179,7 +179,7 @@
|
||||
function render(plotData) {
|
||||
if (plotData) {
|
||||
const opts = {
|
||||
title: "",
|
||||
title: "CPU Roofline Diagram",
|
||||
mode: 2,
|
||||
width: width,
|
||||
height: height,
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
Properties:
|
||||
- `cluster String`: Currently selected cluster
|
||||
- `metricsInHistograms [String]`: The currently selected metrics to display as histogram
|
||||
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
|
||||
- ìsOpen Bool`: Is selection opened
|
||||
-->
|
||||
|
||||
@@ -21,22 +21,27 @@
|
||||
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||
|
||||
export let cluster;
|
||||
export let metricsInHistograms;
|
||||
export let selectedHistograms;
|
||||
export let isOpen;
|
||||
|
||||
const client = getContextClient();
|
||||
const initialized = getContext("initialized");
|
||||
|
||||
let availableMetrics = []
|
||||
function loadHistoMetrics(isInitialized, thisCluster) {
|
||||
if (!isInitialized) return [];
|
||||
|
||||
function loadHistoMetrics(isInitialized) {
|
||||
if (!isInitialized) return;
|
||||
const rawAvailableMetrics = getContext("globalMetrics").filter((gm) => gm?.footprint).map((fgm) => { return fgm.name })
|
||||
availableMetrics = [...rawAvailableMetrics]
|
||||
if (!thisCluster) {
|
||||
return getContext("globalMetrics")
|
||||
.filter((gm) => gm?.footprint)
|
||||
.map((fgm) => { return fgm.name })
|
||||
} else {
|
||||
return getContext("globalMetrics")
|
||||
.filter((gm) => gm?.availability.find((av) => av.cluster == thisCluster))
|
||||
.filter((agm) => agm?.footprint)
|
||||
.map((afgm) => { return afgm.name })
|
||||
}
|
||||
}
|
||||
|
||||
let pendingMetrics = [...metricsInHistograms]; // Copy
|
||||
|
||||
const updateConfigurationMutation = ({ name, value }) => {
|
||||
return mutationStore({
|
||||
client: client,
|
||||
@@ -61,17 +66,16 @@
|
||||
}
|
||||
|
||||
function closeAndApply() {
|
||||
metricsInHistograms = [...pendingMetrics]; // Set for parent
|
||||
isOpen = !isOpen;
|
||||
updateConfiguration({
|
||||
name: cluster
|
||||
? `user_view_histogramMetrics:${cluster}`
|
||||
: "user_view_histogramMetrics",
|
||||
value: metricsInHistograms,
|
||||
value: selectedHistograms,
|
||||
});
|
||||
}
|
||||
|
||||
$: loadHistoMetrics($initialized);
|
||||
$: availableMetrics = loadHistoMetrics($initialized, cluster);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -81,7 +85,7 @@
|
||||
<ListGroup>
|
||||
{#each availableMetrics as metric (metric)}
|
||||
<ListGroupItem>
|
||||
<input type="checkbox" bind:group={pendingMetrics} value={metric} />
|
||||
<input type="checkbox" bind:group={selectedHistograms} value={metric} />
|
||||
{metric}
|
||||
</ListGroupItem>
|
||||
{/each}
|
||||
|
@@ -18,6 +18,8 @@
|
||||
InputGroup,
|
||||
InputGroupText,
|
||||
Icon,
|
||||
Row,
|
||||
Col
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { maxScope } from "../generic/utils.js";
|
||||
import StatsTableEntry from "./StatsTableEntry.svelte";
|
||||
@@ -26,7 +28,7 @@
|
||||
export let job;
|
||||
export let jobMetrics;
|
||||
|
||||
const allMetrics = [...new Set(jobMetrics.map((m) => m.name))].sort()
|
||||
const sortedJobMetrics = [...new Set(jobMetrics.map((m) => m.name))].sort()
|
||||
const scopesForMetric = (metric) =>
|
||||
jobMetrics.filter((jm) => jm.name == metric).map((jm) => jm.scope);
|
||||
|
||||
@@ -34,11 +36,12 @@
|
||||
selectedScopes = {},
|
||||
sorting = {},
|
||||
isMetricSelectionOpen = false,
|
||||
availableMetrics = new Set(),
|
||||
selectedMetrics =
|
||||
getContext("cc-config")[`job_view_nodestats_selectedMetrics:${job.cluster}`] ||
|
||||
getContext("cc-config")["job_view_nodestats_selectedMetrics"];
|
||||
|
||||
for (let metric of allMetrics) {
|
||||
for (let metric of sortedJobMetrics) {
|
||||
// Not Exclusive or Multi-Node: get maxScope directly (mostly: node)
|
||||
// -> Else: Load smallest available granularity as default as per availability
|
||||
const availableScopes = scopesForMetric(metric);
|
||||
@@ -95,15 +98,19 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<Row>
|
||||
<Col class="m-2">
|
||||
<Button outline on:click={() => (isMetricSelectionOpen = true)} class="w-auto px-2" color="primary">
|
||||
Select Metrics (Selected {selectedMetrics.length} of {availableMetrics.size} available)
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr class="mb-1 mt-1"/>
|
||||
<Table class="mb-0">
|
||||
<thead>
|
||||
<!-- Header Row 1: Selectors -->
|
||||
<tr>
|
||||
<th>
|
||||
<Button outline on:click={() => (isMetricSelectionOpen = true)} class="w-100 px-2" color="primary">
|
||||
Select Metrics
|
||||
</Button>
|
||||
</th>
|
||||
<th/>
|
||||
{#each selectedMetrics as metric}
|
||||
<!-- To Match Row-2 Header Field Count-->
|
||||
<th colspan={selectedScopes[metric] == "node" ? 3 : 4}>
|
||||
@@ -163,7 +170,7 @@
|
||||
<MetricSelection
|
||||
cluster={job.cluster}
|
||||
configName="job_view_nodestats_selectedMetrics"
|
||||
allMetrics={new Set(allMetrics)}
|
||||
bind:allMetrics={availableMetrics}
|
||||
bind:metrics={selectedMetrics}
|
||||
bind:isOpen={isMetricSelectionOpen}
|
||||
/>
|
||||
|
Reference in New Issue
Block a user