add job exclusivity filter, review db indices

This commit is contained in:
Christoph Kluge
2026-01-13 16:59:52 +01:00
parent 8576ae458d
commit 518e9950ea
10 changed files with 365 additions and 248 deletions

View File

@@ -28,7 +28,7 @@
} from "@sveltestrap/sveltestrap";
import Info from "./filters/InfoBox.svelte";
import Cluster from "./filters/Cluster.svelte";
import JobStates, { allJobStates } from "./filters/JobStates.svelte";
import JobStates, { allJobStates, mapSharedStates } from "./filters/JobStates.svelte";
import StartTime, { startTimeSelectOptions } from "./filters/StartTime.svelte";
import Duration from "./filters/Duration.svelte";
import Tags from "./filters/Tags.svelte";
@@ -69,6 +69,8 @@
cluster: null,
partition: null,
states: allJobStates,
shared: "",
schedule: "",
startTime: { from: null, to: null, range: ""},
duration: {
lessThan: null,
@@ -103,6 +105,8 @@
filterPresets.states || filterPresets.state
? [filterPresets.state].flat()
: allJobStates,
shared: filterPresets.shared || "",
schedule: filterPresets.schedule || "",
startTime: filterPresets.startTime || { from: null, to: null, range: ""},
duration: filterPresets.duration || {
lessThan: null,
@@ -146,19 +150,39 @@
let items = [];
if (filters.dbId.length != 0)
items.push({ dbId: filters.dbId });
if (filters.jobId)
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
if (filters.arrayJobId != null)
items.push({ arrayJobId: filters.arrayJobId });
if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
if (filters.project)
items.push({ project: { [filters.projectMatch]: filters.project } });
if (filters.user)
items.push({ user: { [filters.userMatch]: filters.user } });
if (filters.cluster) items.push({ cluster: { eq: filters.cluster } });
if (filters.partition) items.push({ partition: { eq: filters.partition } });
if (filters.states.length != allJobStates?.length)
items.push({ state: filters.states });
if (filters.shared) items.push({ shared: filters.shared });
if (filters.project)
items.push({ project: { [filters.projectMatch]: filters.project } });
if (filters.user)
items.push({ user: { [filters.userMatch]: filters.user } });
if (filters.numNodes.from != null || filters.numNodes.to != null) {
items.push({
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
});
}
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
items.push({
numAccelerators: {
from: filters.numAccelerators.from,
to: filters.numAccelerators.to,
},
});
}
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
items.push({
numHWThreads: {
from: filters.numHWThreads.from,
to: filters.numHWThreads.to,
},
});
}
if (filters.arrayJobId != null)
items.push({ arrayJobId: filters.arrayJobId });
if (filters.tags.length != 0) items.push({ tags: filters.tags });
if (filters.startTime.from || filters.startTime.to)
items.push({
startTime: { from: filters.startTime.from, to: filters.startTime.to },
@@ -175,36 +199,17 @@
items.push({ duration: { from: 0, to: filters.duration.lessThan } });
if (filters.duration.moreThan)
items.push({ duration: { from: filters.duration.moreThan, to: 604800 } }); // 7 days to include special jobs with long runtimes
if (filters.tags.length != 0) items.push({ tags: filters.tags });
if (filters.numNodes.from != null || filters.numNodes.to != null) {
items.push({
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
});
}
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
items.push({
numHWThreads: {
from: filters.numHWThreads.from,
to: filters.numHWThreads.to,
},
});
}
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
items.push({
numAccelerators: {
from: filters.numAccelerators.from,
to: filters.numAccelerators.to,
},
});
}
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
if (filters.energy.from || filters.energy.to)
items.push({
energy: { from: filters.energy.from, to: filters.energy.to },
});
if (filters.jobId)
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
if (filters.stats.length != 0)
items.push({ metricStats: filters.stats.map((st) => { return { metricName: st.field, range: { from: st.from, to: st.to }} }) });
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
if (filters.schedule) items.push({ schedule: filters.schedule });
applyFilters({ filters: items });
changeURL();
return items;
@@ -248,6 +253,8 @@
if (filters.partition) opts.push(`partition=${filters.partition}`);
if (filters.states.length != allJobStates?.length)
for (let state of filters.states) opts.push(`state=${state}`);
if (filters.shared) opts.push(`shared=${filters.shared}`);
if (filters.schedule) opts.push(`schedule=${filters.schedule}`);
if (filters.startTime.from && filters.startTime.to)
opts.push(
`startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`,
@@ -366,6 +373,23 @@
{#if filters.states.length != allJobStates?.length}
<Info icon="gear-fill" onclick={() => (isJobStatesOpen = true)}>
{filters.states.join(", ")}
{#if filters.shared && !filters.schedule}
({mapSharedStates[filters.shared]})
{:else if filters.schedule && !filters.shared}
({filters.schedule.charAt(0).toUpperCase() + filters.schedule?.slice(1)})
{:else if (filters.shared && filters.schedule)}
({[mapSharedStates[filters.shared], (filters.schedule.charAt(0).toUpperCase() + filters.schedule.slice(1))].join(", ")})
{/if}
</Info>
{:else if (filters.shared || filters.schedule)}
<Info icon="gear-fill" onclick={() => (isJobStatesOpen = true)}>
{#if filters.shared && !filters.schedule}
{mapSharedStates[filters.shared]}
{:else if filters.schedule && !filters.shared}
{filters.schedule.charAt(0).toUpperCase() + filters.schedule?.slice(1)}
{:else if (filters.shared && filters.schedule)}
{[mapSharedStates[filters.shared], (filters.schedule.charAt(0).toUpperCase() + filters.schedule.slice(1))].join(", ")}
{/if}
</Info>
{/if}
@@ -468,6 +492,8 @@
<JobStates
bind:isOpen={isJobStatesOpen}
presetStates={filters.states}
presetShared={filters.shared}
presetSchedule={filters.schedule}
setFilter={(filter) => updateFilters(filter)}
/>

View File

@@ -4,23 +4,35 @@
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetStates [String]?`: The latest selected filter state [Default: [...allJobStates]]
- `presetShared String?`: The latest selected filter shared [Default: ""]
- `presetShedule String?`: The latest selected filter schedule [Default: ""]
- `setFilter Func`: The callback function to apply current filter selection
Exported:
- `const allJobStates [String]`: List of all available job states used in cc-backend
- `const mapSharedStates {String:String}`: Object of all available shared states used in cc-backend with label
-->
<script module>
export const allJobStates = [
"pending",
"running",
"completed",
"failed",
"cancelled",
"stopped",
"timeout",
"deadline",
"preempted",
"suspended",
"cancelled",
"out_of_memory",
"boot_fail",
"node_fail"
];
export const mapSharedStates = {
none: "Exclusive",
multi_user: "Shared",
single_user: "Multitask",
};
</script>
<script>
@@ -34,17 +46,31 @@
ModalBody,
ModalHeader,
ModalFooter,
Input,
Row,
Col
} from "@sveltestrap/sveltestrap";
/* Svelte 5 Props */
let {
isOpen = $bindable(false),
presetStates = [...allJobStates],
presetShared = "",
presetSchedule = "",
setFilter
} = $props();
/* Const Init */
const allSharedStates = [
"none",
"multi_user",
"single_user",
];
/* State Init */
let pendingStates = $state([...presetStates]);
let pendingShared = $state(presetShared);
let pendingSchedule = $state(presetSchedule);
</script>
@@ -60,10 +86,26 @@
name="flavours"
value={state}
/>
{state}
{state.charAt(0).toUpperCase() + state.slice(1)}
</ListGroupItem>
{/each}
</ListGroup>
<hr/>
<Row>
<Col>
<h5>Resource Sharing</h5>
<Input type="radio" bind:group={pendingShared} value="" label="All" />
{#each allSharedStates as shared}
<Input type="radio" bind:group={pendingShared} value={shared} label={mapSharedStates[shared]} />
{/each}
</Col>
<Col>
<h5>Processing Type</h5>
<Input type="radio" bind:group={pendingSchedule} value="" label="All" />
<Input type="radio" bind:group={pendingSchedule} value="interactive" label="Interactive" />
<Input type="radio" bind:group={pendingSchedule} value="batch" label="Batch Process" />
</Col>
</Row>
</ModalBody>
<ModalFooter>
<Button
@@ -71,21 +113,32 @@
disabled={pendingStates.length == 0}
onclick={() => {
isOpen = false;
setFilter({ states: [...pendingStates] });
setFilter({ states: [...pendingStates], shared: pendingShared, schedule: pendingSchedule });
}}>Close & Apply</Button
>
<Button
color="warning"
onclick={() => {
pendingStates = [];
}}>Deselect All</Button
>
{#if pendingStates.length != 0}
<Button
color="warning"
onclick={() => {
pendingStates = [];
}}>Deselect All</Button
>
{:else}
<Button
color="success"
onclick={() => {
pendingStates = [...allJobStates];
}}>Select All</Button
>
{/if}
<Button
color="danger"
onclick={() => {
isOpen = false;
pendingStates = [...allJobStates];
setFilter({ states: [...pendingStates] });
pendingShared = "";
pendingSchedule = "";
setFilter({ states: [...pendingStates], shared: pendingShared, schedule: pendingSchedule });
}}>Reset</Button
>
<Button onclick={() => (isOpen = false)}>Close</Button>