mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-08-23 18:43:00 +02:00
move and add interface options for status tabs
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
@component Main cluster status view component; renders current system-usage information
|
@component Main cluster status view component; renders current system-usage information
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster String`: The cluster to show status information for
|
- `presetCluster String`: The cluster to show status information for
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -18,78 +18,45 @@
|
|||||||
TabPane
|
TabPane
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
import Refresher from "./generic/helper/Refresher.svelte";
|
|
||||||
import StatusDash from "./status/StatusDash.svelte";
|
import StatusDash from "./status/StatusDash.svelte";
|
||||||
import UsageDash from "./status/UsageDash.svelte";
|
import UsageDash from "./status/UsageDash.svelte";
|
||||||
import NodeDash from "./status/NodeDash.svelte";
|
|
||||||
import StatisticsDash from "./status/StatisticsDash.svelte";
|
import StatisticsDash from "./status/StatisticsDash.svelte";
|
||||||
import DevelDash from "./status/DevelDash.svelte";
|
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
cluster
|
presetCluster
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/*Const Init */
|
/*Const Init */
|
||||||
const useCbColors = getContext("cc-config")?.plot_general_colorblindMode || false
|
const useCbColors = getContext("cc-config")?.plot_general_colorblindMode || false
|
||||||
|
|
||||||
/* State Init */
|
|
||||||
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
|
||||||
let to = $state(new Date(Date.now()));
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Loading indicator & Refresh -->
|
<!-- Loading indicator & Refresh -->
|
||||||
|
|
||||||
<Row cols={{ xs: 2 }} class="mb-2">
|
<Row cols={1} class="mb-2">
|
||||||
<Col>
|
<Col>
|
||||||
<h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
|
<h3 class="mb-0">Current Status of Cluster "{presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)}"</h3>
|
||||||
</Col>
|
|
||||||
<Col class="mt-2 mt-md-0">
|
|
||||||
<Refresher
|
|
||||||
initially={120}
|
|
||||||
onRefresh={() => {
|
|
||||||
from = new Date(Date.now() - 5 * 60 * 1000);
|
|
||||||
to = new Date(Date.now());
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<!-- <Row cols={1} class="text-center mt-3">
|
|
||||||
<Col>
|
|
||||||
{#if $initq.fetching || $mainQuery.fetching}
|
|
||||||
<Spinner />
|
|
||||||
{:else if $initq.error}
|
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
|
||||||
{/if}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{#if $mainQuery.error}
|
|
||||||
<Row cols={1}>
|
|
||||||
<Col>
|
|
||||||
<Card body color="danger">{$mainQuery.error.message}</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/if} -->
|
|
||||||
|
|
||||||
<Card class="overflow-auto" style="height: auto;">
|
<Card class="overflow-auto" style="height: auto;">
|
||||||
<TabContent>
|
<TabContent>
|
||||||
<TabPane tabId="status-dash" tab="Status" active>
|
<TabPane tabId="status-dash" tab="Status" active>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<StatusDash {cluster} {useCbColors} useAltColors></StatusDash>
|
<StatusDash {presetCluster} {useCbColors} useAltColors></StatusDash>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tabId="usage-dash" tab="Usage">
|
<TabPane tabId="usage-dash" tab="Usage">
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<UsageDash {cluster} {useCbColors}></UsageDash>
|
<UsageDash {presetCluster} {useCbColors}></UsageDash>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tabId="metric-dash" tab="Statistics">
|
<TabPane tabId="metric-dash" tab="Statistics">
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<StatisticsDash {cluster} {useCbColors}></StatisticsDash>
|
<StatisticsDash {presetCluster} {useCbColors}></StatisticsDash>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
@@ -404,6 +404,7 @@
|
|||||||
cluster={selectedCluster}
|
cluster={selectedCluster}
|
||||||
bind:isOpen={isHistogramSelectionOpen}
|
bind:isOpen={isHistogramSelectionOpen}
|
||||||
presetSelectedHistograms={selectedHistograms}
|
presetSelectedHistograms={selectedHistograms}
|
||||||
|
configName="user_view_histogramMetrics"
|
||||||
applyChange={(newSelection) => {
|
applyChange={(newSelection) => {
|
||||||
selectedHistogramsBuffer[selectedCluster || 'all'] = [...newSelection];
|
selectedHistogramsBuffer[selectedCluster || 'all'] = [...newSelection];
|
||||||
}}
|
}}
|
||||||
|
@@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster String`: Currently selected cluster
|
- `cluster String`: Currently selected cluster
|
||||||
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
|
|
||||||
- `ìsOpen Bool`: Is selection opened [Bindable]
|
- `ìsOpen Bool`: Is selection opened [Bindable]
|
||||||
|
- `configName String`: The config id string to be updated in database on selection change
|
||||||
|
- `presetSelectedHistograms [String]`: The currently selected metrics to display as histogram
|
||||||
- `applyChange Func`: The callback function to apply current selection
|
- `applyChange Func`: The callback function to apply current selection
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
let {
|
let {
|
||||||
cluster,
|
cluster,
|
||||||
isOpen = $bindable(),
|
isOpen = $bindable(),
|
||||||
|
configName,
|
||||||
presetSelectedHistograms,
|
presetSelectedHistograms,
|
||||||
applyChange
|
applyChange
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -67,8 +69,8 @@
|
|||||||
applyChange(selectedHistograms)
|
applyChange(selectedHistograms)
|
||||||
updateConfiguration({
|
updateConfiguration({
|
||||||
name: cluster
|
name: cluster
|
||||||
? `user_view_histogramMetrics:${cluster}`
|
? `${configName}:${cluster}`
|
||||||
: "user_view_histogramMetrics",
|
: configName,
|
||||||
value: selectedHistograms,
|
value: selectedHistograms,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import Status from './Status.root.svelte'
|
|||||||
mount(Status, {
|
mount(Status, {
|
||||||
target: document.getElementById('svelte-app'),
|
target: document.getElementById('svelte-app'),
|
||||||
props: {
|
props: {
|
||||||
cluster: infos.cluster,
|
presetCluster: infos.cluster,
|
||||||
},
|
},
|
||||||
context: new Map([
|
context: new Map([
|
||||||
['cc-config', clusterCockpitConfig]
|
['cc-config', clusterCockpitConfig]
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
@component Main cluster status view component; renders current system-usage information
|
@component Main cluster status view component; renders current system-usage information
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster String`: The cluster to show status information for
|
- `presetCluster String`: The cluster to show status information for
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
queryStore,
|
queryStore,
|
||||||
gql,
|
gql,
|
||||||
getContextClient,
|
getContextClient,
|
||||||
// mutationStore,
|
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
import {
|
import {
|
||||||
init,
|
init,
|
||||||
@@ -28,10 +27,11 @@
|
|||||||
import PlotGrid from "../generic/PlotGrid.svelte";
|
import PlotGrid from "../generic/PlotGrid.svelte";
|
||||||
import Histogram from "../generic/plots/Histogram.svelte";
|
import Histogram from "../generic/plots/Histogram.svelte";
|
||||||
import HistogramSelection from "../generic/select/HistogramSelection.svelte";
|
import HistogramSelection from "../generic/select/HistogramSelection.svelte";
|
||||||
|
import Refresher from "../generic/helper/Refresher.svelte";
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
cluster
|
presetCluster
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
@@ -40,16 +40,17 @@
|
|||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
|
let cluster = $state(presetCluster);
|
||||||
// Histrogram
|
// Histogram
|
||||||
let isHistogramSelectionOpen = $state(false);
|
let isHistogramSelectionOpen = $state(false);
|
||||||
|
let from = $state(new Date(Date.now() - (30 * 24 * 60 * 60 * 1000))); // Simple way to retrigger GQL: Jobs Started last Month
|
||||||
// TODO: Originally Uses User View Selection! -> Change to Status View
|
let to = $state(new Date(Date.now()));
|
||||||
let selectedHistograms = $state(cluster
|
|
||||||
? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
|
||||||
: ccconfig['user_view_histogramMetrics'] || []);
|
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
|
let selectedHistograms = $derived(cluster
|
||||||
|
? ccconfig[`status_view_selectedHistograms:${cluster}`] || ( ccconfig['status_view_selectedHistograms'] || [] )
|
||||||
|
: ccconfig['status_view_selectedHistograms'] || []);
|
||||||
|
|
||||||
// Note: nodeMetrics are requested on configured $timestep resolution
|
// Note: nodeMetrics are requested on configured $timestep resolution
|
||||||
const metricStatusQuery = $derived(queryStore({
|
const metricStatusQuery = $derived(queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
@@ -73,43 +74,16 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster}}, {startTime: { from, to }}],
|
||||||
selectedHistograms: selectedHistograms,
|
selectedHistograms: selectedHistograms,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
// TODO: Originally Uses User View Selection! -> Change to Status View : Adapt Mutations from TopUserSelect
|
|
||||||
// function updateTopUserConfiguration(select) {
|
|
||||||
// if (ccconfig[`status_view_selectedHistograms:${cluster}`] != select) {
|
|
||||||
// updateConfigurationMutation({
|
|
||||||
// name: `status_view_selectedHistograms:${cluster}`,
|
|
||||||
// value: JSON.stringify(select),
|
|
||||||
// }).subscribe((res) => {
|
|
||||||
// if (res.fetching === false && res.error) {
|
|
||||||
// throw res.error;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const updateConfigurationMutation = ({ name, value }) => {
|
|
||||||
// return mutationStore({
|
|
||||||
// client: client,
|
|
||||||
// query: gql`
|
|
||||||
// mutation ($name: String!, $value: String!) {
|
|
||||||
// updateConfiguration(name: $name, value: $value)
|
|
||||||
// }
|
|
||||||
// `,
|
|
||||||
// variables: { name, value },
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Loading indicators & Metric Sleect -->
|
<!-- Loading indicators & Metric Sleect -->
|
||||||
<Row cols={{ xs: 1 }}>
|
<Row class="justify-content-between">
|
||||||
<Col class="text-md-end">
|
<Col class="mb-2 mb-md-0" xs="12" md="5" lg="4" xl="3">
|
||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -118,7 +92,17 @@
|
|||||||
<Icon name="bar-chart-line" /> Select Histograms
|
<Icon name="bar-chart-line" /> Select Histograms
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col xs="12" md="5" lg="4" xl="3">
|
||||||
|
<Refresher
|
||||||
|
initially={120}
|
||||||
|
onRefresh={() => {
|
||||||
|
from = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)); // Triggers GQL
|
||||||
|
to = new Date(Date.now());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row cols={1} class="text-center mt-3">
|
<Row cols={1} class="text-center mt-3">
|
||||||
<Col>
|
<Col>
|
||||||
{#if $initq.fetching || $metricStatusQuery.fetching}
|
{#if $initq.fetching || $metricStatusQuery.fetching}
|
||||||
@@ -168,6 +152,7 @@
|
|||||||
{cluster}
|
{cluster}
|
||||||
bind:isOpen={isHistogramSelectionOpen}
|
bind:isOpen={isHistogramSelectionOpen}
|
||||||
presetSelectedHistograms={selectedHistograms}
|
presetSelectedHistograms={selectedHistograms}
|
||||||
|
configName="status_view_selectedHistograms"
|
||||||
applyChange={(newSelection) => {
|
applyChange={(newSelection) => {
|
||||||
selectedHistograms = [...newSelection];
|
selectedHistograms = [...newSelection];
|
||||||
}}
|
}}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
@component Main cluster status view component; renders current system-usage information
|
@component Main cluster status view component; renders current system-usage information
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster String`: The cluster to show status information for
|
- `presetCluster String`: The cluster to show status information for
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -26,12 +26,13 @@
|
|||||||
init,
|
init,
|
||||||
} from "../generic/utils.js";
|
} from "../generic/utils.js";
|
||||||
import { scaleNumbers, formatTime } from "../generic/units.js";
|
import { scaleNumbers, formatTime } from "../generic/units.js";
|
||||||
|
import Refresher from "../generic/helper/Refresher.svelte";
|
||||||
import Roofline from "../generic/plots/Roofline.svelte";
|
import Roofline from "../generic/plots/Roofline.svelte";
|
||||||
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
cluster,
|
presetCluster,
|
||||||
useCbColors = false,
|
useCbColors = false,
|
||||||
useAltColors = false,
|
useAltColors = false,
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -41,10 +42,11 @@
|
|||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
let cluster = $state(presetCluster);
|
||||||
let to = $state(new Date(Date.now()));
|
|
||||||
let pieWidth = $state(0);
|
let pieWidth = $state(0);
|
||||||
let plotWidths = $state([]);
|
let plotWidths = $state([]);
|
||||||
|
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
||||||
|
let to = $state(new Date(Date.now()));
|
||||||
// Bar Gauges
|
// Bar Gauges
|
||||||
let allocatedNodes = $state({});
|
let allocatedNodes = $state({});
|
||||||
let allocatedAccs = $state({});
|
let allocatedAccs = $state({});
|
||||||
@@ -232,7 +234,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* Const Functions */
|
/* Const Functions */
|
||||||
// New: Sum Up Node Averages
|
|
||||||
const sumUp = (data, subcluster, metric) =>
|
const sumUp = (data, subcluster, metric) =>
|
||||||
data.reduce(
|
data.reduce(
|
||||||
(sum, node) =>
|
(sum, node) =>
|
||||||
@@ -246,22 +247,6 @@
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Old: SumUp Metric Time Data
|
|
||||||
// const sumUp = (data, subcluster, metric) =>
|
|
||||||
// data.reduce(
|
|
||||||
// (sum, node) =>
|
|
||||||
// node.subCluster == subcluster
|
|
||||||
// ? sum +
|
|
||||||
// (node.metrics
|
|
||||||
// .find((m) => m.name == metric)
|
|
||||||
// ?.metric.series.reduce(
|
|
||||||
// (sum, series) => sum + series.data[series.data.length - 1],
|
|
||||||
// 0,
|
|
||||||
// ) || 0)
|
|
||||||
// : sum,
|
|
||||||
// 0,
|
|
||||||
// );
|
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
function transformJobsStatsToData(subclusterData) {
|
function transformJobsStatsToData(subclusterData) {
|
||||||
/* c will contain values from 0 to 1 representing the duration */
|
/* c will contain values from 0 to 1 representing the duration */
|
||||||
@@ -311,8 +296,7 @@
|
|||||||
|
|
||||||
let intensity = f / m
|
let intensity = f / m
|
||||||
if (Number.isNaN(intensity) || !Number.isFinite(intensity)) {
|
if (Number.isNaN(intensity) || !Number.isFinite(intensity)) {
|
||||||
// continue // Old: Introduces mismatch between Data and Info Arrays
|
intensity = 0.0 // Set to Float Zero: Will not show in Log-Plot (Always below render limit)
|
||||||
intensity = 0.0 // New: Set to Float Zero: Will not show in Log-Plot (Always below render limit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
x.push(intensity)
|
x.push(intensity)
|
||||||
@@ -342,8 +326,6 @@
|
|||||||
if (subClusterData) { // && $nodesState?.data) {
|
if (subClusterData) { // && $nodesState?.data) {
|
||||||
// Use Nodes as Returned from CCMS, *NOT* as saved in DB via SlurmState-API!
|
// Use Nodes as Returned from CCMS, *NOT* as saved in DB via SlurmState-API!
|
||||||
for (let j = 0; j < subClusterData.length; j++) {
|
for (let j = 0; j < subClusterData.length; j++) {
|
||||||
// nodesCounts[subClusterData[i].subCluster] = $nodesState.data.nodes.count; // Probably better as own derived!
|
|
||||||
|
|
||||||
const nodeName = subClusterData[j]?.host ? subClusterData[j].host : "unknown"
|
const nodeName = subClusterData[j]?.host ? subClusterData[j].host : "unknown"
|
||||||
const nodeMatch = $statusQuery?.data?.nodes?.items?.find((n) => n.hostname == nodeName && n.subCluster == subClusterData[j].subCluster);
|
const nodeMatch = $statusQuery?.data?.nodes?.items?.find((n) => n.hostname == nodeName && n.subCluster == subClusterData[j].subCluster);
|
||||||
const nodeState = nodeMatch?.nodeState ? nodeMatch.nodeState : "notindb"
|
const nodeState = nodeMatch?.nodeState ? nodeMatch.nodeState : "notindb"
|
||||||
@@ -375,6 +357,21 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Refresher and space for other options -->
|
||||||
|
<Row class="justify-content-end">
|
||||||
|
<Col xs="12" md="5" lg="4" xl="3">
|
||||||
|
<Refresher
|
||||||
|
initially={120}
|
||||||
|
onRefresh={() => {
|
||||||
|
from = new Date(Date.now() - 5 * 60 * 1000);
|
||||||
|
to = new Date(Date.now());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
<!-- Node Health Pis, later Charts -->
|
<!-- Node Health Pis, later Charts -->
|
||||||
{#if $initq.data && $nodesStateCounts.data}
|
{#if $initq.data && $nodesStateCounts.data}
|
||||||
<Row cols={{ lg: 4, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
<Row cols={{ lg: 4, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
@component Main cluster status view component; renders current system-usage information
|
@component Main cluster status view component; renders current system-usage information
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster String`: The cluster to show status information for
|
- `presetCluster String`: The cluster to show status information for
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
Card,
|
Card,
|
||||||
Table,
|
Table,
|
||||||
Icon,
|
Icon,
|
||||||
Tooltip
|
Tooltip,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputGroupText
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import {
|
import {
|
||||||
queryStore,
|
queryStore,
|
||||||
@@ -28,10 +31,11 @@
|
|||||||
} from "../generic/utils.js";
|
} from "../generic/utils.js";
|
||||||
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
||||||
import Histogram from "../generic/plots/Histogram.svelte";
|
import Histogram from "../generic/plots/Histogram.svelte";
|
||||||
|
import Refresher from "../generic/helper/Refresher.svelte";
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
cluster,
|
presetCluster,
|
||||||
useCbColors = false,
|
useCbColors = false,
|
||||||
useAltColors = false
|
useAltColors = false
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -39,11 +43,16 @@
|
|||||||
/* Const Init */
|
/* Const Init */
|
||||||
const { query: initq } = init();
|
const { query: initq } = init();
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
const durationBinOptions = ["1m","10m","1h","6h","12h"];
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
|
let cluster = $state(presetCluster)
|
||||||
|
let from = $state(new Date(Date.now() - (30 * 24 * 60 * 60 * 1000))); // Simple way to retrigger GQL: Jobs Started last Month
|
||||||
|
let to = $state(new Date(Date.now()));
|
||||||
let colWidthJobs = $state(0);
|
let colWidthJobs = $state(0);
|
||||||
let colWidthNodes = $state(0);
|
let colWidthNodes = $state(0);
|
||||||
let colWidthAccs = $state(0);
|
let colWidthAccs = $state(0);
|
||||||
|
let numDurationBins = $state("1h");
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
const topJobsQuery = $derived(queryStore({
|
const topJobsQuery = $derived(queryStore({
|
||||||
@@ -75,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster}}, {startTime: { from, to }}],
|
||||||
paging: { itemsPerPage: 10, page: 1 } // Top 10
|
paging: { itemsPerPage: 10, page: 1 } // Top 10
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -109,7 +118,7 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster }}, {startTime: { from, to }}],
|
||||||
paging: { itemsPerPage: 10, page: 1 } // Top 10
|
paging: { itemsPerPage: 10, page: 1 } // Top 10
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -143,7 +152,7 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster }}, {startTime: { from, to }}],
|
||||||
paging: { itemsPerPage: 10, page: 1 } // Top 10
|
paging: { itemsPerPage: 10, page: 1 } // Top 10
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -155,8 +164,9 @@
|
|||||||
query (
|
query (
|
||||||
$filter: [JobFilter!]!
|
$filter: [JobFilter!]!
|
||||||
$selectedHistograms: [String!]
|
$selectedHistograms: [String!]
|
||||||
|
$numDurationBins: String
|
||||||
) {
|
) {
|
||||||
jobsStatistics(filter: $filter, metrics: $selectedHistograms) {
|
jobsStatistics(filter: $filter, metrics: $selectedHistograms, numDurationBins: $numDurationBins) {
|
||||||
histDuration {
|
histDuration {
|
||||||
count
|
count
|
||||||
value
|
value
|
||||||
@@ -173,8 +183,9 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster }}, {startTime: { from, to }}],
|
||||||
selectedHistograms: [], // No Metrics requested for node hardware stats
|
selectedHistograms: [], // No Metrics requested for node hardware stats
|
||||||
|
numDurationBins: numDurationBins,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -191,25 +202,58 @@
|
|||||||
}
|
}
|
||||||
return c[(c.length + targetIdx) % c.length];
|
return c[(c.length + targetIdx) % c.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Refresher and space for other options -->
|
||||||
|
<Row class="justify-content-between">
|
||||||
|
<Col class="mb-2 mb-md-0" xs="12" md="5" lg="4" xl="3">
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupText>
|
||||||
|
<Icon name="bar-chart-line-fill" />
|
||||||
|
</InputGroupText>
|
||||||
|
<InputGroupText>
|
||||||
|
Duration Bin Size
|
||||||
|
</InputGroupText>
|
||||||
|
<Input type="select" bind:value={numDurationBins}>
|
||||||
|
{#each durationBinOptions as dbin}
|
||||||
|
<option value={dbin}>{dbin}</option>
|
||||||
|
{/each}
|
||||||
|
</Input>
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
|
<Col xs="12" md="5" lg="4" xl="3">
|
||||||
|
<Refresher
|
||||||
|
initially={120}
|
||||||
|
onRefresh={() => {
|
||||||
|
from = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)); // Triggers GQL
|
||||||
|
to = new Date(Date.now());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
<!-- Job Duration, Top Users and Projects-->
|
<!-- Job Duration, Top Users and Projects-->
|
||||||
{#if $topJobsQuery.fetching || $nodeStatusQuery.fetching}
|
{#if $topJobsQuery.fetching || $nodeStatusQuery.fetching}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else if $topJobsQuery.data && $nodeStatusQuery.data}
|
{:else if $topJobsQuery.data && $nodeStatusQuery.data}
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs="12" lg="4" class="p-2">
|
<Col xs="12" lg="4" class="p-2">
|
||||||
<Histogram
|
{#key $nodeStatusQuery.data.jobsStatistics[0].histDuration}
|
||||||
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histDuration)}
|
<Histogram
|
||||||
title="Duration Distribution"
|
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histDuration)}
|
||||||
xlabel="Current Job Runtimes"
|
title="Duration Distribution"
|
||||||
xunit="Runtime"
|
xlabel="Current Job Runtimes"
|
||||||
ylabel="Number of Jobs"
|
xunit="Runtime"
|
||||||
yunit="Jobs"
|
ylabel="Number of Jobs"
|
||||||
height="275"
|
yunit="Jobs"
|
||||||
usesBins
|
height="275"
|
||||||
xtime
|
usesBins
|
||||||
/>
|
xtime
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="6" md="3" lg="2" class="p-2">
|
<Col xs="6" md="3" lg="2" class="p-2">
|
||||||
<div bind:clientWidth={colWidthJobs}>
|
<div bind:clientWidth={colWidthJobs}>
|
||||||
@@ -233,7 +277,7 @@
|
|||||||
<tr class="mb-2">
|
<tr class="mb-2">
|
||||||
<th></th>
|
<th></th>
|
||||||
<th style="padding-left: 0.5rem;">User</th>
|
<th style="padding-left: 0.5rem;">User</th>
|
||||||
<th>Active Jobs</th>
|
<th>Jobs</th>
|
||||||
</tr>
|
</tr>
|
||||||
{#each $topJobsQuery.data.topUser as tu, i}
|
{#each $topJobsQuery.data.topUser as tu, i}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -276,7 +320,7 @@
|
|||||||
<tr class="mb-2">
|
<tr class="mb-2">
|
||||||
<th></th>
|
<th></th>
|
||||||
<th style="padding-left: 0.5rem;">Project</th>
|
<th style="padding-left: 0.5rem;">Project</th>
|
||||||
<th>Active Jobs</th>
|
<th>Jobs</th>
|
||||||
</tr>
|
</tr>
|
||||||
{#each $topJobsQuery.data.topProjects as tp, i}
|
{#each $topJobsQuery.data.topProjects as tp, i}
|
||||||
<tr>
|
<tr>
|
||||||
|
Reference in New Issue
Block a user