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 }
}
`);
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 */
const client = getContextClient();
const query = gql`
query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!) {
scopedJobStats(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes) {
@@ -89,25 +85,68 @@
/* State Init */
let plots = $state({});
let isMetricsSelectionOpen = $state(false);
let selectedMetrics = $state([]);
let selectedScopes = $state([]);
let totalMetrics = $state(0);
/* Derived */
const showSummary = $derived((!!ccconfig[`jobView_showFootprint`] || !!ccconfig[`jobView_showPolarPlot`]))
/* Derived Init Return */
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({
client: client,
query: query,
variables: { dbid, selectedMetrics, selectedScopes },
})
);
const missingMetrics = $derived.by(() => {
if ($initq?.data && $jobMetrics?.data) {
let job = $initq.data.job;
if (thisJob && $jobMetrics?.data) {
let metrics = $jobMetrics.data.scopedJobStats;
let metricNames = $initq.data.globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === job.cluster)) {
let metricNames = globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === thisJob.cluster)) {
names.push(gm.name);
}
return names;
@@ -118,9 +157,10 @@
!metrics.some((jm) => jm.name == metric) &&
selectedMetrics.includes(metric) &&
!checkMetricDisabled(
globalMetrics,
metric,
$initq.data.job.cluster,
$initq.data.job.subCluster,
thisJob.cluster,
thisJob.subCluster,
),
);
} else {
@@ -129,17 +169,16 @@
});
const missingHosts = $derived.by(() => {
if ($initq?.data && $jobMetrics?.data) {
let job = $initq.data.job;
if (thisJob && $jobMetrics?.data) {
let metrics = $jobMetrics.data.scopedJobStats;
let metricNames = $initq.data.globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === job.cluster)) {
let metricNames = globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === thisJob.cluster)) {
names.push(gm.name);
}
return names;
}, []);
return job.resources
return thisJob.resources
.map(({ hostname }) => ({
hostname: hostname,
metrics: metricNames.filter(
@@ -165,51 +204,19 @@
? "Loading..."
: $initq?.error
? "Error"
: `Job ${$initq.data.job.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)];
: `Job ${thisJob.jobId} - ClusterCockpit`;
});
/* Functions */
const orderAndMap = (grouped, selectedMetrics) =>
selectedMetrics.map((metric) => ({
const orderAndMap = (grouped, inputMetrics) =>
inputMetrics.map((metric) => ({
metric: metric,
data: grouped.find((group) => group[0].name == metric),
disabled: checkMetricDisabled(
globalMetrics,
metric,
$initq.data.job.cluster,
$initq.data.job.subCluster,
thisJob.cluster,
thisJob.subCluster,
),
}));
</script>
@@ -219,34 +226,34 @@
<Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0">
{#if $initq.error}
<Card body color="danger">{$initq.error.message}</Card>
{:else if $initq?.data}
{:else if thisJob}
<Card class="overflow-auto" style="height: auto;">
<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>
<CardBody>
<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:
</Card>
<Card body>
{@html $initq.data.job.metaData.message}
{@html thisJob.metaData.message}
</Card>
</CardBody>
</TabPane>
{/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">
<JobInfo job={$initq.data.job} {username} {authlevel} {roles} showTagEdit/>
<JobInfo job={thisJob} {username} {authlevel} {roles} showTagEdit/>
</CardBody>
</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">
<span slot="tab">
{$initq.data.job.concurrentJobs.items.length} Concurrent Jobs
{thisJob.concurrentJobs.items.length} Concurrent Jobs
</span>
<CardBody>
<ConcurrentJobs cJobs={$initq.data.job.concurrentJobs} showLinks={(authlevel > roles.manager)}/>
<ConcurrentJobs cJobs={thisJob.concurrentJobs} showLinks={(authlevel > roles.manager)}/>
</CardBody>
</TabPane>
{/if}
@@ -261,9 +268,9 @@
<Col xs={12} md={6} xl={4} xxl={3} class="mb-3 mb-xxl-0">
{#if $initq.error}
<Card body color="danger">{$initq.error.message}</Card>
{:else if $initq?.data}
{:else if thisJob}
{#if showSummary}
<JobSummary job={$initq.data.job}/>
<JobSummary job={thisJob}/>
{/if}
{:else}
<Spinner secondary />
@@ -274,9 +281,9 @@
<Col xs={12} md={12} xl={5} xxl={6}>
{#if $initq.error}
<Card body color="danger">{$initq.error.message}</Card>
{:else if $initq?.data}
{:else if thisJob}
{#if showRoofline}
<JobRoofline job={$initq.data.job} clusters={$initq.data.clusters}/>
<JobRoofline job={thisJob} {clusterInfo}/>
{/if}
{:else}
<Spinner secondary />
@@ -285,10 +292,10 @@
</Row>
<!-- 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">
<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>
</Row>
{/if}
@@ -297,7 +304,7 @@
<Card class="mb-3">
<CardBody>
<Row class="mb-2">
{#if $initq?.data}
{#if thisJob}
<Col xs="auto">
<Button outline onclick={() => (isMetricsSelectionOpen = true)} color="primary">
Select Metrics (Selected {selectedMetrics.length} of {totalMetrics} available)
@@ -310,7 +317,7 @@
{#if $jobMetrics.error}
<Row class="mt-2">
<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>
<br />
{/if}
@@ -323,18 +330,18 @@
<Spinner secondary />
</Col>
</Row>
{:else if $initq?.data && $jobMetrics?.data?.scopedJobStats}
{:else if thisJob && $jobMetrics?.data?.scopedJobStats}
<!-- Note: Ignore '#snippet' Error in IDE -->
{#snippet gridContent(item)}
{#if item.data}
<Metric
bind:this={plots[item.metric]}
job={$initq.data.job}
job={thisJob}
metricName={item.metric}
metricUnit={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.unit}
nativeScope={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.scope}
metricUnit={globalMetrics.find((gm) => gm.name == item.metric)?.unit}
nativeScope={globalMetrics.find((gm) => gm.name == item.metric)?.scope}
presetScopes={item.data.map((x) => x.scope)}
isShared={$initq.data.job.shared != "none"}
isShared={thisJob.shared != "none"}
/>
{:else if item.disabled == true}
<Card color="info">
@@ -342,7 +349,7 @@
<b>Disabled Metric</b>
</CardHeader>
<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>
</CardBody>
</Card>
@@ -353,7 +360,7 @@
</CardHeader>
<CardBody>
<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>
</Card>
{/if}
@@ -374,7 +381,7 @@
<!-- Metadata && Statistcics Table -->
<Row class="mb-3">
<Col>
{#if $initq?.data}
{#if thisJob}
<Card>
<TabContent>
{#if somethingMissing}
@@ -409,12 +416,12 @@
{/if}
{#if showStatsTable}
<!-- 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}
<TabPane tabId="job-script" tab="Job Script">
<div class="pre-wrapper">
{#if $initq.data.job.metaData?.jobScript}
<pre><code>{$initq.data.job.metaData?.jobScript}</code></pre>
{#if thisJob.metaData?.jobScript}
<pre><code>{thisJob.metaData?.jobScript}</code></pre>
{:else}
<Card body color="warning">No job script available</Card>
{/if}
@@ -422,8 +429,8 @@
</TabPane>
<TabPane tabId="slurm-info" tab="Slurm Info">
<div class="pre-wrapper">
{#if $initq.data.job.metaData?.slurmInfo}
<pre><code>{$initq.data.job.metaData?.slurmInfo}</code></pre>
{#if thisJob.metaData?.slurmInfo}
<pre><code>{thisJob.metaData?.slurmInfo}</code></pre>
{:else}
<Card body color="warning"
>No additional slurm information available</Card
@@ -437,15 +444,15 @@
</Col>
</Row>
{#if $initq?.data}
{#if thisJob}
<MetricSelection
bind:isOpen={isMetricsSelectionOpen}
bind:totalMetrics
presetMetrics={selectedMetrics}
cluster={$initq.data.job.cluster}
subCluster={$initq.data.job.subCluster}
cluster={thisJob.cluster}
subCluster={thisJob.subCluster}
configName="metricConfig_jobViewPlotMetrics"
preInitialized
{globalMetrics}
applyMetrics={(newMetrics) =>
selectedMetrics = [...newMetrics]
}