X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/0dffc025de9ebc7a54596e1018a589a6f5b8a03e..35568d227889ca85026c647d57144fee1306bc87:/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 e152c6f854..b201ed7f10 100644 --- a/apps/workbench/app/assets/javascripts/log_viewer.js +++ b/apps/workbench/app/assets/javascripts/log_viewer.js @@ -1,56 +1,286 @@ -function addToLogViewer(logViewer, lines) { +// 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') { - v11 = ' '; + v11 = ""; + } else { + v11 = Number(v11); } var message = v[12]; var type = ""; - if (v11 != ' ') { + var node = ""; + var slot = ""; + if (v11 !== "") { + if (!taskState.hasOwnProperty(v11)) { + taskState[v11] = {}; + taskState.task_count += 1; + } + if (/^stderr /.test(message)) { - if (/^stderr crunchstat: /.test(message)) { + message = message.substr(7); + if (/^crunchstat: /.test(message)) { type = "crunchstat"; - message = message.substr(19); - } else if (/^stderr srun: /.test(message)) { - type = "task-dispatch"; - message = message.substr(7); - } else if (/^stderr slurmd/.test(message)) { + message = message.substr(12); + } else if (/^srun: /.test(message) || /^slurmd/.test(message)) { type = "task-dispatch"; - message = message.substr(7); } else { - type = "task-output"; - message = message.substr(7); + type = "task-print"; } } else { + 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; + } + } + 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 line " + a + ": " + lines[a]); + } + } + logViewer.add(items); +} + +function sortById(a, b, opt) { + a = a.values(); + b = b.values(); + + 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 (aa["taskid"] !== "" && bb["taskid"] === "") { + return 1; + } + + if (aa["taskid"] !== "" && bb["taskid"] !== "") { + if (aa["taskid"] > bb["taskid"]) { + return 1; + } + if (aa["taskid"] < bb["taskid"]) { + return -1; + } + } + + 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 (aa["node"] !== "" && bb["node"] !== "") { + if (aa["node"] > bb["node"]) { + return 1; + } + if (aa["node"] < bb["node"]) { + return -1; + } + } + 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 { - console.log("Did not parse: " + lines[a]); + p += "" + (n+1) + " "; } } - logViewer.update(); + $(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); }