complete review of context initialization and access, streamlining

This commit is contained in:
Christoph Kluge
2026-02-06 17:51:39 +01:00
parent a8d385a1ee
commit c43d4a0f16
21 changed files with 365 additions and 318 deletions

View File

@@ -68,12 +68,8 @@
energyFootprint { hardware, metric, value } energyFootprint { hardware, metric, value }
} }
`); `);
const client = getContextClient();
const ccconfig = getContext("cc-config");
const showRoofline = !!ccconfig[`jobView_showRoofline`];
const showStatsTable = !!ccconfig[`jobView_showStatTable`];
/* Note: Actual metric data queried in <Metric> Component, only require base infos here -> reduce backend load by requesting just stats */ /* Note: Actual metric data queried in <Metric> Component, only require base infos here -> reduce backend load by requesting just stats */
const client = getContextClient();
const query = gql` const query = gql`
query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!) { query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!) {
scopedJobStats(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes) { scopedJobStats(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes) {
@@ -89,12 +85,56 @@
/* State Init */ /* State Init */
let plots = $state({}); let plots = $state({});
let isMetricsSelectionOpen = $state(false); let isMetricsSelectionOpen = $state(false);
let selectedMetrics = $state([]);
let selectedScopes = $state([]);
let totalMetrics = $state(0); let totalMetrics = $state(0);
/* Derived */ /* Derived Init Return */
const showSummary = $derived((!!ccconfig[`jobView_showFootprint`] || !!ccconfig[`jobView_showPolarPlot`])) const thisJob = $derived($initq?.data ? $initq.data.job : null);
/* Derived Settings */
const globalMetrics = $derived(thisJob ? getContext("globalMetrics") : null);
const clusterInfo = $derived(thisJob ? getContext("clusters") : null);
const ccconfig = $derived(thisJob ? getContext("cc-config") : null);
const showRoofline = $derived(ccconfig ? !!ccconfig[`jobView_showRoofline`] : false);
const showStatsTable = $derived(ccconfig ? !!ccconfig[`jobView_showStatTable`] : false);
const showSummary = $derived(ccconfig ? (!!ccconfig[`jobView_showFootprint`] || !!ccconfig[`jobView_showPolarPlot`]) : false)
/* Derived Var Preprocessing*/
let selectedMetrics = $derived.by(() => {
if(thisJob && ccconfig) {
if (thisJob.cluster) {
if (thisJob.subCluster) {
return ccconfig[`metricConfig_jobViewPlotMetrics:${thisJob.cluster}:${thisJob.subCluster}`] ||
ccconfig[`metricConfig_jobViewPlotMetrics:${thisJob.cluster}`] ||
ccconfig.metricConfig_jobViewPlotMetrics
}
return ccconfig[`metricConfig_jobViewPlotMetrics:${thisJob.cluster}`] ||
ccconfig.metricConfig_jobViewPlotMetrics
}
return ccconfig.metricConfig_jobViewPlotMetrics
}
return [];
});
let selectedScopes = $derived.by(() => {
const pendingScopes = ["node"]
if (thisJob) {
const accScopeDefault = [...selectedMetrics].some(function (m) {
const thisCluster = clusterInfo.find((c) => c.name == thisJob.cluster);
const subCluster = thisCluster.subClusters.find((sc) => sc.name == thisJob.subCluster);
return subCluster.metricConfig.find((smc) => smc.name == m)?.scope === "accelerator";
});
if (accScopeDefault) pendingScopes.push("accelerator")
if (thisJob.numNodes === 1) {
pendingScopes.push("socket")
pendingScopes.push("core")
}
}
return[...new Set(pendingScopes)];
});
/* Derived Query and Postprocessing*/
const jobMetrics = $derived(queryStore({ const jobMetrics = $derived(queryStore({
client: client, client: client,
query: query, query: query,
@@ -103,11 +143,10 @@
); );
const missingMetrics = $derived.by(() => { const missingMetrics = $derived.by(() => {
if ($initq?.data && $jobMetrics?.data) { if (thisJob && $jobMetrics?.data) {
let job = $initq.data.job;
let metrics = $jobMetrics.data.scopedJobStats; let metrics = $jobMetrics.data.scopedJobStats;
let metricNames = $initq.data.globalMetrics.reduce((names, gm) => { let metricNames = globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === job.cluster)) { if (gm.availability.find((av) => av.cluster === thisJob.cluster)) {
names.push(gm.name); names.push(gm.name);
} }
return names; return names;
@@ -118,9 +157,10 @@
!metrics.some((jm) => jm.name == metric) && !metrics.some((jm) => jm.name == metric) &&
selectedMetrics.includes(metric) && selectedMetrics.includes(metric) &&
!checkMetricDisabled( !checkMetricDisabled(
globalMetrics,
metric, metric,
$initq.data.job.cluster, thisJob.cluster,
$initq.data.job.subCluster, thisJob.subCluster,
), ),
); );
} else { } else {
@@ -129,17 +169,16 @@
}); });
const missingHosts = $derived.by(() => { const missingHosts = $derived.by(() => {
if ($initq?.data && $jobMetrics?.data) { if (thisJob && $jobMetrics?.data) {
let job = $initq.data.job;
let metrics = $jobMetrics.data.scopedJobStats; let metrics = $jobMetrics.data.scopedJobStats;
let metricNames = $initq.data.globalMetrics.reduce((names, gm) => { let metricNames = globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === job.cluster)) { if (gm.availability.find((av) => av.cluster === thisJob.cluster)) {
names.push(gm.name); names.push(gm.name);
} }
return names; return names;
}, []); }, []);
return job.resources return thisJob.resources
.map(({ hostname }) => ({ .map(({ hostname }) => ({
hostname: hostname, hostname: hostname,
metrics: metricNames.filter( metrics: metricNames.filter(
@@ -165,51 +204,19 @@
? "Loading..." ? "Loading..."
: $initq?.error : $initq?.error
? "Error" ? "Error"
: `Job ${$initq.data.job.jobId} - ClusterCockpit`; : `Job ${thisJob.jobId} - ClusterCockpit`;
});
/* On Init */
getContext("on-init")(() => {
let job = $initq.data.job;
if (!job) return;
const pendingMetrics = (
ccconfig[`metricConfig_jobViewPlotMetrics:${job.cluster}:${job.subCluster}`] ||
ccconfig[`metricConfig_jobViewPlotMetrics:${job.cluster}`]
) ||
$initq.data.globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === job.cluster && av.subClusters.includes(job.subCluster))) {
names.push(gm.name);
}
return names;
}, [])
// Select default Scopes to load: Check before if any metric has accelerator scope by default
const accScopeDefault = [...pendingMetrics].some(function (m) {
const cluster = $initq.data.clusters.find((c) => c.name == job.cluster);
const subCluster = cluster.subClusters.find((sc) => sc.name == job.subCluster);
return subCluster.metricConfig.find((smc) => smc.name == m)?.scope === "accelerator";
});
const pendingScopes = ["node"]
if (accScopeDefault) pendingScopes.push("accelerator")
if (job.numNodes === 1) {
pendingScopes.push("socket")
pendingScopes.push("core")
}
selectedMetrics = [...new Set(pendingMetrics)];
selectedScopes = [...new Set(pendingScopes)];
}); });
/* Functions */ /* Functions */
const orderAndMap = (grouped, selectedMetrics) => const orderAndMap = (grouped, inputMetrics) =>
selectedMetrics.map((metric) => ({ inputMetrics.map((metric) => ({
metric: metric, metric: metric,
data: grouped.find((group) => group[0].name == metric), data: grouped.find((group) => group[0].name == metric),
disabled: checkMetricDisabled( disabled: checkMetricDisabled(
globalMetrics,
metric, metric,
$initq.data.job.cluster, thisJob.cluster,
$initq.data.job.subCluster, thisJob.subCluster,
), ),
})); }));
</script> </script>
@@ -219,34 +226,34 @@
<Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0"> <Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0">
{#if $initq.error} {#if $initq.error}
<Card body color="danger">{$initq.error.message}</Card> <Card body color="danger">{$initq.error.message}</Card>
{:else if $initq?.data} {:else if thisJob}
<Card class="overflow-auto" style="height: auto;"> <Card class="overflow-auto" style="height: auto;">
<TabContent> <!-- on:tab={(e) => (status = e.detail)} --> <TabContent> <!-- on:tab={(e) => (status = e.detail)} -->
{#if $initq.data?.job?.metaData?.message} {#if thisJob?.metaData?.message}
<TabPane tabId="admin-msg" tab="Admin Note" active> <TabPane tabId="admin-msg" tab="Admin Note" active>
<CardBody> <CardBody>
<Card body class="mb-2" color="warning"> <Card body class="mb-2" color="warning">
<h5>Job {$initq.data?.job?.jobId} ({$initq.data?.job?.cluster})</h5> <h5>Job {thisJob?.jobId} ({thisJob?.cluster})</h5>
The following note was added by administrators: The following note was added by administrators:
</Card> </Card>
<Card body> <Card body>
{@html $initq.data.job.metaData.message} {@html thisJob.metaData.message}
</Card> </Card>
</CardBody> </CardBody>
</TabPane> </TabPane>
{/if} {/if}
<TabPane tabId="meta-info" tab="Job Info" active={$initq.data?.job?.metaData?.message?false:true}> <TabPane tabId="meta-info" tab="Job Info" active={thisJob?.metaData?.message?false:true}>
<CardBody class="pb-2"> <CardBody class="pb-2">
<JobInfo job={$initq.data.job} {username} {authlevel} {roles} showTagEdit/> <JobInfo job={thisJob} {username} {authlevel} {roles} showTagEdit/>
</CardBody> </CardBody>
</TabPane> </TabPane>
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0} {#if thisJob.concurrentJobs != null && thisJob.concurrentJobs.items.length != 0}
<TabPane tabId="shared-jobs"> <TabPane tabId="shared-jobs">
<span slot="tab"> <span slot="tab">
{$initq.data.job.concurrentJobs.items.length} Concurrent Jobs {thisJob.concurrentJobs.items.length} Concurrent Jobs
</span> </span>
<CardBody> <CardBody>
<ConcurrentJobs cJobs={$initq.data.job.concurrentJobs} showLinks={(authlevel > roles.manager)}/> <ConcurrentJobs cJobs={thisJob.concurrentJobs} showLinks={(authlevel > roles.manager)}/>
</CardBody> </CardBody>
</TabPane> </TabPane>
{/if} {/if}
@@ -261,9 +268,9 @@
<Col xs={12} md={6} xl={4} xxl={3} class="mb-3 mb-xxl-0"> <Col xs={12} md={6} xl={4} xxl={3} class="mb-3 mb-xxl-0">
{#if $initq.error} {#if $initq.error}
<Card body color="danger">{$initq.error.message}</Card> <Card body color="danger">{$initq.error.message}</Card>
{:else if $initq?.data} {:else if thisJob}
{#if showSummary} {#if showSummary}
<JobSummary job={$initq.data.job}/> <JobSummary job={thisJob}/>
{/if} {/if}
{:else} {:else}
<Spinner secondary /> <Spinner secondary />
@@ -274,9 +281,9 @@
<Col xs={12} md={12} xl={5} xxl={6}> <Col xs={12} md={12} xl={5} xxl={6}>
{#if $initq.error} {#if $initq.error}
<Card body color="danger">{$initq.error.message}</Card> <Card body color="danger">{$initq.error.message}</Card>
{:else if $initq?.data} {:else if thisJob}
{#if showRoofline} {#if showRoofline}
<JobRoofline job={$initq.data.job} clusters={$initq.data.clusters}/> <JobRoofline job={thisJob} {clusterInfo}/>
{/if} {/if}
{:else} {:else}
<Spinner secondary /> <Spinner secondary />
@@ -285,10 +292,10 @@
</Row> </Row>
<!-- Row 2: Energy Information if available --> <!-- Row 2: Energy Information if available -->
{#if $initq?.data && $initq.data.job.energyFootprint.length != 0} {#if thisJob && thisJob?.energyFootprint?.length != 0}
<Row class="mb-3"> <Row class="mb-3">
<Col> <Col>
<EnergySummary jobId={$initq.data.job.jobId} jobEnergy={$initq.data.job.energy} jobEnergyFootprint={$initq.data.job.energyFootprint}/> <EnergySummary jobId={thisJob.jobId} jobEnergy={thisJob.energy} jobEnergyFootprint={thisJob.energyFootprint}/>
</Col> </Col>
</Row> </Row>
{/if} {/if}
@@ -297,7 +304,7 @@
<Card class="mb-3"> <Card class="mb-3">
<CardBody> <CardBody>
<Row class="mb-2"> <Row class="mb-2">
{#if $initq?.data} {#if thisJob}
<Col xs="auto"> <Col xs="auto">
<Button outline onclick={() => (isMetricsSelectionOpen = true)} color="primary"> <Button outline onclick={() => (isMetricsSelectionOpen = true)} color="primary">
Select Metrics (Selected {selectedMetrics.length} of {totalMetrics} available) Select Metrics (Selected {selectedMetrics.length} of {totalMetrics} available)
@@ -310,7 +317,7 @@
{#if $jobMetrics.error} {#if $jobMetrics.error}
<Row class="mt-2"> <Row class="mt-2">
<Col> <Col>
{#if $initq?.data && ($initq.data.job?.monitoringStatus == 0 || $initq.data.job?.monitoringStatus == 2)} {#if thisJob && (thisJob?.monitoringStatus == 0 || thisJob?.monitoringStatus == 2)}
<Card body color="warning">Not monitored or archiving failed</Card> <Card body color="warning">Not monitored or archiving failed</Card>
<br /> <br />
{/if} {/if}
@@ -323,18 +330,18 @@
<Spinner secondary /> <Spinner secondary />
</Col> </Col>
</Row> </Row>
{:else if $initq?.data && $jobMetrics?.data?.scopedJobStats} {:else if thisJob && $jobMetrics?.data?.scopedJobStats}
<!-- Note: Ignore '#snippet' Error in IDE --> <!-- Note: Ignore '#snippet' Error in IDE -->
{#snippet gridContent(item)} {#snippet gridContent(item)}
{#if item.data} {#if item.data}
<Metric <Metric
bind:this={plots[item.metric]} bind:this={plots[item.metric]}
job={$initq.data.job} job={thisJob}
metricName={item.metric} metricName={item.metric}
metricUnit={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.unit} metricUnit={globalMetrics.find((gm) => gm.name == item.metric)?.unit}
nativeScope={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.scope} nativeScope={globalMetrics.find((gm) => gm.name == item.metric)?.scope}
presetScopes={item.data.map((x) => x.scope)} presetScopes={item.data.map((x) => x.scope)}
isShared={$initq.data.job.shared != "none"} isShared={thisJob.shared != "none"}
/> />
{:else if item.disabled == true} {:else if item.disabled == true}
<Card color="info"> <Card color="info">
@@ -342,7 +349,7 @@
<b>Disabled Metric</b> <b>Disabled Metric</b>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<p>Metric <b>{item.metric}</b> is disabled for cluster <b>{$initq.data.job.cluster}:{$initq.data.job.subCluster}</b>.</p> <p>Metric <b>{item.metric}</b> is disabled for cluster <b>{thisJob.cluster}:{thisJob.subCluster}</b>.</p>
<p class="mb-1">To remove this card, open metric selection and press "Close and Apply".</p> <p class="mb-1">To remove this card, open metric selection and press "Close and Apply".</p>
</CardBody> </CardBody>
</Card> </Card>
@@ -353,7 +360,7 @@
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<p>No dataset(s) returned for <b>{item.metric}</b>.</p> <p>No dataset(s) returned for <b>{item.metric}</b>.</p>
<p class="mb-1">Metric was not found in metric store for cluster <b>{$initq.data.job.cluster}</b>.</p> <p class="mb-1">Metric was not found in metric store for cluster <b>{thisJob.cluster}</b>.</p>
</CardBody> </CardBody>
</Card> </Card>
{/if} {/if}
@@ -374,7 +381,7 @@
<!-- Metadata && Statistcics Table --> <!-- Metadata && Statistcics Table -->
<Row class="mb-3"> <Row class="mb-3">
<Col> <Col>
{#if $initq?.data} {#if thisJob}
<Card> <Card>
<TabContent> <TabContent>
{#if somethingMissing} {#if somethingMissing}
@@ -409,12 +416,12 @@
{/if} {/if}
{#if showStatsTable} {#if showStatsTable}
<!-- Includes <TabPane> Statistics Table with Independent GQL Query --> <!-- Includes <TabPane> Statistics Table with Independent GQL Query -->
<StatsTab job={$initq.data.job} clusters={$initq.data.clusters} tabActive={!somethingMissing}/> <StatsTab job={thisJob} {clusterInfo} {globalMetrics} {ccconfig} tabActive={!somethingMissing}/>
{/if} {/if}
<TabPane tabId="job-script" tab="Job Script"> <TabPane tabId="job-script" tab="Job Script">
<div class="pre-wrapper"> <div class="pre-wrapper">
{#if $initq.data.job.metaData?.jobScript} {#if thisJob.metaData?.jobScript}
<pre><code>{$initq.data.job.metaData?.jobScript}</code></pre> <pre><code>{thisJob.metaData?.jobScript}</code></pre>
{:else} {:else}
<Card body color="warning">No job script available</Card> <Card body color="warning">No job script available</Card>
{/if} {/if}
@@ -422,8 +429,8 @@
</TabPane> </TabPane>
<TabPane tabId="slurm-info" tab="Slurm Info"> <TabPane tabId="slurm-info" tab="Slurm Info">
<div class="pre-wrapper"> <div class="pre-wrapper">
{#if $initq.data.job.metaData?.slurmInfo} {#if thisJob.metaData?.slurmInfo}
<pre><code>{$initq.data.job.metaData?.slurmInfo}</code></pre> <pre><code>{thisJob.metaData?.slurmInfo}</code></pre>
{:else} {:else}
<Card body color="warning" <Card body color="warning"
>No additional slurm information available</Card >No additional slurm information available</Card
@@ -437,15 +444,15 @@
</Col> </Col>
</Row> </Row>
{#if $initq?.data} {#if thisJob}
<MetricSelection <MetricSelection
bind:isOpen={isMetricsSelectionOpen} bind:isOpen={isMetricsSelectionOpen}
bind:totalMetrics bind:totalMetrics
presetMetrics={selectedMetrics} presetMetrics={selectedMetrics}
cluster={$initq.data.job.cluster} cluster={thisJob.cluster}
subCluster={$initq.data.job.subCluster} subCluster={thisJob.subCluster}
configName="metricConfig_jobViewPlotMetrics" configName="metricConfig_jobViewPlotMetrics"
preInitialized {globalMetrics}
applyMetrics={(newMetrics) => applyMetrics={(newMetrics) =>
selectedMetrics = [...newMetrics] selectedMetrics = [...newMetrics]
} }

View File

@@ -36,7 +36,6 @@
/* Const Init */ /* Const Init */
const { query: initq } = init(); const { query: initq } = init();
const ccconfig = getContext("cc-config");
const matchedJobCompareLimit = 500; const matchedJobCompareLimit = 500;
/* State Init */ /* State Init */
@@ -52,26 +51,36 @@
let isMetricsSelectionOpen = $state(false); let isMetricsSelectionOpen = $state(false);
let sorting = $state({ field: "startTime", type: "col", order: "DESC" }); let sorting = $state({ field: "startTime", type: "col", order: "DESC" });
/* Derived Init Return */
const thisInit = $derived($initq?.data ? true : false);
/* Derived */ /* Derived */
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
const globalMetrics = $derived(thisInit ? getContext("globalMetrics") : null);
let presetProject = $derived(filterPresets?.project ? filterPresets.project : ""); let presetProject = $derived(filterPresets?.project ? filterPresets.project : "");
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null); let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null); let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
let metrics = $derived.by(() => { let metrics = $derived.by(() => {
if (selectedCluster) { if (thisInit && ccconfig) {
if (selectedSubCluster) { if (selectedCluster) {
return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}:${selectedSubCluster}`] || if (selectedSubCluster) {
ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] || return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}:${selectedSubCluster}`] ||
ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] ||
ccconfig.metricConfig_jobListMetrics
}
return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] ||
ccconfig.metricConfig_jobListMetrics ccconfig.metricConfig_jobListMetrics
} }
return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] || return ccconfig.metricConfig_jobListMetrics
ccconfig.metricConfig_jobListMetrics
} }
return ccconfig.metricConfig_jobListMetrics return [];
}); });
let showFootprint = $derived(selectedCluster let showFootprint = $derived((thisInit && ccconfig)
? !!ccconfig[`jobList_showFootprint:${selectedCluster}`] ? selectedCluster
: !!ccconfig.jobList_showFootprint ? ccconfig[`jobList_showFootprint:${selectedCluster}`]
: ccconfig.jobList_showFootprint
: {}
); );
/* Functions */ /* Functions */
@@ -219,6 +228,7 @@
<Sorting <Sorting
bind:isOpen={isSortingOpen} bind:isOpen={isSortingOpen}
presetSorting={sorting} presetSorting={sorting}
{globalMetrics}
applySorting={(newSort) => applySorting={(newSort) =>
sorting = {...newSort} sorting = {...newSort}
} }
@@ -232,6 +242,7 @@
subCluster={selectedSubCluster} subCluster={selectedSubCluster}
configName="metricConfig_jobListMetrics" configName="metricConfig_jobListMetrics"
footprintSelect footprintSelect
{globalMetrics}
applyMetrics={(newMetrics) => applyMetrics={(newMetrics) =>
metrics = [...newMetrics] metrics = [...newMetrics]
} }

View File

@@ -49,14 +49,10 @@
/* Const Init */ /* Const Init */
const { query: initq } = init(); const { query: initq } = init();
const initialized = getContext("initialized") const client = getContextClient();
const globalMetrics = getContext("globalMetrics")
const ccconfig = getContext("cc-config");
const clusters = getContext("clusters");
const nowEpoch = Date.now(); const nowEpoch = Date.now();
const paging = { itemsPerPage: 50, page: 1 }; const paging = { itemsPerPage: 50, page: 1 };
const sorting = { field: "startTime", type: "col", order: "DESC" }; const sorting = { field: "startTime", type: "col", order: "DESC" };
const client = getContextClient();
const nodeMetricsQuery = gql` const nodeMetricsQuery = gql`
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) { query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) { nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
@@ -112,14 +108,32 @@
let from = $state(presetFrom ? presetFrom : new Date(nowEpoch - (4 * 3600 * 1000))); let from = $state(presetFrom ? presetFrom : new Date(nowEpoch - (4 * 3600 * 1000)));
// svelte-ignore state_referenced_locally // svelte-ignore state_referenced_locally
let to = $state(presetTo ? presetTo : new Date(nowEpoch)); let to = $state(presetTo ? presetTo : new Date(nowEpoch));
let systemUnits = $state({});
/* Derived Init Return */
const thisInit = $derived($initq?.data ? true : false);
/* Derived */ /* Derived */
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
const globalMetrics = $derived(thisInit ? getContext("globalMetrics") : null);
const clusterInfos = $derived(thisInit ? getContext("clusters") : null);
const filter = $derived([ const filter = $derived([
{ cluster: { eq: cluster } }, { cluster: { eq: cluster } },
{ node: { contains: hostname } }, { node: { contains: hostname } },
{ state: ["running"] }, { state: ["running"] },
]); ]);
const systemUnits = $derived.by(() => {
const pendingUnits = {};
if (thisInit) {
const systemMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))]
for (let sm of systemMetrics) {
pendingUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
}
}
return {...pendingUnits};
});
const nodeMetricsData = $derived(queryStore({ const nodeMetricsData = $derived(queryStore({
client: client, client: client,
query: nodeMetricsQuery, query: nodeMetricsQuery,
@@ -140,20 +154,6 @@
); );
const thisNodeState = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.state ? $nodeMetricsData.data.nodeMetrics[0].state : 'notindb'); const thisNodeState = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.state ? $nodeMetricsData.data.nodeMetrics[0].state : 'notindb');
/* Effect */
$effect(() => {
loadUnits($initialized);
});
/* Functions */
function loadUnits(isInitialized) {
if (!isInitialized) return
const systemMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))]
for (let sm of systemMetrics) {
systemUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
}
}
</script> </script>
<Row cols={{ xs: 2, lg: 5 }}> <Row cols={{ xs: 2, lg: 5 }}>
@@ -246,7 +246,7 @@
<MetricPlot <MetricPlot
metric={item.name} metric={item.name}
timestep={item.metric.timestep} timestep={item.metric.timestep}
cluster={clusters.find((c) => c.name == cluster)} cluster={clusterInfos.find((c) => c.name == cluster)}
subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster} subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster}
series={item.metric.series} series={item.metric.series}
enableFlip enableFlip
@@ -277,6 +277,7 @@
.map((m) => ({ .map((m) => ({
...m, ...m,
disabled: checkMetricDisabled( disabled: checkMetricDisabled(
globalMetrics,
m.name, m.name,
cluster, cluster,
$nodeMetricsData.data.nodeMetrics[0].subCluster, $nodeMetricsData.data.nodeMetrics[0].subCluster,

View File

@@ -51,13 +51,6 @@
/* Const Init */ /* Const Init */
const { query: initq } = init(); const { query: initq } = init();
const client = getContextClient(); const client = getContextClient();
const ccconfig = getContext("cc-config");
const initialized = getContext("initialized");
const globalMetrics = getContext("globalMetrics");
const resampleConfig = getContext("resampling") || null;
const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : [];
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
const stateOptions = ['all', 'allocated', 'idle', 'reserved', 'mixed', 'down', 'unknown', 'notindb']; const stateOptions = ['all', 'allocated', 'idle', 'reserved', 'mixed', 'down', 'unknown', 'notindb'];
const nowDate = new Date(Date.now()); const nowDate = new Date(Date.now());
@@ -65,35 +58,55 @@
let timeoutId = null; let timeoutId = null;
/* State Init */ /* State Init */
let selectedResolution = $state(resampleConfig ? resampleDefault : 0);
let hostnameFilter = $state(""); let hostnameFilter = $state("");
let hoststateFilter = $state("all"); let hoststateFilter = $state("all");
let pendingHostnameFilter = $state(""); let pendingHostnameFilter = $state("");
let isMetricsSelectionOpen = $state(false); let isMetricsSelectionOpen = $state(false);
/* Derived Init Return */
const thisInit = $derived($initq?.data ? true : false);
/* Derived States */ /* Derived States */
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
const globalMetrics = $derived(thisInit ? getContext("globalMetrics") : null);
const resampleConfig = $derived(thisInit ? getContext("resampling") : null);
const resampleResolutions = $derived(resampleConfig ? [...resampleConfig.resolutions] : []);
const resampleDefault = $derived(resampleConfig ? Math.max(...resampleConfig.resolutions) : 0);
const displayNodeOverview = $derived((displayType === 'OVERVIEW'));
const systemMetrics = $derived(globalMetrics ? [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))] : []);
const systemUnits = $derived.by(() => {
const pendingUnits = {};
if (thisInit && systemMetrics.length > 0) {
for (let sm of systemMetrics) {
pendingUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
};
}
return {...pendingUnits};
});
let selectedResolution = $derived(resampleDefault);
let to = $derived(presetTo ? presetTo : new Date(Date.now())); let to = $derived(presetTo ? presetTo : new Date(Date.now()));
let from = $derived(presetFrom ? presetFrom : new Date(nowDate.setHours(nowDate.getHours() - 4))); let from = $derived(presetFrom ? presetFrom : new Date(nowDate.setHours(nowDate.getHours() - 4)));
const displayNodeOverview = $derived((displayType === 'OVERVIEW'));
const systemMetrics = $derived($initialized ? [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))] : []);
const presetSystemUnits = $derived(loadUnits(systemMetrics));
let selectedMetric = $derived.by(() => { let selectedMetric = $derived.by(() => {
let configKey = `nodeOverview_selectedMetric`; let configKey = `nodeOverview_selectedMetric`;
if (cluster) configKey += `:${cluster}`; if (cluster) configKey += `:${cluster}`;
if (subCluster) configKey += `:${subCluster}`; if (subCluster) configKey += `:${subCluster}`;
if ($initialized) { if (thisInit) {
if (ccconfig[configKey]) return ccconfig[configKey] if (ccconfig[configKey]) return ccconfig[configKey]
else if (systemMetrics.length !== 0) return systemMetrics[0].name else if (systemMetrics.length !== 0) return systemMetrics[0].name
} }
return "" return ""
}); });
let selectedMetrics = $derived.by(() => { let selectedMetrics = $derived.by(() => {
let configKey = `nodeList_selectedMetrics`; let configKey = `nodeList_selectedMetrics`;
if (cluster) configKey += `:${cluster}`; if (cluster) configKey += `:${cluster}`;
if (subCluster) configKey += `:${subCluster}`; if (subCluster) configKey += `:${subCluster}`;
if ($initialized) { if (thisInit) {
if (ccconfig[configKey]) return ccconfig[configKey] if (ccconfig[configKey]) return ccconfig[configKey]
else if (systemMetrics.length >= 3) return [systemMetrics[0].name, systemMetrics[1].name, systemMetrics[2].name] else if (systemMetrics.length >= 3) return [systemMetrics[0].name, systemMetrics[1].name, systemMetrics[2].name]
} }
@@ -108,16 +121,6 @@
}); });
/* Functions */ /* Functions */
function loadUnits(systemMetrics) {
let pendingUnits = {};
if (systemMetrics.length > 0) {
for (let sm of systemMetrics) {
pendingUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
};
};
return {...pendingUnits};
};
// Wait after input for some time to prevent too many requests // Wait after input for some time to prevent too many requests
function updateHostnameFilter() { function updateHostnameFilter() {
if (timeoutId != null) clearTimeout(timeoutId); if (timeoutId != null) clearTimeout(timeoutId);
@@ -157,7 +160,7 @@
<!-- ROW1: Tools--> <!-- ROW1: Tools-->
<Row cols={{ xs: 2, lg: !displayNodeOverview ? (resampleConfig ? 6 : 5) : 5 }} class="mb-3"> <Row cols={{ xs: 2, lg: !displayNodeOverview ? (resampleConfig ? 6 : 5) : 5 }} class="mb-3">
{#if $initq?.data} {#if thisInit}
<!-- List Metric Select Col--> <!-- List Metric Select Col-->
{#if !displayNodeOverview} {#if !displayNodeOverview}
<Col> <Col>
@@ -234,7 +237,7 @@
<Input type="select" bind:value={selectedMetric}> <Input type="select" bind:value={selectedMetric}>
{#each systemMetrics as metric (metric.name)} {#each systemMetrics as metric (metric.name)}
<option value={metric.name} <option value={metric.name}
>{metric.name} {presetSystemUnits[metric.name] ? "("+presetSystemUnits[metric.name]+")" : ""}</option >{metric.name} {systemUnits[metric.name] ? "("+systemUnits[metric.name]+")" : ""}</option
> >
{:else} {:else}
<option disabled>No available options</option> <option disabled>No available options</option>
@@ -266,10 +269,11 @@
{:else} {:else}
{#if displayNodeOverview} {#if displayNodeOverview}
<!-- ROW2-1: Node Overview (Grid Included)--> <!-- ROW2-1: Node Overview (Grid Included)-->
<NodeOverview {cluster} {ccconfig} {selectedMetric} {from} {to} {hostnameFilter} {hoststateFilter}/> <NodeOverview {cluster} {ccconfig} {selectedMetric} {globalMetrics} {from} {to} {hostnameFilter} {hoststateFilter}/>
{:else} {:else}
<!-- ROW2-2: Node List (Grid Included)--> <!-- ROW2-2: Node List (Grid Included)-->
<NodeList {cluster} {subCluster} {ccconfig} pendingSelectedMetrics={selectedMetrics} {selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {presetSystemUnits}/> <NodeList {cluster} {subCluster} {ccconfig} {globalMetrics}
pendingSelectedMetrics={selectedMetrics} {selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {systemUnits}/>
{/if} {/if}
{/if} {/if}
@@ -279,6 +283,7 @@
presetMetrics={selectedMetrics} presetMetrics={selectedMetrics}
{cluster} {cluster}
{subCluster} {subCluster}
{globalMetrics}
configName="nodeList_selectedMetrics" configName="nodeList_selectedMetrics"
applyMetrics={(newMetrics) => applyMetrics={(newMetrics) =>
selectedMetrics = [...newMetrics] selectedMetrics = [...newMetrics]

View File

@@ -56,12 +56,10 @@
/* Const Init */ /* Const Init */
const { query: initq } = init(); const { query: initq } = init();
const ccconfig = getContext("cc-config");
const client = getContextClient(); const client = getContextClient();
const durationBinOptions = ["1m","10m","1h","6h","12h"]; const durationBinOptions = ["1m","10m","1h","6h","12h"];
const metricBinOptions = [10, 20, 50, 100]; const metricBinOptions = [10, 20, 50, 100];
const matchedJobCompareLimit = 500; const matchedJobCompareLimit = 500;
const shortDuration = ccconfig.jobList_hideShortRunningJobs; // Always configured
/* State Init */ /* State Init */
// List & Control Vars // List & Control Vars
@@ -73,7 +71,6 @@
let isSortingOpen = $state(false); let isSortingOpen = $state(false);
let isMetricsSelectionOpen = $state(false); let isMetricsSelectionOpen = $state(false);
let sorting = $state({ field: "startTime", type: "col", order: "DESC" }); let sorting = $state({ field: "startTime", type: "col", order: "DESC" });
let selectedHistogramsBuffer = $state({ all: (ccconfig['userView_histogramMetrics'] || []) })
let jobCompare = $state(null); let jobCompare = $state(null);
let matchedCompareJobs = $state(0); let matchedCompareJobs = $state(0);
let showCompare = $state(false); let showCompare = $state(false);
@@ -84,26 +81,48 @@
let numDurationBins = $state("1h"); let numDurationBins = $state("1h");
let numMetricBins = $state(10); let numMetricBins = $state(10);
/* Derived Init Return */
const thisInit = $derived($initq?.data ? true : false);
/* Derived */ /* Derived */
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
const globalMetrics = $derived(thisInit ? getContext("globalMetrics") : null);
const shortDuration = $derived(ccconfig?.jobList_hideShortRunningJobs);
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null); let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null); let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
let metrics = $derived.by(() => { let metrics = $derived.by(() => {
if (selectedCluster) { if (thisInit && ccconfig) {
if (selectedSubCluster) { if (selectedCluster) {
return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}:${selectedSubCluster}`] || if (selectedSubCluster) {
ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] || return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}:${selectedSubCluster}`] ||
ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] ||
ccconfig.metricConfig_jobListMetrics
}
return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] ||
ccconfig.metricConfig_jobListMetrics ccconfig.metricConfig_jobListMetrics
} }
return ccconfig[`metricConfig_jobListMetrics:${selectedCluster}`] || return ccconfig.metricConfig_jobListMetrics
ccconfig.metricConfig_jobListMetrics
} }
return ccconfig.metricConfig_jobListMetrics return [];
}); });
let showFootprint = $derived(filterPresets.cluster
? !!ccconfig[`jobList_showFootprint:${filterPresets.cluster}`] let showFootprint = $derived((thisInit && ccconfig)
: !!ccconfig.jobList_showFootprint ? filterPresets?.cluster
? ccconfig[`jobList_showFootprint:${filterPresets.cluster}`]
: ccconfig.jobList_showFootprint
: {}
); );
let selectedHistograms = $derived(selectedCluster ? selectedHistogramsBuffer[selectedCluster] : selectedHistogramsBuffer['all']);
let selectedHistograms = $derived.by(() => {
if (thisInit && ccconfig) {
if (selectedCluster) {
return ccconfig[`userView_histogramMetrics:${selectedCluster}`] // No Fallback; Unspecific lists an include unavailable metrics
}
return ccconfig.userView_histogramMetrics
}
return []
});
let stats = $derived( let stats = $derived(
queryStore({ queryStore({
client: client, client: client,
@@ -159,19 +178,9 @@
}); });
}); });
$effect(() => {
if (!selectedHistogramsBuffer[selectedCluster]) {
selectedHistogramsBuffer[selectedCluster] = ccconfig[`userView_histogramMetrics:${selectedCluster}`];
};
});
/* On Mount */ /* On Mount */
onMount(() => { onMount(() => {
filterComponent.updateFilters(); filterComponent.updateFilters();
// Why? -> `$derived(ccconfig[$cluster])` only loads array from last Backend-Query if $cluster changed reactively (without reload)
if (filterPresets?.cluster) {
selectedHistogramsBuffer[filterPresets.cluster] = ccconfig[`userView_histogramMetrics:${filterPresets.cluster}`];
};
}); });
</script> </script>
@@ -508,6 +517,7 @@
<Sorting <Sorting
bind:isOpen={isSortingOpen} bind:isOpen={isSortingOpen}
presetSorting={sorting} presetSorting={sorting}
{globalMetrics}
applySorting={(newSort) => applySorting={(newSort) =>
sorting = {...newSort} sorting = {...newSort}
} }
@@ -521,6 +531,7 @@
subCluster={selectedSubCluster} subCluster={selectedSubCluster}
configName="metricConfig_jobListMetrics" configName="metricConfig_jobListMetrics"
footprintSelect footprintSelect
{globalMetrics}
applyMetrics={(newMetrics) => applyMetrics={(newMetrics) =>
metrics = [...newMetrics] metrics = [...newMetrics]
} }
@@ -531,7 +542,8 @@
bind:isOpen={isHistogramSelectionOpen} bind:isOpen={isHistogramSelectionOpen}
presetSelectedHistograms={selectedHistograms} presetSelectedHistograms={selectedHistograms}
configName="userView_histogramMetrics" configName="userView_histogramMetrics"
{globalMetrics}
applyChange={(newSelection) => { applyChange={(newSelection) => {
selectedHistogramsBuffer[selectedCluster || 'all'] = [...newSelection]; selectedHistograms = [...newSelection];
}} }}
/> />

View File

@@ -39,10 +39,6 @@
} = $props(); } = $props();
/* Const Init */ /* Const Init */
const ccconfig = getContext("cc-config");
const initialized = getContext("initialized");
const globalMetrics = getContext("globalMetrics");
const usePaging = ccconfig?.jobList_usePaging || false;
const jobInfoColumnWidth = 250; const jobInfoColumnWidth = 250;
const client = getContextClient(); const client = getContextClient();
const query = gql` const query = gql`
@@ -100,11 +96,18 @@
let headerPaddingTop = $state(0); let headerPaddingTop = $state(0);
let jobs = $state([]); let jobs = $state([]);
let page = $state(1); let page = $state(1);
let itemsPerPage = $state(usePaging ? (ccconfig?.jobList_jobsPerPage || 10) : 10);
let triggerMetricRefresh = $state(false); let triggerMetricRefresh = $state(false);
let tableWidth = $state(0); let tableWidth = $state(0);
/* Derived */ /* Derived */
const initialized = $derived(getContext("initialized") || false);
const ccconfig = $derived(initialized ? getContext("cc-config") : null);
const globalMetrics = $derived(initialized ? getContext("globalMetrics") : null);
const clusterInfos = $derived(initialized ? getContext("clusters"): null);
const resampleConfig = $derived(initialized ? getContext("resampling") : null);
const usePaging = $derived(ccconfig?.jobList_usePaging || false);
let itemsPerPage = $derived(usePaging ? (ccconfig?.jobList_jobsPerPage || 10) : 10);
let filter = $derived([...filterBuffer]); let filter = $derived([...filterBuffer]);
let paging = $derived({ itemsPerPage, page }); let paging = $derived({ itemsPerPage, page });
const plotWidth = $derived.by(() => { const plotWidth = $derived.by(() => {
@@ -274,7 +277,7 @@
style="width: {plotWidth}px; padding-top: {headerPaddingTop}px" style="width: {plotWidth}px; padding-top: {headerPaddingTop}px"
> >
{metric} {metric}
{#if $initialized} {#if initialized}
({getUnit(metric)}) ({getUnit(metric)})
{/if} {/if}
</th> </th>
@@ -292,7 +295,8 @@
</tr> </tr>
{:else} {:else}
{#each jobs as job (job.id)} {#each jobs as job (job.id)}
<JobListRow {triggerMetricRefresh} {job} {metrics} {plotWidth} {showFootprint} previousSelect={selectedJobs.includes(job.id)} <JobListRow {triggerMetricRefresh} {job} {metrics} {plotWidth} {showFootprint} {globalMetrics} {clusterInfos} {resampleConfig}
previousSelect={selectedJobs.includes(job.id)}
selectJob={(detail) => selectedJobs = [...selectedJobs, detail]} selectJob={(detail) => selectedJobs = [...selectedJobs, detail]}
unselectJob={(detail) => selectedJobs = selectedJobs.filter(item => item !== detail)} unselectJob={(detail) => selectedJobs = selectedJobs.filter(item => item !== detail)}
/> />

View File

@@ -30,11 +30,10 @@
setFilter setFilter
} = $props(); } = $props();
/* Const Init */
const clusters = getContext("clusters");
const initialized = getContext("initialized");
/* Derived */ /* Derived */
const initialized = $derived(getContext("initialized") || false);
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
let pendingCluster = $derived(presetCluster); let pendingCluster = $derived(presetCluster);
let pendingPartition = $derived(presetPartition); let pendingPartition = $derived(presetPartition);
</script> </script>
@@ -56,7 +55,7 @@
> >
Any Cluster Any Cluster
</ListGroupItem> </ListGroupItem>
{#each clusters as cluster} {#each clusterInfos as cluster}
<ListGroupItem <ListGroupItem
disabled={disableClusterSelection} disabled={disableClusterSelection}
active={pendingCluster == cluster.name} active={pendingCluster == cluster.name}
@@ -80,7 +79,7 @@
> >
Any Partition Any Partition
</ListGroupItem> </ListGroupItem>
{#each clusters?.find((c) => c.name == pendingCluster)?.partitions as partition} {#each clusterInfos?.find((c) => c.name == pendingCluster)?.partitions as partition}
<ListGroupItem <ListGroupItem
active={pendingPartition == partition} active={pendingPartition == partition}
onclick={() => (pendingPartition = partition)} onclick={() => (pendingPartition = partition)}

View File

@@ -42,8 +42,8 @@
contains: "Contains", contains: "Contains",
} }
const findMaxNumAccels = (clusters) => const findMaxNumAccels = (infos) =>
clusters.reduce( infos.reduce(
(max, cluster) => (max, cluster) =>
Math.max( Math.max(
max, max,
@@ -56,8 +56,8 @@
); );
// Limited to Single-Node Thread Count // Limited to Single-Node Thread Count
const findMaxNumHWThreadsPerNode = (clusters) => const findMaxNumHWThreadsPerNode = (infos) =>
clusters.reduce( infos.reduce(
(max, cluster) => (max, cluster) =>
Math.max( Math.max(
max, max,
@@ -92,8 +92,8 @@
let threadState = $derived(presetNumHWThreads); let threadState = $derived(presetNumHWThreads);
let accState = $derived(presetNumAccelerators); let accState = $derived(presetNumAccelerators);
const clusters = $derived(getContext("clusters")); const initialized = $derived(getContext("initialized") || false);
const initialized = $derived(getContext("initialized")); const clusterInfos = $derived($initialized ? getContext("clusters") : null);
// Is Selection Active // Is Selection Active
const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 1, to: maxNumNodes }))); const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 1, to: maxNumNodes })));
const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 1, to: maxNumHWThreads }))); const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 1, to: maxNumHWThreads })));
@@ -109,12 +109,12 @@
$effect(() => { $effect(() => {
if ($initialized) { if ($initialized) {
if (activeCluster != null) { if (activeCluster != null) {
const { subClusters } = clusters.find((c) => c.name == activeCluster); const { subClusters } = clusterInfos.find((c) => c.name == activeCluster);
maxNumAccelerators = findMaxNumAccels([{ subClusters }]); maxNumAccelerators = findMaxNumAccels([{ subClusters }]);
maxNumHWThreads = findMaxNumHWThreadsPerNode([{ subClusters }]); maxNumHWThreads = findMaxNumHWThreadsPerNode([{ subClusters }]);
} else if (clusters.length > 0) { } else if (clusterInfos.length > 0) {
maxNumAccelerators = findMaxNumAccels(clusters); maxNumAccelerators = findMaxNumAccels(clusterInfos);
maxNumHWThreads = findMaxNumHWThreadsPerNode(clusters); maxNumHWThreads = findMaxNumHWThreadsPerNode(clusterInfos);
} }
} }
}); });

View File

@@ -31,8 +31,8 @@
} = $props(); } = $props();
/* Derived */ /* Derived */
const allTags = $derived(getContext("tags")) const initialized = $derived(getContext("initialized") || false)
const initialized = $derived(getContext("initialized")) const allTags = $derived($initialized ? getContext("tags") : [])
/* State Init */ /* State Init */
let searchTerm = $state(""); let searchTerm = $state("");

View File

@@ -18,8 +18,8 @@
} = $props(); } = $props();
/* Derived */ /* Derived */
const allTags = $derived(getContext('tags')); const initialized = $derived(getContext('initialized') || false);
const initialized = $derived(getContext('initialized')); const allTags = $derived($initialized ? getContext('tags') : []);
/* Effects */ /* Effects */
$effect(() => { $effect(() => {

View File

@@ -48,8 +48,6 @@
const client = getContextClient(); const client = getContextClient();
/* State Init */ /* State Init */
let initialized = getContext("initialized")
let allTags = getContext("tags")
let newTagType = $state(""); let newTagType = $state("");
let newTagName = $state(""); let newTagName = $state("");
let filterTerm = $state(""); let filterTerm = $state("");
@@ -57,10 +55,13 @@
let isOpen = $state(false); let isOpen = $state(false);
/* Derived */ /* Derived */
const initialized = $derived(getContext("initialized") || false );
let allTags = $derived(initialized ? getContext("tags") : [])
let newTagScope = $derived(username); let newTagScope = $derived(username);
const isAdmin = $derived((roles && authlevel == roles.admin)); const isAdmin = $derived((roles && authlevel == roles.admin));
const isSupport = $derived((roles && authlevel == roles.support)); const isSupport = $derived((roles && authlevel == roles.support));
const allTagsFiltered = $derived(($initialized, jobTags, fuzzySearchTags(filterTerm, allTags))); // $init und JobTags only for triggering react const allTagsFiltered = $derived((initialized, jobTags, fuzzySearchTags(filterTerm, allTags))); // $init und JobTags only for triggering react
const usedTagsFiltered = $derived(matchJobTags(jobTags, allTagsFiltered, 'used', isAdmin, isSupport)); const usedTagsFiltered = $derived(matchJobTags(jobTags, allTagsFiltered, 'used', isAdmin, isSupport));
const unusedTagsFiltered = $derived(matchJobTags(jobTags, allTagsFiltered, 'unused', isAdmin, isSupport)); const unusedTagsFiltered = $derived(matchJobTags(jobTags, allTagsFiltered, 'unused', isAdmin, isSupport));

View File

@@ -11,11 +11,13 @@
- `triggerMetricRefresh Bool?`: If changed to true from upstream, will trigger metric query [Default: false] - `triggerMetricRefresh Bool?`: If changed to true from upstream, will trigger metric query [Default: false]
- `selectJob Func`: The callback function to select a job for comparison - `selectJob Func`: The callback function to select a job for comparison
- `unselectJob Func`: The callback function to unselect a job from comparison - `unselectJob Func`: The callback function to unselect a job from comparison
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `clusterInfos [Obj]`: Includes the backend supplied cluster topology
- `resampleConfig [Obj]`: Includes the backend supplied resampling info
--> -->
<script> <script>
import { queryStore, gql, getContextClient } from "@urql/svelte"; import { queryStore, gql, getContextClient } from "@urql/svelte";
import { getContext } from "svelte";
import { Card, Spinner } from "@sveltestrap/sveltestrap"; import { Card, Spinner } from "@sveltestrap/sveltestrap";
import { maxScope, checkMetricDisabled } from "../utils.js"; import { maxScope, checkMetricDisabled } from "../utils.js";
import JobInfo from "./JobInfo.svelte"; import JobInfo from "./JobInfo.svelte";
@@ -33,13 +35,13 @@
triggerMetricRefresh = false, triggerMetricRefresh = false,
selectJob, selectJob,
unselectJob, unselectJob,
globalMetrics,
clusterInfos,
resampleConfig
} = $props(); } = $props();
/* Const Init */ /* Const Init */
const client = getContextClient(); const client = getContextClient();
const cluster = getContext("clusters");
const resampleConfig = getContext("resampling") || null;
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
const query = gql` const query = gql`
query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!, $selectedResolution: Int) { query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!, $selectedResolution: Int) {
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes, resolution: $selectedResolution) { jobMetrics(id: $id, metrics: $metrics, scopes: $scopes, resolution: $selectedResolution) {
@@ -73,11 +75,11 @@
`; `;
/* State Init */ /* State Init */
let selectedResolution = $state(resampleDefault);
let zoomStates = $state({}); let zoomStates = $state({});
let thresholdStates = $state({}); let thresholdStates = $state({});
/* Derived */ /* Derived */
const resampleDefault = $derived(resampleConfig ? Math.max(...resampleConfig.resolutions) : 0);
const jobId = $derived(job?.id); const jobId = $derived(job?.id);
const scopes = $derived.by(() => { const scopes = $derived.by(() => {
if (job.numNodes == 1) { if (job.numNodes == 1) {
@@ -87,6 +89,8 @@
return ["node"]; return ["node"];
}; };
}); });
let selectedResolution = $derived(resampleDefault);
let isSelected = $derived(previousSelect); let isSelected = $derived(previousSelect);
let metricsQuery = $derived(queryStore({ let metricsQuery = $derived(queryStore({
client: client, client: client,
@@ -94,6 +98,7 @@
variables: { id: jobId, metrics, scopes, selectedResolution }, variables: { id: jobId, metrics, scopes, selectedResolution },
}) })
); );
const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope($metricsQuery.data.jobMetrics) : []); const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope($metricsQuery.data.jobMetrics) : []);
/* Effects */ /* Effects */
@@ -160,6 +165,7 @@
return { return {
name: jobMetric.data.name, name: jobMetric.data.name,
disabled: checkMetricDisabled( disabled: checkMetricDisabled(
globalMetrics,
jobMetric.data.name, jobMetric.data.name,
job.cluster, job.cluster,
job.subCluster, job.subCluster,
@@ -220,7 +226,7 @@
series={metric.data.metric.series} series={metric.data.metric.series}
statisticsSeries={metric.data.metric.statisticsSeries} statisticsSeries={metric.data.metric.statisticsSeries}
metric={metric.data.name} metric={metric.data.name}
cluster={cluster.find((c) => c.name == job.cluster)} cluster={clusterInfos.find((c) => c.name == job.cluster)}
subCluster={job.subCluster} subCluster={job.subCluster}
isShared={job.shared != "none"} isShared={job.shared != "none"}
numhwthreads={job.numHWThreads} numhwthreads={job.numHWThreads}

View File

@@ -6,6 +6,7 @@
- `ì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 - `configName String`: The config id string to be updated in database on selection change
- `presetSelectedHistograms [String]`: The currently selected metrics to display as histogram - `presetSelectedHistograms [String]`: The currently selected metrics to display as histogram
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `applyChange Func`: The callback function to apply current selection - `applyChange Func`: The callback function to apply current selection
--> -->
@@ -24,10 +25,11 @@
/* Svelte 5 Props */ /* Svelte 5 Props */
let { let {
cluster, cluster = "",
isOpen = $bindable(), isOpen = $bindable(),
configName, configName,
presetSelectedHistograms, presetSelectedHistograms,
globalMetrics,
applyChange applyChange
} = $props(); } = $props();
@@ -42,11 +44,11 @@
function loadHistoMetrics(thisCluster) { function loadHistoMetrics(thisCluster) {
// isInit Check Removed: Parent Component has finished Init-Query: Globalmetrics available here. // isInit Check Removed: Parent Component has finished Init-Query: Globalmetrics available here.
if (!thisCluster) { if (!thisCluster) {
return getContext("globalMetrics") return globalMetrics
.filter((gm) => gm?.footprint) .filter((gm) => gm?.footprint)
.map((fgm) => { return fgm.name }) .map((fgm) => { return fgm.name })
} else { } else {
return getContext("globalMetrics") return globalMetrics
.filter((gm) => gm?.availability.find((av) => av.cluster == thisCluster)) .filter((gm) => gm?.availability.find((av) => av.cluster == thisCluster))
.filter((agm) => agm?.footprint) .filter((agm) => agm?.footprint)
.map((afgm) => { return afgm.name }) .map((afgm) => { return afgm.name })

View File

@@ -9,13 +9,12 @@
- `cluster String?`: The currently selected cluster [Default: null] - `cluster String?`: The currently selected cluster [Default: null]
- `subCluster String?`: The currently selected subCluster [Default: null] - `subCluster String?`: The currently selected subCluster [Default: null]
- `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false] - `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false]
- `preInitialized Bool?`: If the parent component has a dedicated call to init() [Default: false]
- `configName String`: The config key for the last saved selection (constant) - `configName String`: The config key for the last saved selection (constant)
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `applyMetrics Func`: The callback function to apply current selection - `applyMetrics Func`: The callback function to apply current selection
--> -->
<script> <script>
import { getContext } from "svelte";
import { import {
Modal, Modal,
ModalBody, ModalBody,
@@ -35,14 +34,12 @@
cluster = null, cluster = null,
subCluster = null, subCluster = null,
footprintSelect = false, footprintSelect = false,
preInitialized = false, // Job View is Pre-Init'd: $initialized "alone" store returns false
configName, configName,
globalMetrics,
applyMetrics applyMetrics
} = $props(); } = $props();
/* Const Init */ /* Const Init */
const globalMetrics = getContext("globalMetrics");
const initialized = getContext("initialized");
const client = getContextClient(); const client = getContextClient();
const updateConfigurationMutation = ({ name, value }) => { const updateConfigurationMutation = ({ name, value }) => {
return mutationStore({ return mutationStore({
@@ -58,27 +55,23 @@
/* State Init */ /* State Init */
let pendingShowFootprint = $state(!!showFootprint); let pendingShowFootprint = $state(!!showFootprint);
let listedMetrics = $state([]);
let columnHovering = $state(null); let columnHovering = $state(null);
/* Derives States */ /* Derives States */
let pendingMetrics = $derived(presetMetrics); const allMetrics = $derived(loadAvailable(globalMetrics));
const allMetrics = $derived(loadAvailable(preInitialized || $initialized)); let pendingMetrics = $derived(presetMetrics || []);
let listedMetrics = $derived([...presetMetrics, ...allMetrics.difference(new Set(presetMetrics))]); // List (preset) active metrics first, then list inactives
/* Reactive Effects */ /* Reactive Effects */
$effect(() => { $effect(() => {
totalMetrics = allMetrics?.size || 0; totalMetrics = allMetrics?.size || 0;
}); });
$effect(() => {
listedMetrics = [...presetMetrics, ...allMetrics.difference(new Set(presetMetrics))]; // List (preset) active metrics first, then list inactives
});
/* Functions */ /* Functions */
function loadAvailable(init) { function loadAvailable(gms) {
const availableMetrics = new Set(); const availableMetrics = new Set();
if (init) { if (gms) {
for (let gm of globalMetrics) { for (let gm of gms) {
if (!cluster) { if (!cluster) {
availableMetrics.add(gm.name) availableMetrics.add(gm.name)
} else { } else {
@@ -90,7 +83,7 @@
} }
} }
} }
return availableMetrics return availableMetrics;
} }
function printAvailability(metric, cluster) { function printAvailability(metric, cluster) {

View File

@@ -5,11 +5,11 @@
- `presetSorting Object?`: The latest sort selection state - `presetSorting Object?`: The latest sort selection state
- Default { field: "startTime", type: "col", order: "DESC" } - Default { field: "startTime", type: "col", order: "DESC" }
- `isOpen Bool?`: Is modal opened [Bindable, Default: false] - `isOpen Bool?`: Is modal opened [Bindable, Default: false]
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `applySorting Func`: The callback function to apply current selection - `applySorting Func`: The callback function to apply current selection
--> -->
<script> <script>
import { getContext, onMount } from "svelte";
import { import {
Icon, Icon,
Button, Button,
@@ -25,12 +25,11 @@
let { let {
isOpen = $bindable(false), isOpen = $bindable(false),
presetSorting = { field: "startTime", type: "col", order: "DESC" }, presetSorting = { field: "startTime", type: "col", order: "DESC" },
globalMetrics,
applySorting applySorting
} = $props(); } = $props();
/* Const Init */ /* Const Init */
const initialized = getContext("initialized");
const globalMetrics = getContext("globalMetrics");
const fixedSortables = $state([ const fixedSortables = $state([
{ field: "startTime", type: "col", text: "Start Time (Default)", order: "DESC" }, { field: "startTime", type: "col", text: "Start Time (Default)", order: "DESC" },
{ field: "duration", type: "col", text: "Duration", order: "DESC" }, { field: "duration", type: "col", text: "Duration", order: "DESC" },
@@ -42,22 +41,11 @@
/* State Init */ /* State Init */
let activeColumnIdx = $state(0); let activeColumnIdx = $state(0);
let metricSortables = $state([]);
/* Derived */ /* Derived */
let sorting = $derived({...presetSorting}) let sorting = $derived({...presetSorting})
let sortableColumns = $derived([...fixedSortables, ...metricSortables]); let metricSortables = $derived.by(() => {
return globalMetrics.map((gm) => {
/* Effect */
$effect(() => {
if ($initialized) {
loadMetricSortables();
};
});
/* Functions */
function loadMetricSortables() {
metricSortables = globalMetrics.map((gm) => {
if (gm?.footprint) { if (gm?.footprint) {
return { return {
field: gm.name + '_' + gm.footprint, field: gm.name + '_' + gm.footprint,
@@ -68,8 +56,10 @@
} }
return null return null
}).filter((r) => r != null) }).filter((r) => r != null)
}; });
let sortableColumns = $derived([...fixedSortables, ...metricSortables]);
/* Functions */
function loadActiveIndex() { function loadActiveIndex() {
activeColumnIdx = sortableColumns.findIndex( activeColumnIdx = sortableColumns.findIndex(
(col) => col.field == sorting.field, (col) => col.field == sorting.field,

View File

@@ -302,19 +302,17 @@ export function stickyHeader(datatableHeaderSelector, updatePading) {
onDestroy(() => document.removeEventListener("scroll", onscroll)); onDestroy(() => document.removeEventListener("scroll", onscroll));
} }
export function checkMetricDisabled(m, c, s) { // [m]etric, [c]luster, [s]ubcluster export function checkMetricDisabled(gm, m, c, s) { // [g]lobal[m]etrics, [m]etric, [c]luster, [s]ubcluster
const metrics = getContext("globalMetrics"); const available = gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)
const available = metrics?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)
// Return inverse logic // Return inverse logic
return !available return !available
} }
export function checkMetricsDisabled(ma, c, s) { // [m]etric[a]rray, [c]luster, [s]ubcluster export function checkMetricsDisabled(gm, ma, c, s) { // [g]lobal[m]etrics, [m]etric[a]rray, [c]luster, [s]ubcluster
let result = {}; let result = {};
const metrics = getContext("globalMetrics");
ma.forEach((m) => { ma.forEach((m) => {
// Return named inverse logic: !available // Return named inverse logic: !available
result[m] = !(metrics?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)) result[m] = !(gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s))
}); });
return result return result
} }

View File

@@ -3,7 +3,7 @@
Properties: Properties:
- `job Object`: The GQL job object - `job Object`: The GQL job object
- `clusters Array`: The GQL clusters array - `clusterInfo Array`: The GQL clusters array
--> -->
<script> <script>
@@ -24,7 +24,7 @@
/* Svelte 5 Props */ /* Svelte 5 Props */
let { let {
job, job,
clusters, clusterInfo,
} = $props(); } = $props();
/* Const Init */ /* Const Init */
@@ -62,7 +62,7 @@
<div bind:clientWidth={roofWidth}> <div bind:clientWidth={roofWidth}>
<Roofline <Roofline
width={roofWidth} width={roofWidth}
subCluster={clusters subCluster={clusterInfo
.find((c) => c.name == job.cluster) .find((c) => c.name == job.cluster)
.subClusters.find((sc) => sc.name == job.subCluster)} .subClusters.find((sc) => sc.name == job.subCluster)}
data={transformDataForRoofline( data={transformDataForRoofline(

View File

@@ -3,8 +3,10 @@
Properties: Properties:
- `job Object`: The job object - `job Object`: The job object
- `clusters Object`: The clusters object - `clusterInfo Object`: The clusters object
- `tabActive bool`: Boolean if StatsTabe Tab is Active on Creation - `tabActive bool`: Boolean if StatsTabe Tab is Active on Creation
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `ccconfig Object?`: The ClusterCockpit Config Context
--> -->
<script> <script>
@@ -13,7 +15,6 @@
gql, gql,
getContextClient getContextClient
} from "@urql/svelte"; } from "@urql/svelte";
import { getContext } from "svelte";
import { import {
Card, Card,
Button, Button,
@@ -29,8 +30,10 @@
/* Svelte 5 Props */ /* Svelte 5 Props */
let { let {
job, job,
clusters, clusterInfo,
tabActive, tabActive,
globalMetrics,
ccconfig
} = $props(); } = $props();
/* Const Init */ /* Const Init */
@@ -55,65 +58,73 @@
/* State Init */ /* State Init */
let moreScopes = $state(false); let moreScopes = $state(false);
let selectedScopes = $state([]);
let selectedMetrics = $state([]);
let totalMetrics = $state(0); // For Info Only, filled by MetricSelection Component let totalMetrics = $state(0); // For Info Only, filled by MetricSelection Component
let isMetricSelectionOpen = $state(false); let isMetricSelectionOpen = $state(false);
/* Derived */ /* Derived Var Preprocessing*/
let selectedTableMetrics = $derived.by(() => {
if(job && ccconfig) {
if (job.cluster) {
if (job.subCluster) {
return ccconfig[`metricConfig_jobViewTableMetrics:${job.cluster}:${job.subCluster}`] ||
ccconfig[`metricConfig_jobViewTableMetrics:${job.cluster}`] ||
ccconfig.metricConfig_jobViewTableMetrics
}
return ccconfig[`metricConfig_jobViewTableMetrics:${job.cluster}`] ||
ccconfig.metricConfig_jobViewTableMetrics
}
return ccconfig.metricConfig_jobViewTableMetrics
}
return [];
});
let selectedTableScopes = $derived.by(() => {
if (job) {
if (!moreScopes) {
// Select default Scopes to load: Check before if any metric has accelerator scope by default
const pendingScopes = ["node"]
const accScopeDefault = [...selectedTableMetrics].some(function (m) {
const cluster = clusterInfo.find((c) => c.name == job.cluster);
const subCluster = cluster.subClusters.find((sc) => sc.name == job.subCluster);
return subCluster.metricConfig.find((smc) => smc.name == m)?.scope === "accelerator";
});
if (job.numNodes === 1) {
pendingScopes.push("socket")
pendingScopes.push("core")
pendingScopes.push("hwthread")
if (accScopeDefault) { pendingScopes.push("accelerator") }
}
return[...new Set(pendingScopes)];
} else {
// If flag set: Always load all scopes
return ["node", "socket", "core", "hwthread", "accelerator"];
}
} // Fallback
return ["node"]
});
/* Derived Query */
const scopedStats = $derived(queryStore({ const scopedStats = $derived(queryStore({
client: client, client: client,
query: query, query: query,
variables: { dbid: job.id, selectedMetrics, selectedScopes }, variables: {
dbid: job.id,
selectedMetrics: selectedTableMetrics,
selectedScopes: selectedTableScopes
},
}) })
); );
/* Functions */
function loadScopes() {
// Archived Jobs Load All Scopes By Default (See Backend)
moreScopes = true;
selectedScopes = ["node", "socket", "core", "hwthread", "accelerator"];
};
/* On Init */
// Handle Job Query on Init -> is not executed anymore
getContext("on-init")(() => {
if (!job) return;
const pendingMetrics = (
getContext("cc-config")[`metricConfig_jobViewTableMetrics:${job.cluster}:${job.subCluster}`] ||
getContext("cc-config")[`metricConfig_jobViewTableMetrics:${job.cluster}`]
) || getContext("cc-config")["metricConfig_jobViewTableMetrics"];
// Select default Scopes to load: Check before if any metric has accelerator scope by default
const accScopeDefault = [...pendingMetrics].some(function (m) {
const cluster = clusters.find((c) => c.name == job.cluster);
const subCluster = cluster.subClusters.find((sc) => sc.name == job.subCluster);
return subCluster.metricConfig.find((smc) => smc.name == m)?.scope === "accelerator";
});
const pendingScopes = ["node"]
if (job.numNodes === 1) {
pendingScopes.push("socket")
pendingScopes.push("core")
pendingScopes.push("hwthread")
if (accScopeDefault) { pendingScopes.push("accelerator") }
}
selectedMetrics = [...pendingMetrics];
selectedScopes = [...pendingScopes];
});
</script> </script>
<TabPane tabId="stats" tab="Statistics Table" class="overflow-x-auto" active={tabActive}> <TabPane tabId="stats" tab="Statistics Table" class="overflow-x-auto" active={tabActive}>
<Row> <Row>
<Col class="m-2"> <Col class="m-2">
<Button outline onclick={() => (isMetricSelectionOpen = true)} class="px-2" color="primary" style="margin-right:0.5rem"> <Button outline onclick={() => (isMetricSelectionOpen = true)} class="px-2" color="primary" style="margin-right:0.5rem">
Select Metrics (Selected {selectedMetrics.length} of {totalMetrics} available) Select Metrics (Selected {selectedTableMetrics.length} of {totalMetrics} available)
</Button> </Button>
{#if job.numNodes > 1 && job.state === "running"} {#if job.numNodes > 1 && job.state === "running"}
<Button class="px-2 ml-auto" color="success" outline onclick={loadScopes} disabled={moreScopes}> <Button class="px-2 ml-auto" color="success" outline onclick={() => (moreScopes = !moreScopes)} disabled={moreScopes}>
{#if !moreScopes} {#if !moreScopes}
<Icon name="plus-square-fill" style="margin-right:0.25rem"/> Add More Scopes <Icon name="plus-square-fill" style="margin-right:0.25rem"/> Add More Scopes
{:else} {:else}
@@ -141,7 +152,7 @@
<StatsTable <StatsTable
hosts={job.resources.map((r) => r.hostname).sort()} hosts={job.resources.map((r) => r.hostname).sort()}
jobStats={$scopedStats?.data?.scopedJobStats} jobStats={$scopedStats?.data?.scopedJobStats}
{selectedMetrics} selectedMetrics={selectedTableMetrics}
/> />
{/if} {/if}
</TabPane> </TabPane>
@@ -149,12 +160,12 @@
<MetricSelection <MetricSelection
bind:isOpen={isMetricSelectionOpen} bind:isOpen={isMetricSelectionOpen}
bind:totalMetrics bind:totalMetrics
presetMetrics={selectedMetrics} presetMetrics={selectedTableMetrics}
cluster={job.cluster} cluster={job.cluster}
subCluster={job.subCluster} subCluster={job.subCluster}
configName="metricConfig_jobViewTableMetrics" configName="metricConfig_jobViewTableMetrics"
preInitialized {globalMetrics}
applyMetrics={(newMetrics) => applyMetrics={(newMetrics) =>
selectedMetrics = [...newMetrics] selectedTableMetrics = [...newMetrics]
} }
/> />

View File

@@ -5,11 +5,12 @@
- `cluster String`: The nodes' cluster - `cluster String`: The nodes' cluster
- `subCluster String`: The nodes' subCluster [Default: ""] - `subCluster String`: The nodes' subCluster [Default: ""]
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null] - `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `pendingSelectedMetrics [String]`: The array of selected metrics [Default []] - `pendingSelectedMetrics [String]`: The array of selected metrics [Default []]
- `selectedResolution Number?`: The selected data resolution [Default: 0] - `selectedResolution Number?`: The selected data resolution [Default: 0]
- `hostnameFilter String?`: The active hostnamefilter [Default: ""] - `hostnameFilter String?`: The active hostnamefilter [Default: ""]
- `hoststateFilter String?`: The active hoststatefilter [Default: ""] - `hoststateFilter String?`: The active hoststatefilter [Default: ""]
- `presetSystemUnits Object`: The object of metric units [Default: null] - `systemUnits Object`: The object of metric units [Default: null]
- `from Date?`: The selected "from" date [Default: null] - `from Date?`: The selected "from" date [Default: null]
- `to Date?`: The selected "to" date [Default: null] - `to Date?`: The selected "to" date [Default: null]
--> -->
@@ -27,11 +28,12 @@
cluster, cluster,
subCluster = "", subCluster = "",
ccconfig = null, ccconfig = null,
globalMetrics = null,
pendingSelectedMetrics = [], pendingSelectedMetrics = [],
selectedResolution = 0, selectedResolution = 0,
hostnameFilter = "", hostnameFilter = "",
hoststateFilter = "", hoststateFilter = "",
presetSystemUnits = null, systemUnits = null,
from = null, from = null,
to = null to = null
} = $props(); } = $props();
@@ -236,7 +238,7 @@
scope="col" scope="col"
style="padding-top: {headerPaddingTop}px" style="padding-top: {headerPaddingTop}px"
> >
{metric} ({presetSystemUnits[metric]}) {metric} ({systemUnits[metric]})
</th> </th>
{/each} {/each}
</tr> </tr>
@@ -250,7 +252,7 @@
</Row> </Row>
{:else} {:else}
{#each nodes as nodeData (nodeData.host)} {#each nodes as nodeData (nodeData.host)}
<NodeListRow {nodeData} {cluster} {selectedMetrics}/> <NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics}/>
{:else} {:else}
<tr> <tr>
<td colspan={selectedMetrics.length + 1}> No nodes found </td> <td colspan={selectedMetrics.length + 1}> No nodes found </td>

View File

@@ -9,10 +9,10 @@
- `hostnameFilter String?`: The active hoststatefilter [Default: ""] - `hostnameFilter String?`: The active hoststatefilter [Default: ""]
- `from Date?`: The selected "from" date [Default: null] - `from Date?`: The selected "from" date [Default: null]
- `to Date?`: The selected "to" date [Default: null] - `to Date?`: The selected "to" date [Default: null]
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
--> -->
<script> <script>
import { getContext } from "svelte";
import { queryStore, gql, getContextClient } from "@urql/svelte"; import { queryStore, gql, getContextClient } from "@urql/svelte";
import { Row, Col, Card, CardHeader, CardBody, Spinner, Badge } from "@sveltestrap/sveltestrap"; import { Row, Col, Card, CardHeader, CardBody, Spinner, Badge } from "@sveltestrap/sveltestrap";
import { checkMetricDisabled } from "../generic/utils.js"; import { checkMetricDisabled } from "../generic/utils.js";
@@ -26,11 +26,11 @@
hostnameFilter = "", hostnameFilter = "",
hoststateFilter = "", hoststateFilter = "",
from = null, from = null,
to = null to = null,
globalMetrics
} = $props(); } = $props();
/* Const Init */ /* Const Init */
const initialized = getContext("initialized");
const client = getContextClient(); const client = getContextClient();
// Node State Colors // Node State Colors
const stateColors = { const stateColors = {
@@ -87,7 +87,7 @@
}, },
})); }));
const mappedData = $derived(handleQueryData($initialized, $nodesQuery?.data)); const mappedData = $derived(handleQueryData($nodesQuery?.data));
const filteredData = $derived(mappedData.filter((h) => { const filteredData = $derived(mappedData.filter((h) => {
if (hostnameFilter) { if (hostnameFilter) {
if (hoststateFilter == 'all') return h.host.includes(hostnameFilter) if (hoststateFilter == 'all') return h.host.includes(hostnameFilter)
@@ -99,7 +99,7 @@
})); }));
/* Functions */ /* Functions */
function handleQueryData(isInitialized, queryData) { function handleQueryData(queryData) {
let rawData = [] let rawData = []
if (queryData) { if (queryData) {
rawData = queryData.nodeMetrics.filter((h) => { rawData = queryData.nodeMetrics.filter((h) => {
@@ -120,7 +120,8 @@
data: h.metrics.filter( data: h.metrics.filter(
(m) => m?.name == selectedMetric && m.scope == "node", (m) => m?.name == selectedMetric && m.scope == "node",
), ),
disabled: isInitialized ? checkMetricDisabled(selectedMetric, cluster, h.subCluster) : null, // TODO: Move To New Func Variant With Disabled Check on WHole Cluster Level: This never Triggers!
disabled: checkMetricDisabled(globalMetrics, selectedMetric, cluster, h.subCluster),
})) }))
.sort((a, b) => a.host.localeCompare(b.host)) .sort((a, b) => a.host.localeCompare(b.host))
} }
@@ -163,6 +164,7 @@
</div> </div>
{#if item?.data} {#if item?.data}
{#if item.disabled === true} {#if item.disabled === true}
<!-- TODO: Will never be Shown: Overview Single Metric Return Will be Null, see Else Case-->
<Card body class="mx-3" color="info" <Card body class="mx-3" color="info"
>Metric disabled for subcluster <code >Metric disabled for subcluster <code
>{selectedMetric}:{item.subCluster}</code >{selectedMetric}:{item.subCluster}</code
@@ -182,7 +184,7 @@
enableFlip enableFlip
/> />
{/key} {/key}
{:else if item.disabled === null} {:else}
<Card body class="mx-3" color="info"> <Card body class="mx-3" color="info">
Global Metric List Not Initialized Global Metric List Not Initialized
Can not determine {selectedMetric} availability: Please Reload Page Can not determine {selectedMetric} availability: Please Reload Page

View File

@@ -5,6 +5,7 @@
- `cluster String`: The nodes' cluster - `cluster String`: The nodes' cluster
- `nodeData Object`: The node data object including metric data - `nodeData Object`: The node data object including metric data
- `selectedMetrics [String]`: The array of selected metrics - `selectedMetrics [String]`: The array of selected metrics
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
--> -->
<script> <script>
@@ -24,6 +25,7 @@
cluster, cluster,
nodeData, nodeData,
selectedMetrics, selectedMetrics,
globalMetrics
} = $props(); } = $props();
/* Var Init*/ /* Var Init*/
@@ -92,6 +94,7 @@
if (scopedNodeMetric?.data) { if (scopedNodeMetric?.data) {
return { return {
disabled: checkMetricDisabled( disabled: checkMetricDisabled(
globalMetrics,
scopedNodeMetric.data.name, scopedNodeMetric.data.name,
cluster, cluster,
nodeData.subCluster, nodeData.subCluster,