Merge pull request #306 from ClusterCockpit/dev

fix: fix job list render for continuous mode on filter or sort changes
This commit is contained in:
Jan Eitzinger 2024-12-03 07:47:46 +01:00 committed by GitHub
commit 9396e7492c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 516 additions and 439 deletions

View File

@ -32,7 +32,7 @@ var Keys schema.ProgramConfig = schema.ProgramConfig{
"job_view_polarPlotMetrics": []string{"flops_any", "mem_bw", "mem_used"}, "job_view_polarPlotMetrics": []string{"flops_any", "mem_bw", "mem_used"},
"job_view_selectedMetrics": []string{"flops_any", "mem_bw", "mem_used"}, "job_view_selectedMetrics": []string{"flops_any", "mem_bw", "mem_used"},
"job_view_showFootprint": true, "job_view_showFootprint": true,
"job_list_usePaging": true, "job_list_usePaging": false,
"plot_general_colorBackground": true, "plot_general_colorBackground": true,
"plot_general_colorscheme": []string{"#00bfff", "#0000ff", "#ff00ff", "#ff0000", "#ff8000", "#ffff00", "#80ff00"}, "plot_general_colorscheme": []string{"#00bfff", "#0000ff", "#ff00ff", "#ff0000", "#ff8000", "#ffff00", "#80ff00"},
"plot_general_lineWidth": 3, "plot_general_lineWidth": 3,

View File

@ -91,7 +91,7 @@ type ResampleConfig struct {
type CronFrequency struct { type CronFrequency struct {
// Duration Update Worker [Defaults to '5m'] // Duration Update Worker [Defaults to '5m']
DurationWorker string `json:"duration-worker"` DurationWorker string `json:"duration-worker"`
// Metric- and Energy Footprint Update Worker [Defaults to '10m'] // Metric-Footprint Update Worker [Defaults to '10m']
FootprintWorker string `json:"footprint-worker"` FootprintWorker string `json:"footprint-worker"`
} }

View File

@ -8,6 +8,10 @@
"description": "Address where the http (or https) server will listen on (for example: 'localhost:80').", "description": "Address where the http (or https) server will listen on (for example: 'localhost:80').",
"type": "string" "type": "string"
}, },
"apiAllowedIPs": {
"description": "Addresses from which secured API endpoints can be reached",
"type": "string"
},
"user": { "user": {
"description": "Drop root permissions once .env was read and the port was taken. Only applicable if using privileged port.", "description": "Drop root permissions once .env was read and the port was taken. Only applicable if using privileged port.",
"type": "string" "type": "string"
@ -40,7 +44,7 @@
"description": "For sqlite3 a filename, for mysql a DSN in this format: https://github.com/go-sql-driver/mysql#dsn-data-source-name (Without query parameters!).", "description": "For sqlite3 a filename, for mysql a DSN in this format: https://github.com/go-sql-driver/mysql#dsn-data-source-name (Without query parameters!).",
"type": "string" "type": "string"
}, },
"job-archive": { "archive": {
"description": "Configuration keys for job-archive", "description": "Configuration keys for job-archive",
"type": "object", "type": "object",
"properties": { "properties": {
@ -127,6 +131,45 @@
"description": "Do not show running jobs shorter than X seconds.", "description": "Do not show running jobs shorter than X seconds.",
"type": "integer" "type": "integer"
}, },
"emission-constant": {
"description": ".",
"type": "integer"
},
"cron-frequency": {
"description": "Frequency of cron job workers.",
"type": "object",
"properties": {
"duration-worker": {
"description": "Duration Update Worker [Defaults to '5m']",
"type": "string"
},
"footprint-worker": {
"description": "Metric-Footprint Update Worker [Defaults to '10m']",
"type": "string"
}
}
},
"enable-resampling": {
"description": "Enable dynamic zoom in frontend metric plots.",
"type": "object",
"properties": {
"trigger": {
"description": "Trigger next zoom level at less than this many visible datapoints.",
"type": "integer"
},
"resolutions": {
"description": "Array of resampling target resolutions, in seconds.",
"type": "array",
"items": {
"type": "integer"
}
}
},
"required": [
"trigger",
"resolutions"
]
},
"jwts": { "jwts": {
"description": "For JWT token authentication.", "description": "For JWT token authentication.",
"type": "object", "type": "object",
@ -156,6 +199,23 @@
"max-age" "max-age"
] ]
}, },
"oidc": {
"provider": {
"description": "",
"type": "string"
},
"syncUserOnLogin": {
"description": "",
"type": "boolean"
},
"updateUserOnLogin": {
"description": "",
"type": "boolean"
},
"required": [
"provider"
]
},
"ldap": { "ldap": {
"description": "For LDAP Authentication and user synchronisation.", "description": "For LDAP Authentication and user synchronisation.",
"type": "object", "type": "object",
@ -345,6 +405,14 @@
"description": "Initial metric shown in system view", "description": "Initial metric shown in system view",
"type": "string" "type": "string"
}, },
"job_view_showFootprint": {
"description": "Option to toggle footprint ui in single job view",
"type": "boolean"
},
"job_list_usePaging": {
"description": "Option to switch from continous scroll to paging",
"type": "boolean"
},
"analysis_view_histogramMetrics": { "analysis_view_histogramMetrics": {
"description": "Metrics to show as job count histograms in analysis view", "description": "Metrics to show as job count histograms in analysis view",
"type": "array", "type": "array",
@ -416,6 +484,8 @@
"plot_view_showRoofline", "plot_view_showRoofline",
"plot_view_showStatTable", "plot_view_showStatTable",
"system_view_selectedMetric", "system_view_selectedMetric",
"job_view_showFootprint",
"job_list_usePaging",
"analysis_view_histogramMetrics", "analysis_view_histogramMetrics",
"analysis_view_scatterPlotMetrics", "analysis_view_scatterPlotMetrics",
"job_view_nodestats_selectedMetrics", "job_view_nodestats_selectedMetrics",
@ -424,27 +494,6 @@
"plot_general_colorscheme", "plot_general_colorscheme",
"plot_list_selectedMetrics" "plot_list_selectedMetrics"
] ]
},
"enable-resampling": {
"description": "Enable dynamic zoom in frontend metric plots.",
"type": "object",
"properties": {
"trigger": {
"description": "Trigger next zoom level at less than this many visible datapoints.",
"type": "integer"
},
"resolutions": {
"description": "Array of resampling target resolutions, in seconds.",
"type": "array",
"items": {
"type": "integer"
}
}
},
"required": [
"trigger",
"resolutions"
]
} }
}, },
"required": [ "required": [

View File

@ -422,14 +422,14 @@
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td> <td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
{#if groupSelection.key == "user"} {#if groupSelection.key == "user"}
<th scope="col" <th scope="col"
><a href="/monitoring/user/{te.id}?cluster={cluster.name}" ><a href="/monitoring/user/{te.id}?cluster={cluster}"
>{te.id}</a >{te.id}</a
></th ></th
> >
{:else} {:else}
<th scope="col" <th scope="col"
><a ><a
href="/monitoring/jobs/?cluster={cluster.name}&project={te.id}&projectMatch=eq" href="/monitoring/jobs/?cluster={cluster}&project={te.id}&projectMatch=eq"
>{te.id}</a >{te.id}</a
></th ></th
> >

View File

@ -30,6 +30,10 @@
initialized = getContext("initialized"), initialized = getContext("initialized"),
globalMetrics = getContext("globalMetrics"); globalMetrics = getContext("globalMetrics");
const equalsCheck = (a, b) => {
return JSON.stringify(a) === JSON.stringify(b);
}
export let sorting = { field: "startTime", type: "col", order: "DESC" }; export let sorting = { field: "startTime", type: "col", order: "DESC" };
export let matchedJobs = 0; export let matchedJobs = 0;
export let metrics = ccconfig.plot_list_selectedMetrics; export let metrics = ccconfig.plot_list_selectedMetrics;
@ -40,6 +44,8 @@
let page = 1; let page = 1;
let paging = { itemsPerPage, page }; let paging = { itemsPerPage, page };
let filter = []; let filter = [];
let lastFilter = [];
let lastSorting = null;
let triggerMetricRefresh = false; let triggerMetricRefresh = false;
function getUnit(m) { function getUnit(m) {
@ -105,12 +111,33 @@
variables: { paging, sorting, filter }, variables: { paging, sorting, filter },
}); });
let jobs = [] $: if (!usePaging && sorting) {
// console.log('Reset Paging ...')
paging = { itemsPerPage: 10, page: 1 }
};
let jobs = [];
$: if ($initialized && $jobsStore.data) { $: if ($initialized && $jobsStore.data) {
if (usePaging) { if (usePaging) {
jobs = [...$jobsStore.data.jobs.items] jobs = [...$jobsStore.data.jobs.items]
} else { // Prevents jump to table head: Extends existing list instead of rendering new list } else { // Prevents jump to table head in continiuous mode, only if no change in sort or filter
if (equalsCheck(filter, lastFilter) && equalsCheck(sorting, lastSorting)) {
// console.log('Both Equal: Continuous Addition ... Set None')
jobs = jobs.concat([...$jobsStore.data.jobs.items]) jobs = jobs.concat([...$jobsStore.data.jobs.items])
} else if (equalsCheck(filter, lastFilter)) {
// console.log('Filter Equal: Continuous Reset ... Set lastSorting')
lastSorting = { ...sorting }
jobs = [...$jobsStore.data.jobs.items]
} else if (equalsCheck(sorting, lastSorting)) {
// console.log('Sorting Equal: Continuous Reset ... Set lastFilter')
lastFilter = [ ...filter ]
jobs = [...$jobsStore.data.jobs.items]
} else {
// console.log('None Equal: Continuous Reset ... Set lastBoth')
lastSorting = { ...sorting }
lastFilter = [ ...filter ]
jobs = [...$jobsStore.data.jobs.items]
}
} }
} }

View File

@ -84,19 +84,20 @@
if (metricConfig?.aggregation == "sum") { if (metricConfig?.aggregation == "sum") {
let divisor = 1 let divisor;
if (isShared == true) { // Shared if (isShared == true) { // Shared
if (numaccs > 0) divisor = subClusterTopology.accelerators.length / numaccs; if (numaccs > 0) divisor = subClusterTopology.accelerators.length / numaccs;
else if (numhwthreads > 0) divisor = subClusterTopology.node.length / numhwthreads; else if (numhwthreads > 0) divisor = subClusterTopology.core.length / numhwthreads;
} }
else if (scope == 'node') divisor = 1; // Use as configured for nodes
else if (scope == 'socket') divisor = subClusterTopology.socket.length; else if (scope == 'socket') divisor = subClusterTopology.socket.length;
else if (scope == "memoryDomain") divisor = subClusterTopology.memoryDomain.length;
else if (scope == "core") divisor = subClusterTopology.core.length; else if (scope == "core") divisor = subClusterTopology.core.length;
else if (scope == "accelerator") else if (scope == "hwthread") divisor = subClusterTopology.core.length; // alt. name for core
divisor = subClusterTopology.accelerators.length; else if (scope == "accelerator") divisor = subClusterTopology.accelerators.length;
else if (scope == "hwthread") divisor = subClusterTopology.node.length;
else { else {
// console.log('TODO: how to calc thresholds for ', scope) console.log('Unknown scope, return default thresholds ', scope)
return null; divisor = 1;
} }
return { return {