cc-backend/web/frontend/src/utils.js

316 lines
8.7 KiB
JavaScript
Raw Normal View History

2023-05-04 09:19:43 +02:00
import { expiringCacheExchange } from "./cache-exchange.js";
import {
2023-05-05 10:07:12 +02:00
Client,
setContextClient,
fetchExchange,
2023-05-04 09:19:43 +02:00
} from "@urql/svelte";
import { setContext, getContext, hasContext, onDestroy, tick } from "svelte";
import { readable } from "svelte/store";
2022-06-22 11:20:57 +02:00
/*
* Call this function only at component initialization time!
*
* It does several things:
* - Initialize the GraphQL client
* - Creates a readable store 'initialization' which indicates when the values below can be used.
2022-06-22 11:20:57 +02:00
* - Adds 'tags' to the context (list of all tags)
* - 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)
*/
2023-05-04 09:19:43 +02:00
export function init(extraInitQuery = "") {
2023-05-05 10:07:12 +02:00
const jwt = hasContext("jwt")
? getContext("jwt")
: getContext("cc-config")["jwt"];
const client = new Client({
url: `${window.location.origin}/query`,
fetchOptions:
jwt != null ? { headers: { Authorization: `Bearer ${jwt}` } } : {},
exchanges: [
expiringCacheExchange({
ttl: 5 * 60 * 1000,
maxSize: 150,
}),
fetchExchange,
],
});
setContextClient(client);
const query = client
.query(
`query {
2022-06-22 11:20:57 +02:00
clusters {
name,
metricConfig {
name, unit { base, prefix }, peak,
2022-06-22 11:20:57 +02:00
normal, caution, alert,
timestep, scope,
aggregation,
subClusters { name, peak, normal, caution, alert, remove }
2022-06-22 11:20:57 +02:00
}
partitions
subClusters {
name, processorType
socketsPerNode
coresPerSocket
threadsPerCore
flopRateScalar { unit { base, prefix }, value }
flopRateSimd { unit { base, prefix }, value }
memoryBandwidth { unit { base, prefix }, value }
2022-06-22 11:20:57 +02:00
numberOfNodes
topology {
node, socket, core
accelerators { id }
}
}
}
tags { id, name, type }
${extraInitQuery}
2023-05-04 09:19:43 +02:00
}`
2023-05-05 10:07:12 +02:00
)
.toPromise();
let state = { fetching: true, error: null, data: null };
let subscribers = [];
const subscribe = (callback) => {
callback(state);
subscribers.push(callback);
return () => {
subscribers = subscribers.filter((cb) => cb != callback);
};
2022-06-22 11:20:57 +02:00
};
2023-05-05 10:07:12 +02:00
const tags = [],
clusters = [];
setContext("tags", tags);
setContext("clusters", clusters);
setContext("metrics", (cluster, metric) => {
if (typeof cluster !== "object")
cluster = clusters.find((c) => c.name == cluster);
return cluster.metricConfig.find((m) => m.name == metric);
});
setContext("on-init", (callback) =>
state.fetching ? subscribers.push(callback) : callback(state)
);
setContext(
"initialized",
readable(false, (set) => subscribers.push(() => set(true)))
);
query.then(({ error, data }) => {
state.fetching = false;
if (error != null) {
console.error(error);
state.error = error;
tick().then(() => subscribers.forEach((cb) => cb(state)));
return;
}
for (let tag of data.tags) tags.push(tag);
2023-05-05 10:07:12 +02:00
for (let cluster of data.clusters) clusters.push(cluster);
2022-06-22 11:20:57 +02:00
2023-05-05 10:07:12 +02:00
state.data = data;
tick().then(() => subscribers.forEach((cb) => cb(state)));
});
2022-06-22 11:20:57 +02:00
2023-05-05 10:07:12 +02:00
return {
query: { subscribe },
tags,
clusters,
};
2022-06-22 11:20:57 +02:00
}
// Use https://developer.mozilla.org/en-US/docs/Web/API/structuredClone instead?
export function deepCopy(x) {
2023-05-05 10:07:12 +02:00
return JSON.parse(JSON.stringify(x));
2022-06-22 11:20:57 +02:00
}
function fuzzyMatch(term, string) {
2023-05-05 10:07:12 +02:00
return string.toLowerCase().includes(term);
2022-06-22 11:20:57 +02:00
}
export function fuzzySearchTags(term, tags) {
2023-05-05 10:07:12 +02:00
if (!tags) return [];
let results = [];
let termparts = term
.split(":")
.map((s) => s.trim())
.filter((s) => s.length > 0);
if (termparts.length == 0) {
results = tags.slice();
} else if (termparts.length == 1) {
for (let tag of tags)
if (
fuzzyMatch(termparts[0], tag.type) ||
fuzzyMatch(termparts[0], tag.name)
)
results.push(tag);
} else if (termparts.length == 2) {
for (let tag of tags)
if (
fuzzyMatch(termparts[0], tag.type) &&
fuzzyMatch(termparts[1], tag.name)
)
results.push(tag);
}
return results.sort((a, b) => {
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;
return 0;
});
2022-06-22 11:20:57 +02:00
}
export function groupByScope(jobMetrics) {
2023-05-05 10:07:12 +02:00
let metrics = new Map();
for (let metric of jobMetrics) {
if (metrics.has(metric.name)) metrics.get(metric.name).push(metric);
else metrics.set(metric.name, [metric]);
}
return [...metrics.values()].sort((a, b) =>
a[0].name.localeCompare(b[0].name)
);
2022-06-22 11:20:57 +02:00
}
const scopeGranularity = {
2023-05-05 10:07:12 +02:00
node: 10,
socket: 5,
memorydomain: 4,
core: 3,
hwthread: 2,
accelerator: 1
2022-06-22 11:20:57 +02:00
};
export function maxScope(scopes) {
2023-05-05 10:07:12 +02:00
console.assert(
scopes.length > 0 && scopes.every((x) => scopeGranularity[x] != null)
);
let sm = scopes[0],
gran = scopeGranularity[scopes[0]];
for (let scope of scopes) {
let otherGran = scopeGranularity[scope];
if (otherGran > gran) {
sm = scope;
gran = otherGran;
}
2022-06-22 11:20:57 +02:00
}
2023-05-05 10:07:12 +02:00
return sm;
2022-06-22 11:20:57 +02:00
}
export function minScope(scopes) {
2023-05-05 10:07:12 +02:00
console.assert(
scopes.length > 0 && scopes.every((x) => scopeGranularity[x] != null)
);
let sm = scopes[0],
gran = scopeGranularity[scopes[0]];
for (let scope of scopes) {
let otherGran = scopeGranularity[scope];
if (otherGran < gran) {
sm = scope;
gran = otherGran;
}
2022-06-22 11:20:57 +02:00
}
2023-05-05 10:07:12 +02:00
return sm;
2022-06-22 11:20:57 +02:00
}
export async function fetchMetrics(job, metrics, scopes) {
2023-05-05 10:07:12 +02:00
if (job.monitoringStatus == 0) return null;
2022-06-22 11:20:57 +02:00
2023-05-05 10:07:12 +02:00
let query = [];
if (metrics != null) {
for (let metric of metrics) {
query.push(`metric=${metric}`);
}
2022-06-22 11:20:57 +02:00
}
2023-05-05 10:07:12 +02:00
if (scopes != null) {
for (let scope of scopes) {
query.push(`scope=${scope}`);
}
2022-06-22 11:20:57 +02:00
}
2023-05-04 09:19:43 +02:00
2023-05-05 10:07:12 +02:00
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() } };
}
2023-05-04 09:19:43 +02:00
2023-05-05 10:07:12 +02:00
return await res.json();
} catch (e) {
return { error: e };
}
2022-06-22 11:20:57 +02:00
}
export function fetchMetricsStore() {
2023-05-05 10:07:12 +02:00
let set = null;
let prev = { fetching: true, error: null, data: null };
return [
readable(prev, (_set) => {
set = _set;
}),
(job, metrics, scopes) =>
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;
set(next);
}),
];
2022-06-22 11:20:57 +02:00
}
export function stickyHeader(datatableHeaderSelector, updatePading) {
2023-05-05 10:07:12 +02:00
const header = document.querySelector("header > nav.navbar");
if (!header) return;
let ticking = false,
datatableHeader = null;
const onscroll = (event) => {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(() => {
ticking = false;
if (!datatableHeader)
datatableHeader = document.querySelector(datatableHeaderSelector);
const top = datatableHeader.getBoundingClientRect().top;
updatePading(
top < header.clientHeight ? header.clientHeight - top + 10 : 10
);
});
};
2023-05-04 09:19:43 +02:00
2023-05-05 10:07:12 +02:00
document.addEventListener("scroll", onscroll);
onDestroy(() => document.removeEventListener("scroll", onscroll));
2022-06-22 11:20:57 +02:00
}
export function checkMetricDisabled(m, c, s) { //[m]etric, [c]luster, [s]ubcluster
const mc = getContext("metrics");
const thisConfig = mc(c, m);
let thisSCIndex = -1;
if (thisConfig) {
thisSCIndex = thisConfig.subClusters.findIndex(
(subcluster) => subcluster.name == s
);
};
if (thisSCIndex >= 0) {
if (thisConfig.subClusters[thisSCIndex].remove == true) {
return true;
}
}
return false;
}