X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/d99707463cd5bf757322cde334c32e398abbf6f9..b936759134f8d2b1a68b19be06de8a3f41f782d8:/apps/workbench/app/assets/javascripts/log_viewer.js diff --git a/apps/workbench/app/assets/javascripts/log_viewer.js b/apps/workbench/app/assets/javascripts/log_viewer.js index fc1d93414c..b201ed7f10 100644 --- a/apps/workbench/app/assets/javascripts/log_viewer.js +++ b/apps/workbench/app/assets/javascripts/log_viewer.js @@ -1,10 +1,25 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +function newTaskState() { + return {"complete_count": 0, + "failure_count": 0, + "task_count": 0, + "incomplete_count": 0, + "nodes": []}; +} + function addToLogViewer(logViewer, lines, taskState) { var re = /((\d\d\d\d)-(\d\d)-(\d\d))_((\d\d):(\d\d):(\d\d)) ([a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}) (\d+) (\d+)? (.*)/; + + var items = []; + var count = logViewer.items.length; for (var a in lines) { var v = lines[a].match(re); if (v != null) { - var ts = new Date(Date.UTC(v[2], v[3], v[4], v[6], v[7], v[8])); + var ts = new Date(Date.UTC(v[2], v[3]-1, v[4], v[6], v[7], v[8])); v11 = v[11]; if (typeof v[11] === 'undefined') { @@ -15,7 +30,14 @@ function addToLogViewer(logViewer, lines, taskState) { var message = v[12]; var type = ""; + var node = ""; + var slot = ""; if (v11 !== "") { + if (!taskState.hasOwnProperty(v11)) { + taskState[v11] = {}; + taskState.task_count += 1; + } + if (/^stderr /.test(message)) { message = message.substr(7); if (/^crunchstat: /.test(message)) { @@ -24,66 +46,241 @@ function addToLogViewer(logViewer, lines, taskState) { } else if (/^srun: /.test(message) || /^slurmd/.test(message)) { type = "task-dispatch"; } else { - type = "task-output"; + type = "task-print"; } } else { - if (/^success in (\d+)/.test(message)) { - taskState[v11] = "success"; + var m; + if (m = /^success in (\d+) second/.exec(message)) { + taskState[v11].outcome = "success"; + taskState[v11].runtime = Number(m[1]); + taskState.complete_count += 1; + } + else if (m = /^failure \(\#\d+, (temporary|permanent)\) after (\d+) second/.exec(message)) { + taskState[v11].outcome = "failure"; + taskState[v11].runtime = Number(m[2]); + taskState.failure_count += 1; + if (m[1] == "permanent") { + taskState.incomplete_count += 1; + } } - if (/^failure \([^)]+\) (\d+)/.test(message)) { - taskState[v11] = "failure"; + else if (m = /^child \d+ started on ([^.]*)\.(\d+)/.exec(message)) { + taskState[v11].node = m[1]; + taskState[v11].slot = m[2]; + if (taskState.nodes.indexOf(m[1], 0) == -1) { + taskState.nodes.push(m[1]); + } + for (var i in items) { + if (i > 0) { + if (items[i].taskid === v11) { + items[i].node = m[1]; + items[i].slot = m[2]; + } + } + } } type = "task-dispatch"; } + node = taskState[v11].node; + slot = taskState[v11].slot; } else { - if (/^status: /.test(message)) { - type = "job-status"; - message = message.substr(8); - } else { - type = "crunch"; - } + type = "crunch"; } - logViewer.add({ - id: logViewer.items.length, + items.push({ + id: count, + ts: ts, timestamp: ts.toLocaleDateString() + " " + ts.toLocaleTimeString(), taskid: v11, - message: message, + node: node, + slot: slot, + message: message.replace(/&/g, '&').replace(//g, '>'), type: type }); - + count += 1; } else { - console.log("Did not parse: " + lines[a]); + console.log("Did not parse line " + a + ": " + lines[a]); } } - logViewer.update(); + logViewer.add(items); } -function sortByTaskThenId(a, b, opt) { +function sortById(a, b, opt) { a = a.values(); b = b.values(); - if (a["taskid"] === "" && b["taskid"] !== "") { + if (a["id"] > b["id"]) { + return 1; + } + if (a["id"] < b["id"]) { + return -1; + } + return 0; +} + +function sortByTask(a, b, opt) { + var aa = a.values(); + var bb = b.values(); + + if (aa["taskid"] === "" && bb["taskid"] !== "") { return -1; } - if (a["taskid"] !== "" && b["taskid"] === "") { + if (aa["taskid"] !== "" && bb["taskid"] === "") { return 1; } - if (a["taskid"] !== "" && b["taskid"] !== "") { - if (a["taskid"] > b["taskid"]) { + if (aa["taskid"] !== "" && bb["taskid"] !== "") { + if (aa["taskid"] > bb["taskid"]) { return 1; } - if (a["taskid"] < b["taskid"]) { + if (aa["taskid"] < bb["taskid"]) { return -1; } } - if (a["id"] > b["id"]) { + return sortById(a, b, opt); +} + +function sortByNode(a, b, opt) { + var aa = a.values(); + var bb = b.values(); + + if (aa["node"] === "" && bb["node"] !== "") { + return -1; + } + if (aa["node"] !== "" && bb["node"] === "") { return 1; } - if (a["id"] < b["id"]) { - return -1; + + if (aa["node"] !== "" && bb["node"] !== "") { + if (aa["node"] > bb["node"]) { + return 1; + } + if (aa["node"] < bb["node"]) { + return -1; + } } - return 0; + + if (aa["slot"] !== "" && bb["slot"] !== "") { + if (aa["slot"] > bb["slot"]) { + return 1; + } + if (aa["slot"] < bb["slot"]) { + return -1; + } + } + + return sortById(a, b, opt); +} + + +function dumbPluralize(n, s, p) { + if (typeof p === 'undefined') { + p = "s"; + } + if (n == 0 || n > 1) { + return n + " " + (s + p); + } else { + return n + " " + s; + } +} + +function generateJobOverview(id, logViewer, taskState) { + var html = ""; + + if (logViewer.items.length > 2) { + var first = logViewer.items[1]; + var last = logViewer.items[logViewer.items.length-1]; + var duration = (last.values().ts.getTime() - first.values().ts.getTime()) / 1000; + + var hours = 0; + var minutes = 0; + var seconds; + + if (duration >= 3600) { + hours = Math.floor(duration / 3600); + duration -= (hours * 3600); + } + if (duration >= 60) { + minutes = Math.floor(duration / 60); + duration -= (minutes * 60); + } + seconds = duration; + + var tcount = taskState.task_count; + + html += "

"; + html += "Started at " + first.values().timestamp + ". "; + html += "Ran " + dumbPluralize(tcount, " task") + " over "; + if (hours > 0) { + html += dumbPluralize(hours, " hour"); + } + if (minutes > 0) { + html += " " + dumbPluralize(minutes, " minute"); + } + if (seconds > 0) { + html += " " + dumbPluralize(seconds, " second"); + } + + html += " using " + dumbPluralize(taskState.nodes.length, " node"); + + html += ". " + dumbPluralize(taskState.complete_count, "task") + " completed"; + html += ", " + dumbPluralize(taskState.incomplete_count, "task") + " incomplete"; + html += " (" + dumbPluralize(taskState.failure_count, " failure") + ")"; + + html += ". Finished at " + last.values().timestamp + "."; + html += "

"; + } else { + html = "

Job log is empty or failed to load.

"; + } + + $(id).html(html); +} + +function gotoPage(n, logViewer, page, id) { + if (n < 0) { return; } + if (n*page > logViewer.matchingItems.length) { return; } + logViewer.page_offset = n; + logViewer.show(n*page, page); +} + +function updatePaging(id, logViewer, page) { + var p = ""; + var i = logViewer.matchingItems.length; + var n; + for (n = 0; (n*page) < i; n += 1) { + if (n == logViewer.page_offset) { + p += "" + (n+1) + " "; + } else { + p += "" + (n+1) + " "; + } + } + $(id).html(p); + for (n = 0; (n*page) < i; n += 1) { + (function(n) { + $(".log-viewer-page-" + n).on("click", function() { + gotoPage(n, logViewer, page, id); + return false; + }); + })(n); + } + + if (logViewer.page_offset == 0) { + $(".log-viewer-page-up").addClass("text-muted"); + } else { + $(".log-viewer-page-up").removeClass("text-muted"); + } + + if (logViewer.page_offset == (n-1)) { + $(".log-viewer-page-down").addClass("text-muted"); + } else { + $(".log-viewer-page-down").removeClass("text-muted"); + } +} + +function nextPage(logViewer, page, id) { + gotoPage(logViewer.page_offset+1, logViewer, page, id); +} + +function prevPage(logViewer, page, id) { + gotoPage(logViewer.page_offset-1, logViewer, page, id); }