1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 function newTaskState() {
6 return {"complete_count": 0,
13 function addToLogViewer(logViewer, lines, taskState) {
14 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+)? (.*)/;
17 var count = logViewer.items.length;
18 for (var a in lines) {
19 var v = lines[a].match(re);
22 var ts = new Date(Date.UTC(v[2], v[3]-1, v[4], v[6], v[7], v[8]));
25 if (typeof v[11] === 'undefined') {
36 if (!taskState.hasOwnProperty(v11)) {
38 taskState.task_count += 1;
41 if (/^stderr /.test(message)) {
42 message = message.substr(7);
43 if (/^crunchstat: /.test(message)) {
45 message = message.substr(12);
46 } else if (/^srun: /.test(message) || /^slurmd/.test(message)) {
47 type = "task-dispatch";
53 if (m = /^success in (\d+) second/.exec(message)) {
54 taskState[v11].outcome = "success";
55 taskState[v11].runtime = Number(m[1]);
56 taskState.complete_count += 1;
58 else if (m = /^failure \(\#\d+, (temporary|permanent)\) after (\d+) second/.exec(message)) {
59 taskState[v11].outcome = "failure";
60 taskState[v11].runtime = Number(m[2]);
61 taskState.failure_count += 1;
62 if (m[1] == "permanent") {
63 taskState.incomplete_count += 1;
66 else if (m = /^child \d+ started on ([^.]*)\.(\d+)/.exec(message)) {
67 taskState[v11].node = m[1];
68 taskState[v11].slot = m[2];
69 if (taskState.nodes.indexOf(m[1], 0) == -1) {
70 taskState.nodes.push(m[1]);
72 for (var i in items) {
74 if (items[i].taskid === v11) {
81 type = "task-dispatch";
83 node = taskState[v11].node;
84 slot = taskState[v11].slot;
92 timestamp: ts.toLocaleDateString() + " " + ts.toLocaleTimeString(),
96 message: message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'),
101 console.log("Did not parse line " + a + ": " + lines[a]);
104 logViewer.add(items);
107 function sortById(a, b, opt) {
111 if (a["id"] > b["id"]) {
114 if (a["id"] < b["id"]) {
120 function sortByTask(a, b, opt) {
124 if (aa["taskid"] === "" && bb["taskid"] !== "") {
127 if (aa["taskid"] !== "" && bb["taskid"] === "") {
131 if (aa["taskid"] !== "" && bb["taskid"] !== "") {
132 if (aa["taskid"] > bb["taskid"]) {
135 if (aa["taskid"] < bb["taskid"]) {
140 return sortById(a, b, opt);
143 function sortByNode(a, b, opt) {
147 if (aa["node"] === "" && bb["node"] !== "") {
150 if (aa["node"] !== "" && bb["node"] === "") {
154 if (aa["node"] !== "" && bb["node"] !== "") {
155 if (aa["node"] > bb["node"]) {
158 if (aa["node"] < bb["node"]) {
163 if (aa["slot"] !== "" && bb["slot"] !== "") {
164 if (aa["slot"] > bb["slot"]) {
167 if (aa["slot"] < bb["slot"]) {
172 return sortById(a, b, opt);
176 function dumbPluralize(n, s, p) {
177 if (typeof p === 'undefined') {
180 if (n == 0 || n > 1) {
181 return n + " " + (s + p);
187 function generateJobOverview(id, logViewer, taskState) {
190 if (logViewer.items.length > 2) {
191 var first = logViewer.items[1];
192 var last = logViewer.items[logViewer.items.length-1];
193 var duration = (last.values().ts.getTime() - first.values().ts.getTime()) / 1000;
199 if (duration >= 3600) {
200 hours = Math.floor(duration / 3600);
201 duration -= (hours * 3600);
203 if (duration >= 60) {
204 minutes = Math.floor(duration / 60);
205 duration -= (minutes * 60);
209 var tcount = taskState.task_count;
212 html += "Started at " + first.values().timestamp + ". ";
213 html += "Ran " + dumbPluralize(tcount, " task") + " over ";
215 html += dumbPluralize(hours, " hour");
218 html += " " + dumbPluralize(minutes, " minute");
221 html += " " + dumbPluralize(seconds, " second");
224 html += " using " + dumbPluralize(taskState.nodes.length, " node");
226 html += ". " + dumbPluralize(taskState.complete_count, "task") + " completed";
227 html += ", " + dumbPluralize(taskState.incomplete_count, "task") + " incomplete";
228 html += " (" + dumbPluralize(taskState.failure_count, " failure") + ")";
230 html += ". Finished at " + last.values().timestamp + ".";
233 html = "<p>Job log is empty or failed to load.</p>";
239 function gotoPage(n, logViewer, page, id) {
240 if (n < 0) { return; }
241 if (n*page > logViewer.matchingItems.length) { return; }
242 logViewer.page_offset = n;
243 logViewer.show(n*page, page);
246 function updatePaging(id, logViewer, page) {
248 var i = logViewer.matchingItems.length;
250 for (n = 0; (n*page) < i; n += 1) {
251 if (n == logViewer.page_offset) {
252 p += "<span class='log-viewer-page-num'>" + (n+1) + "</span> ";
254 p += "<a href=\"#\" class='log-viewer-page-num log-viewer-page-" + n + "'>" + (n+1) + "</a> ";
258 for (n = 0; (n*page) < i; n += 1) {
260 $(".log-viewer-page-" + n).on("click", function() {
261 gotoPage(n, logViewer, page, id);
267 if (logViewer.page_offset == 0) {
268 $(".log-viewer-page-up").addClass("text-muted");
270 $(".log-viewer-page-up").removeClass("text-muted");
273 if (logViewer.page_offset == (n-1)) {
274 $(".log-viewer-page-down").addClass("text-muted");
276 $(".log-viewer-page-down").removeClass("text-muted");
280 function nextPage(logViewer, page, id) {
281 gotoPage(logViewer.page_offset+1, logViewer, page, id);
284 function prevPage(logViewer, page, id) {
285 gotoPage(logViewer.page_offset-1, logViewer, page, id);