Rework tag and tag edit placement, add other feedback

- admin message shown primarily if exists
- comment demo summary tab
This commit is contained in:
Christoph Kluge 2024-09-18 17:23:29 +02:00
parent 6367c1ab4d
commit d7a8bbf40b
5 changed files with 217 additions and 199 deletions

View File

@ -25,7 +25,6 @@
CardHeader, CardHeader,
CardTitle, CardTitle,
Button, Button,
Icon,
} from "@sveltestrap/sveltestrap"; } from "@sveltestrap/sveltestrap";
import { getContext } from "svelte"; import { getContext } from "svelte";
import { import {
@ -35,7 +34,6 @@
transformDataForRoofline, transformDataForRoofline,
} from "./generic/utils.js"; } from "./generic/utils.js";
import Metric from "./job/Metric.svelte"; import Metric from "./job/Metric.svelte";
import TagManagement from "./job/TagManagement.svelte";
import StatsTable from "./job/StatsTable.svelte"; import StatsTable from "./job/StatsTable.svelte";
import JobSummary from "./job/JobSummary.svelte"; import JobSummary from "./job/JobSummary.svelte";
import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte"; import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte";
@ -54,12 +52,10 @@
const ccconfig = getContext("cc-config") const ccconfig = getContext("cc-config")
let isMetricsSelectionOpen = false, let isMetricsSelectionOpen = false,
showFootprint = !!ccconfig[`job_view_showFootprint`],
selectedMetrics = [], selectedMetrics = [],
selectedScopes = []; selectedScopes = [];
let plots = {}, let plots = {},
jobTags,
roofWidth roofWidth
let missingMetrics = [], let missingMetrics = [],
@ -240,14 +236,22 @@
{:else if $initq.data} {:else if $initq.data}
<Card class="overflow-auto" style="height: 400px;"> <Card class="overflow-auto" style="height: 400px;">
<TabContent> <!-- on:tab={(e) => (status = e.detail)} --> <TabContent> <!-- on:tab={(e) => (status = e.detail)} -->
<TabPane tabId="meta-info" tab="Job Info" active> {#if $initq.data?.job?.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>
The following note was added by administrators:
</Card>
<Card body>
{@html $initq.data.job.metaData.message}
</Card>
</CardBody>
</TabPane>
{/if}
<TabPane tabId="meta-info" tab="Job Info" active={$initq.data?.job?.metaData?.message?false:true}>
<CardBody class="pb-2"> <CardBody class="pb-2">
<JobInfo job={$initq.data.job} {jobTags} /> <JobInfo job={$initq.data.job} {username} {authlevel} {roles} showTags={false} showTagedit/>
</CardBody>
</TabPane>
<TabPane tabId="job-tags" tab="Job Tags">
<CardBody>
<TagManagement job={$initq.data.job} {username} {authlevel} {roles} bind:jobTags renderModal={false}/>
</CardBody> </CardBody>
</TabPane> </TabPane>
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0} {#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0}
@ -260,15 +264,6 @@
</CardBody> </CardBody>
</TabPane> </TabPane>
{/if} {/if}
{#if $initq.data?.job?.metaData?.message}
<TabPane tabId="admin-msg" tab="Admin Note">
<CardBody>
<p>This note was added by administrators:</p>
<hr/>
<p>{@html $initq.data.job.metaData.message}</p>
</CardBody>
</TabPane>
{/if}
</TabContent> </TabContent>
</Card> </Card>
{:else} {:else}
@ -276,21 +271,19 @@
{/if} {/if}
</Col> </Col>
<!-- If enabled: Column 2: Job Footprint, Polar Representation, Heuristic Summary --> <!-- Column 2: Job Footprint, Polar Representation, Heuristic Summary -->
{#if showFootprint} <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 && $jobMetrics?.data}
{:else if $initq?.data && $jobMetrics?.data} <JobSummary job={$initq.data.job} jobMetrics={$jobMetrics.data.jobMetrics}/>
<JobSummary job={$initq.data.job} jobMetrics={$jobMetrics.data.jobMetrics}/> {:else}
{:else} <Spinner secondary />
<Spinner secondary /> {/if}
{/if} </Col>
</Col>
{/if}
<!-- Column 3: Job Roofline; If footprint Enabled: full width, else half width --> <!-- Column 3: Job Roofline; If footprint Enabled: full width, else half width -->
<Col xs={12} md={showFootprint ? 12 : 6} xl={showFootprint ? 5 : 6} xxl={6}> <Col xs={12} md={12} xl={5} xxl={6}>
{#if $initq.error || $jobMetrics.error} {#if $initq.error || $jobMetrics.error}
<Card body color="danger"> <Card body color="danger">
<p>Initq Error: {$initq.error?.message}</p> <p>Initq Error: {$initq.error?.message}</p>

View File

@ -23,12 +23,22 @@
if ($initialized && tag == null) if ($initialized && tag == null)
tag = allTags.find(tag => tag.id == id) tag = allTags.find(tag => tag.id == id)
} }
function getScopeColor(scope) {
switch (scope) {
case "admin":
return "#19e5e6";
case "global":
return "#c85fc8";
default:
return "#ffc107";
}
}
</script> </script>
<style> <style>
a { a {
margin-left: 0.5rem; margin-right: 0.5rem;
line-height: 2;
} }
span { span {
font-size: 0.9rem; font-size: 0.9rem;
@ -37,13 +47,7 @@
<a target={clickable ? "_blank" : null} href={clickable ? `/monitoring/jobs/?tag=${id}` : null}> <a target={clickable ? "_blank" : null} href={clickable ? `/monitoring/jobs/?tag=${id}` : null}>
{#if tag} {#if tag}
{#if tag?.scope === "global"} <span style="background-color:{getScopeColor(tag?.scope)};" class="my-1 badge text-dark">{tag.type}: {tag.name}</span>
<span style="background-color:#c85fc8;" class="badge text-dark">{tag.type}: {tag.name}</span>
{:else if tag?.scope === "admin"}
<span style="background-color:#19e5e6;" class="badge text-dark">{tag.type}: {tag.name}</span>
{:else}
<span class="badge bg-warning text-dark">{tag.type}: {tag.name}</span>
{/if}
{:else} {:else}
Loading... Loading...
{/if} {/if}

View File

@ -1,5 +1,5 @@
<!-- <!--
@component Job View Subcomponent; allows management of job tags by deletion or new entries @component Job Info Subcomponent; allows management of job tags by deletion or new entries
Properties: Properties:
- `job Object`: The job object - `job Object`: The job object
@ -30,8 +30,8 @@
Alert, Alert,
Tooltip, Tooltip,
} from "@sveltestrap/sveltestrap"; } from "@sveltestrap/sveltestrap";
import { fuzzySearchTags } from "../generic/utils.js"; import { fuzzySearchTags } from "../utils.js";
import Tag from "../generic/helper/Tag.svelte"; import Tag from "./Tag.svelte";
export let job; export let job;
export let jobTags = job.tags; export let jobTags = job.tags;
@ -178,12 +178,7 @@
{#if renderModal} {#if renderModal}
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}> <Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
<ModalHeader> <ModalHeader>
Manage Tags Manage Tags <Icon name="tags"/>
{#if pendingChange !== false}
<Spinner size="sm" secondary />
{:else}
<Icon name="tags" />
{/if}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<InputGroup class="mb-3"> <InputGroup class="mb-3">
@ -317,8 +312,8 @@
</ModalFooter> </ModalFooter>
</Modal> </Modal>
<Button outline on:click={() => (isOpen = true)}> <Button outline on:click={() => (isOpen = true)} size="sm" color="primary">
Manage Tags <Icon name="tags" /> Manage {jobTags?.length ? jobTags.length : ''} Tags
</Button> </Button>
{:else} {:else}

View File

@ -10,9 +10,14 @@
import { Badge, Icon } from "@sveltestrap/sveltestrap"; import { Badge, Icon } from "@sveltestrap/sveltestrap";
import { scrambleNames, scramble } from "../utils.js"; import { scrambleNames, scramble } from "../utils.js";
import Tag from "../helper/Tag.svelte"; import Tag from "../helper/Tag.svelte";
import TagManagement from "../helper/TagManagement.svelte";
export let job; export let job;
export let jobTags = job.tags; export let jobTags = job.tags;
export let showTagedit = false;
export let username = null;
export let authlevel= null;
export let roles = null;
function formatDuration(duration) { function formatDuration(duration) {
const hours = Math.floor(duration / 3600); const hours = Math.floor(duration / 3600);
@ -36,7 +41,7 @@
</script> </script>
<div> <div>
<p> <p class="mb-2">
<span class="fw-bold" <span class="fw-bold"
><a href="/monitoring/job/{job.id}" target="_blank">{job.jobId}</a> ><a href="/monitoring/job/{job.id}" target="_blank">{job.jobId}</a>
({job.cluster})</span ({job.cluster})</span
@ -63,7 +68,7 @@
{/if} {/if}
</p> </p>
<p> <p class="mb-2">
<Icon name="person-fill" /> <Icon name="person-fill" />
<a class="fst-italic" href="/monitoring/user/{job.user}" target="_blank"> <a class="fst-italic" href="/monitoring/user/{job.user}" target="_blank">
{scrambleNames ? scramble(job.user) : job.user} {scrambleNames ? scramble(job.user) : job.user}
@ -84,7 +89,7 @@
{/if} {/if}
</p> </p>
<p> <p class="mb-2">
{#if job.numNodes == 1} {#if job.numNodes == 1}
{job.resources[0].hostname} {job.resources[0].hostname}
{:else} {:else}
@ -104,7 +109,7 @@
{job.subCluster} {job.subCluster}
</p> </p>
<p> <p class="mb-2">
Start: <span class="fw-bold" Start: <span class="fw-bold"
>{new Date(job.startTime).toLocaleString()}</span >{new Date(job.startTime).toLocaleString()}</span
> >
@ -117,11 +122,25 @@
{/if} {/if}
</p> </p>
<p class="mb-2"> {#if showTagedit}
{#each jobTags as tag} <hr class="mt-0 mb-2"/>
<Tag {tag} /> <p class="mb-1">
{/each} <TagManagement bind:jobTags {job} {username} {authlevel} {roles} renderModal/> :
</p> {#if jobTags?.length > 0}
{#each jobTags as tag}
<Tag {tag}/>
{/each}
{:else}
<span style="font-size: 0.9rem; background-color: lightgray;" class="my-1 badge text-dark">No Tags</span>
{/if}
</p>
{:else}
<p class="mb-1">
{#each jobTags as tag}
<Tag {tag} />
{/each}
</p>
{/if}
</div> </div>
<style> <style>

View File

@ -68,6 +68,7 @@
export let height = "400px"; export let height = "400px";
const ccconfig = getContext("cc-config") const ccconfig = getContext("cc-config")
const showFootprint = !!ccconfig[`job_view_showFootprint`];
const footprintData = job?.footprint?.map((jf) => { const footprintData = job?.footprint?.map((jf) => {
const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name); const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
@ -165,101 +166,142 @@
} }
} }
function writeSummary(fpd) { /*
// Hardcoded! Needs to be retrieved from globalMetrics function writeSummary(fpd) {
const performanceMetrics = ['flops_any', 'mem_bw']; // Hardcoded! Needs to be retrieved from globalMetrics
const utilizationMetrics = ['cpu_load', 'acc_utilization']; const performanceMetrics = ['flops_any', 'mem_bw'];
const energyMetrics = ['cpu_power']; const utilizationMetrics = ['cpu_load', 'acc_utilization'];
const energyMetrics = ['cpu_power'];
let performanceScore = 0; let performanceScore = 0;
let utilizationScore = 0; let utilizationScore = 0;
let energyScore = 0; let energyScore = 0;
let performanceMetricsCounted = 0; let performanceMetricsCounted = 0;
let utilizationMetricsCounted = 0; let utilizationMetricsCounted = 0;
let energyMetricsCounted = 0; let energyMetricsCounted = 0;
fpd.forEach(metric => { fpd.forEach(metric => {
console.log('Metric, Impact', metric.name, metric.impact) console.log('Metric, Impact', metric.name, metric.impact)
if (performanceMetrics.includes(metric.name)) { if (performanceMetrics.includes(metric.name)) {
performanceScore += metric.impact performanceScore += metric.impact
performanceMetricsCounted += 1 performanceMetricsCounted += 1
} else if (utilizationMetrics.includes(metric.name)) { } else if (utilizationMetrics.includes(metric.name)) {
utilizationScore += metric.impact utilizationScore += metric.impact
utilizationMetricsCounted += 1 utilizationMetricsCounted += 1
} else if (energyMetrics.includes(metric.name)) { } else if (energyMetrics.includes(metric.name)) {
energyScore += metric.impact energyScore += metric.impact
energyMetricsCounted += 1 energyMetricsCounted += 1
}
});
performanceScore = (performanceMetricsCounted == 0) ? performanceScore : (performanceScore / performanceMetricsCounted);
utilizationScore = (utilizationMetricsCounted == 0) ? utilizationScore : (utilizationScore / utilizationMetricsCounted);
energyScore = (energyMetricsCounted == 0) ? energyScore : (energyScore / energyMetricsCounted);
let res = [];
console.log('Perf', performanceScore, performanceMetricsCounted)
console.log('Util', utilizationScore, utilizationMetricsCounted)
console.log('Energy', energyScore, energyMetricsCounted)
if (performanceScore == 1) {
res.push('<b>Performance:</b> Your job performs well.')
} else if (performanceScore != 0) {
res.push('<b>Performance:</b> Your job performs suboptimal.')
} }
});
performanceScore = (performanceMetricsCounted == 0) ? performanceScore : (performanceScore / performanceMetricsCounted); if (utilizationScore == 1) {
utilizationScore = (utilizationMetricsCounted == 0) ? utilizationScore : (utilizationScore / utilizationMetricsCounted); res.push('<b>Utilization:</b> Your job utilizes resources well.')
energyScore = (energyMetricsCounted == 0) ? energyScore : (energyScore / energyMetricsCounted); } else if (utilizationScore != 0) {
res.push('<b>Utilization:</b> Your job utilizes resources suboptimal.')
}
let res = []; if (energyScore == 1) {
res.push('<b>Energy:</b> Your job has good energy values.')
} else if (energyScore != 0) {
res.push('<b>Energy:</b> Your job consumes more energy than necessary.')
}
console.log('Perf', performanceScore, performanceMetricsCounted) return res;
console.log('Util', utilizationScore, utilizationMetricsCounted) };
console.log('Energy', energyScore, energyMetricsCounted)
if (performanceScore == 1) { $: summaryMessages = writeSummary(footprintData)
res.push('<b>Performance:</b> Your job performs well.') */
} else if (performanceScore != 0) {
res.push('<b>Performance:</b> Your job performs suboptimal.')
}
if (utilizationScore == 1) {
res.push('<b>Utilization:</b> Your job utilizes resources well.')
} else if (utilizationScore != 0) {
res.push('<b>Utilization:</b> Your job utilizes resources suboptimal.')
}
if (energyScore == 1) {
res.push('<b>Energy:</b> Your job has good energy values.')
} else if (energyScore != 0) {
res.push('<b>Energy:</b> Your job consumes more energy than necessary.')
}
return res;
};
$: summaryMessages = writeSummary(footprintData)
</script> </script>
<Card class="overflow-auto" style="width: {width}; height: {height}"> <Card class="overflow-auto" style="width: {width}; height: {height}">
<TabContent> <!-- on:tab={(e) => (status = e.detail)} --> <TabContent> <!-- on:tab={(e) => (status = e.detail)} -->
<TabPane tabId="foot" tab="Footprint" active> {#if showFootprint}
<CardBody> <TabPane tabId="foot" tab="Footprint" active>
{#each footprintData as fpd, index} <CardBody>
{#if fpd.impact !== 4} {#each footprintData as fpd, index}
<div class="mb-1 d-flex justify-content-between"> {#if fpd.impact !== 4}
<div>&nbsp;<b>{fpd.name} ({fpd.stat})</b></div> <div class="mb-1 d-flex justify-content-between">
<div <div>&nbsp;<b>{fpd.name} ({fpd.stat})</b></div>
class="cursor-help d-inline-flex" <div
id={`footprint-${job.jobId}-${index}`} class="cursor-help d-inline-flex"
> id={`footprint-${job.jobId}-${index}`}
<div class="mx-1"> >
{#if fpd.impact === 3 || fpd.impact === -1} <div class="mx-1">
<Icon name="exclamation-triangle-fill" class="text-danger" /> {#if fpd.impact === 3 || fpd.impact === -1}
{:else if fpd.impact === 2} <Icon name="exclamation-triangle-fill" class="text-danger" />
<Icon name="exclamation-triangle" class="text-warning" /> {:else if fpd.impact === 2}
{/if} <Icon name="exclamation-triangle" class="text-warning" />
{#if fpd.impact === 3} {/if}
<Icon name="emoji-frown" class="text-danger" /> {#if fpd.impact === 3}
{:else if fpd.impact === 2} <Icon name="emoji-frown" class="text-danger" />
<Icon name="emoji-neutral" class="text-warning" /> {:else if fpd.impact === 2}
{:else if fpd.impact === 1} <Icon name="emoji-neutral" class="text-warning" />
<Icon name="emoji-smile" class="text-success" /> {:else if fpd.impact === 1}
{:else if fpd.impact === 0} <Icon name="emoji-smile" class="text-success" />
<Icon name="emoji-laughing" class="text-info" /> {:else if fpd.impact === 0}
{:else if fpd.impact === -1} <Icon name="emoji-laughing" class="text-info" />
<Icon name="emoji-dizzy" class="text-danger" /> {:else if fpd.impact === -1}
{/if} <Icon name="emoji-dizzy" class="text-danger" />
{/if}
</div>
<div>
{fpd.value} / {fpd.peak}
{fpd.unit} &nbsp;
</div>
</div> </div>
<Tooltip
target={`footprint-${job.jobId}-${index}`}
placement="right"
>{fpd.message}</Tooltip
>
</div>
<Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
{#if fpd.dir}
<Col xs="1">
<Icon name="caret-left-fill" />
</Col>
{/if}
<Col xs="11" class="align-content-center">
<Progress value={fpd.value} max={fpd.peak} color={fpd.color} />
</Col>
{#if !fpd.dir}
<Col xs="1">
<Icon name="caret-right-fill" />
</Col>
{/if}
</Row>
{:else}
<div class="mb-1 d-flex justify-content-between">
<div> <div>
{fpd.value} / {fpd.peak} &nbsp;<b>{fpd.name} ({fpd.stat})</b>
{fpd.unit} &nbsp; </div>
<div
class="cursor-help d-inline-flex"
id={`footprint-${job.jobId}-${index}`}
>
<div class="mx-1">
<Icon name="info-circle"/>
</div>
<div>
{fpd.value}&nbsp;
</div>
</div> </div>
</div> </div>
<Tooltip <Tooltip
@ -267,49 +309,12 @@
placement="right" placement="right"
>{fpd.message}</Tooltip >{fpd.message}</Tooltip
> >
</div> {/if}
<Row cols={12} class="{(footprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}"> {/each}
{#if fpd.dir} </CardBody>
<Col xs="1"> </TabPane>
<Icon name="caret-left-fill" /> {/if}
</Col> <TabPane tabId="polar" tab="Polar" active={!showFootprint}>
{/if}
<Col xs="11" class="align-content-center">
<Progress value={fpd.value} max={fpd.peak} color={fpd.color} />
</Col>
{#if !fpd.dir}
<Col xs="1">
<Icon name="caret-right-fill" />
</Col>
{/if}
</Row>
{:else}
<div class="mb-1 d-flex justify-content-between">
<div>
&nbsp;<b>{fpd.name} ({fpd.stat})</b>
</div>
<div
class="cursor-help d-inline-flex"
id={`footprint-${job.jobId}-${index}`}
>
<div class="mx-1">
<Icon name="info-circle"/>
</div>
<div>
{fpd.value}&nbsp;
</div>
</div>
</div>
<Tooltip
target={`footprint-${job.jobId}-${index}`}
placement="right"
>{fpd.message}</Tooltip
>
{/if}
{/each}
</CardBody>
</TabPane>
<TabPane tabId="polar" tab="Polar">
<CardBody> <CardBody>
<Polar <Polar
{footprintData} {footprintData}
@ -317,19 +322,21 @@
/> />
</CardBody> </CardBody>
</TabPane> </TabPane>
<TabPane tabId="summary" tab="Summary"> <!--
<CardBody> <TabPane tabId="summary" tab="Summary">
<p>Based on footprint data, this job performs as follows:</p> <CardBody>
<hr/> <p>Based on footprint data, this job performs as follows:</p>
<ul> <hr/>
{#each summaryMessages as sm} <ul>
<li> {#each summaryMessages as sm}
{@html sm} <li>
</li> {@html sm}
{/each} </li>
</ul> {/each}
</CardBody> </ul>
</TabPane> </CardBody>
</TabPane>
-->
</TabContent> </TabContent>
</Card> </Card>