Continue fixing errors

This commit is contained in:
Jan Eitzinger 2023-05-04 09:19:43 +02:00
parent f235b5e911
commit 518f4664a1
4 changed files with 224 additions and 199 deletions

View File

@ -6,7 +6,7 @@ import terser from '@rollup/plugin-terser';
import css from 'rollup-plugin-css-only'; import css from 'rollup-plugin-css-only';
// const production = !process.env.ROLLUP_WATCH; // const production = !process.env.ROLLUP_WATCH;
const production = true const production = false
const plugins = [ const plugins = [
svelte({ svelte({

View File

@ -9,6 +9,7 @@
import Filters from "./filters/Filters.svelte"; import Filters from "./filters/Filters.svelte";
import { queryStore, gql, getContextClient } from "@urql/svelte"; import { queryStore, gql, getContextClient } from "@urql/svelte";
import { scramble, scrambleNames } from "./joblist/JobInfo.svelte"; import { scramble, scrambleNames } from "./joblist/JobInfo.svelte";
import { UniqueInputFieldNamesRule } from "graphql";
const {} = init(); const {} = init();
@ -20,7 +21,9 @@
"Invalid list type provided!" "Invalid list type provided!"
); );
const stats = queryStore({ let filters;
$: stats = queryStore({
client: getContextClient(), client: getContextClient(),
query: gql` query: gql`
query($filter: [JobFilter!]!) { query($filter: [JobFilter!]!) {
@ -32,10 +35,10 @@
totalCoreHours totalCoreHours
} }
}`, }`,
variables: { filter, type }, variables: { filters },
pause: true
}); });
let filters;
let nameFilter = ""; let nameFilter = "";
let sorting = { field: "totalJobs", direction: "down" }; let sorting = { field: "totalJobs", direction: "down" };
@ -88,9 +91,8 @@
startTimeQuickSelect={true} startTimeQuickSelect={true}
menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up" menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up"
on:update={({ detail }) => { on:update={({ detail }) => {
$stats.variables = { filter: detail.filters }; filters = detail.filters;
$stats.context.pause = false; stats.resume();
$stats.reexecute();
}} }}
/> />
</Col> </Col>

View File

@ -81,8 +81,6 @@
$jobs.reexecute({ requestPolicy: 'network-only' }) $jobs.reexecute({ requestPolicy: 'network-only' })
} }
query(jobs)
let tableWidth = null let tableWidth = null
let jobInfoColumnWidth = 250 let jobInfoColumnWidth = 250
$: plotWidth = Math.floor((tableWidth - jobInfoColumnWidth) / metrics.length - 10) $: plotWidth = Math.floor((tableWidth - jobInfoColumnWidth) / metrics.length - 10)

View File

@ -1,7 +1,11 @@
import { expiringCacheExchange } from './cache-exchange.js' import { expiringCacheExchange } from "./cache-exchange.js";
import { Client, setContextClient, fetchExchange } from '@urql/svelte'; import {
import { setContext, getContext, hasContext, onDestroy, tick } from 'svelte' Client,
import { readable } from 'svelte/store' setContextClient,
fetchExchange,
} from "@urql/svelte";
import { setContext, getContext, hasContext, onDestroy, tick } from "svelte";
import { readable } from "svelte/store";
/* /*
* Call this function only at component initialization time! * Call this function only at component initialization time!
@ -13,28 +17,29 @@ import { readable } from 'svelte/store'
* - Adds 'clusters' to the context (object with cluster names as keys) * - Adds 'clusters' to the context (object with cluster names as keys)
* - Adds 'metrics' to the context, a function that takes a cluster and metric name and returns the MetricConfig (or undefined) * - Adds 'metrics' to the context, a function that takes a cluster and metric name and returns the MetricConfig (or undefined)
*/ */
export function init(extraInitQuery = '') { export function init(extraInitQuery = "") {
const jwt = hasContext('jwt') const jwt = hasContext("jwt")
? getContext('jwt') ? getContext("jwt")
: getContext('cc-config')['jwt'] : getContext("cc-config")["jwt"];
const client = new Client({ const client = new Client({
url: `${window.location.origin}/query`, url: `${window.location.origin}/query`,
fetchOptions: jwt != null fetchOptions:
? { headers: { 'Authorization': `Bearer ${jwt}` } } : {}, jwt != null ? { headers: { Authorization: `Bearer ${jwt}` } } : {},
exchanges: [ exchanges: [
dedupExchange, expiringCacheExchange({
expiringCacheExchange({ ttl: 5 * 60 * 1000,
ttl: 5 * 60 * 1000, maxSize: 150,
maxSize: 150, }),
}), fetchExchange,
fetchExchange ],
] });
})
setContextClient(client) setContextClient(client);
const query = client.query(`query { const query = client
.query(
`query {
clusters { clusters {
name, name,
metricConfig { metricConfig {
@ -62,227 +67,247 @@ export function init(extraInitQuery = '') {
} }
tags { id, name, type } tags { id, name, type }
${extraInitQuery} ${extraInitQuery}
}`).toPromise() }`
)
.toPromise();
let state = { fetching: true, error: null, data: null } let state = { fetching: true, error: null, data: null };
let subscribers = [] let subscribers = [];
const subscribe = (callback) => { const subscribe = (callback) => {
callback(state) callback(state);
subscribers.push(callback) subscribers.push(callback);
return () => { return () => {
subscribers = subscribers.filter(cb => cb != callback) subscribers = subscribers.filter((cb) => cb != callback);
}
}; };
};
const tags = [], clusters = [] const tags = [],
setContext('tags', tags) clusters = [];
setContext('clusters', clusters) setContext("tags", tags);
setContext('metrics', (cluster, metric) => { setContext("clusters", clusters);
if (typeof cluster !== 'object') setContext("metrics", (cluster, metric) => {
cluster = clusters.find(c => c.name == cluster) if (typeof cluster !== "object")
cluster = clusters.find((c) => c.name == cluster);
return cluster.metricConfig.find(m => m.name == metric) return cluster.metricConfig.find((m) => m.name == metric);
}) });
setContext('on-init', callback => state.fetching setContext("on-init", (callback) =>
? subscribers.push(callback) state.fetching ? subscribers.push(callback) : callback(state)
: callback(state)) );
setContext('initialized', readable(false, (set) => setContext(
subscribers.push(() => set(true)))) "initialized",
readable(false, (set) => subscribers.push(() => set(true)))
);
query.then(({ error, data }) => { query.then(({ error, data }) => {
state.fetching = false state.fetching = false;
if (error != null) { if (error != null) {
console.error(error) console.error(error);
state.error = error state.error = error;
tick().then(() => subscribers.forEach(cb => cb(state))) tick().then(() => subscribers.forEach((cb) => cb(state)));
return return;
}
for (let tag of data.tags)
tags.push(tag)
for (let cluster of data.clusters)
clusters.push(cluster)
state.data = data
tick().then(() => subscribers.forEach(cb => cb(state)))
})
return {
query: { subscribe },
tags,
clusters,
} }
for (let tag of data.tags) tags.push(tag);
for (let cluster of data.clusters) clusters.push(cluster);
state.data = data;
tick().then(() => subscribers.forEach((cb) => cb(state)));
});
return {
query: { subscribe },
tags,
clusters,
};
} }
export function formatNumber(x) { export function formatNumber(x) {
let suffix = '' let suffix = "";
if (x >= 1000000000) { if (x >= 1000000000) {
x /= 1000000 x /= 1000000;
suffix = 'G' suffix = "G";
} else if (x >= 1000000) { } else if (x >= 1000000) {
x /= 1000000 x /= 1000000;
suffix = 'M' suffix = "M";
} else if (x >= 1000) { } else if (x >= 1000) {
x /= 1000 x /= 1000;
suffix = 'k' suffix = "k";
} }
return `${(Math.round(x * 100) / 100)} ${suffix}` return `${Math.round(x * 100) / 100} ${suffix}`;
} }
// Use https://developer.mozilla.org/en-US/docs/Web/API/structuredClone instead? // Use https://developer.mozilla.org/en-US/docs/Web/API/structuredClone instead?
export function deepCopy(x) { export function deepCopy(x) {
return JSON.parse(JSON.stringify(x)) return JSON.parse(JSON.stringify(x));
} }
function fuzzyMatch(term, string) { function fuzzyMatch(term, string) {
return string.toLowerCase().includes(term) return string.toLowerCase().includes(term);
} }
export function fuzzySearchTags(term, tags) { export function fuzzySearchTags(term, tags) {
if (!tags) if (!tags) return [];
return []
let results = [] let results = [];
let termparts = term.split(':').map(s => s.trim()).filter(s => s.length > 0) let termparts = term
.split(":")
.map((s) => s.trim())
.filter((s) => s.length > 0);
if (termparts.length == 0) { if (termparts.length == 0) {
results = tags.slice() results = tags.slice();
} else if (termparts.length == 1) { } else if (termparts.length == 1) {
for (let tag of tags) for (let tag of tags)
if (fuzzyMatch(termparts[0], tag.type) if (
|| fuzzyMatch(termparts[0], tag.name)) fuzzyMatch(termparts[0], tag.type) ||
results.push(tag) fuzzyMatch(termparts[0], tag.name)
} else if (termparts.length == 2) { )
for (let tag of tags) results.push(tag);
if (fuzzyMatch(termparts[0], tag.type) } else if (termparts.length == 2) {
&& fuzzyMatch(termparts[1], tag.name)) for (let tag of tags)
results.push(tag) if (
} fuzzyMatch(termparts[0], tag.type) &&
fuzzyMatch(termparts[1], tag.name)
)
results.push(tag);
}
return results.sort((a, b) => { return results.sort((a, b) => {
if (a.type < b.type) return -1 if (a.type < b.type) return -1;
if (a.type > b.type) return 1 if (a.type > b.type) return 1;
if (a.name < b.name) return -1 if (a.name < b.name) return -1;
if (a.name > b.name) return 1 if (a.name > b.name) return 1;
return 0 return 0;
}) });
} }
export function groupByScope(jobMetrics) { export function groupByScope(jobMetrics) {
let metrics = new Map() let metrics = new Map();
for (let metric of jobMetrics) { for (let metric of jobMetrics) {
if (metrics.has(metric.name)) if (metrics.has(metric.name)) metrics.get(metric.name).push(metric);
metrics.get(metric.name).push(metric) else metrics.set(metric.name, [metric]);
else }
metrics.set(metric.name, [metric])
}
return [...metrics.values()].sort((a, b) => a[0].name.localeCompare(b[0].name)) return [...metrics.values()].sort((a, b) =>
a[0].name.localeCompare(b[0].name)
);
} }
const scopeGranularity = { const scopeGranularity = {
"node": 10, node: 10,
"socket": 5, socket: 5,
"accelerator": 5, accelerator: 5,
"core": 2, core: 2,
"hwthread": 1 hwthread: 1,
}; };
export function maxScope(scopes) { export function maxScope(scopes) {
console.assert(scopes.length > 0 && scopes.every(x => scopeGranularity[x] != null)) console.assert(
let sm = scopes[0], gran = scopeGranularity[scopes[0]] scopes.length > 0 && scopes.every((x) => scopeGranularity[x] != null)
for (let scope of scopes) { );
let otherGran = scopeGranularity[scope] let sm = scopes[0],
if (otherGran > gran) { gran = scopeGranularity[scopes[0]];
sm = scope for (let scope of scopes) {
gran = otherGran let otherGran = scopeGranularity[scope];
} if (otherGran > gran) {
sm = scope;
gran = otherGran;
} }
return sm }
return sm;
} }
export function minScope(scopes) { export function minScope(scopes) {
console.assert(scopes.length > 0 && scopes.every(x => scopeGranularity[x] != null)) console.assert(
let sm = scopes[0], gran = scopeGranularity[scopes[0]] scopes.length > 0 && scopes.every((x) => scopeGranularity[x] != null)
for (let scope of scopes) { );
let otherGran = scopeGranularity[scope] let sm = scopes[0],
if (otherGran < gran) { gran = scopeGranularity[scopes[0]];
sm = scope for (let scope of scopes) {
gran = otherGran let otherGran = scopeGranularity[scope];
} if (otherGran < gran) {
sm = scope;
gran = otherGran;
} }
return sm }
return sm;
} }
export async function fetchMetrics(job, metrics, scopes) { export async function fetchMetrics(job, metrics, scopes) {
if (job.monitoringStatus == 0) if (job.monitoringStatus == 0) return null;
return null
let query = [] let query = [];
if (metrics != null) { if (metrics != null) {
for (let metric of metrics) { for (let metric of metrics) {
query.push(`metric=${metric}`) query.push(`metric=${metric}`);
}
} }
if (scopes != null) { }
for (let scope of scopes) { if (scopes != null) {
query.push(`scope=${scope}`) for (let scope of scopes) {
} query.push(`scope=${scope}`);
}
}
try {
let res = await fetch(
`/api/jobs/metrics/${job.id}${query.length > 0 ? "?" : ""}${query.join(
"&"
)}`
);
if (res.status != 200) {
return { error: { status: res.status, message: await res.text() } };
} }
try { return await res.json();
let res = await fetch(`/api/jobs/metrics/${job.id}${(query.length > 0) ? '?' : ''}${query.join('&')}`) } catch (e) {
if (res.status != 200) { return { error: e };
return { error: { status: res.status, message: await res.text() } } }
}
return await res.json()
} catch (e) {
return { error: e }
}
} }
export function fetchMetricsStore() { export function fetchMetricsStore() {
let set = null let set = null;
let prev = { fetching: true, error: null, data: null } let prev = { fetching: true, error: null, data: null };
return [ return [
readable(prev, (_set) => { set = _set }), readable(prev, (_set) => {
(job, metrics, scopes) => fetchMetrics(job, metrics, scopes).then(res => { set = _set;
let next = { fetching: false, error: res.error, data: res.data } }),
if (prev.data && next.data) (job, metrics, scopes) =>
next.data.jobMetrics.push(...prev.data.jobMetrics) fetchMetrics(job, metrics, scopes).then((res) => {
let next = { fetching: false, error: res.error, data: res.data };
if (prev.data && next.data)
next.data.jobMetrics.push(...prev.data.jobMetrics);
prev = next prev = next;
set(next) set(next);
}) }),
] ];
} }
export function stickyHeader(datatableHeaderSelector, updatePading) { export function stickyHeader(datatableHeaderSelector, updatePading) {
const header = document.querySelector('header > nav.navbar') const header = document.querySelector("header > nav.navbar");
if (!header) if (!header) return;
return
let ticking = false, datatableHeader = null let ticking = false,
const onscroll = event => { datatableHeader = null;
if (ticking) const onscroll = (event) => {
return if (ticking) return;
ticking = true ticking = true;
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
ticking = false ticking = false;
if (!datatableHeader) if (!datatableHeader)
datatableHeader = document.querySelector(datatableHeaderSelector) datatableHeader = document.querySelector(datatableHeaderSelector);
const top = datatableHeader.getBoundingClientRect().top const top = datatableHeader.getBoundingClientRect().top;
updatePading(top < header.clientHeight updatePading(
? (header.clientHeight - top) + 10 top < header.clientHeight ? header.clientHeight - top + 10 : 10
: 10) );
}) });
} };
document.addEventListener('scroll', onscroll) document.addEventListener("scroll", onscroll);
onDestroy(() => document.removeEventListener('scroll', onscroll)) onDestroy(() => document.removeEventListener("scroll", onscroll));
} }