Fix 2.4.2 upgrade notes formatting refs #19330
[arvados.git] / apps / workbench / app / assets / javascripts / log_viewer.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 function newTaskState() {
6     return {"complete_count": 0,
7             "failure_count": 0,
8             "task_count": 0,
9             "incomplete_count": 0,
10             "nodes": []};
11 }
12
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+)? (.*)/;
15
16     var items = [];
17     var count = logViewer.items.length;
18     for (var a in lines) {
19         var v = lines[a].match(re);
20         if (v != null) {
21
22             var ts = new Date(Date.UTC(v[2], v[3]-1, v[4], v[6], v[7], v[8]));
23
24             v11 = v[11];
25             if (typeof v[11] === 'undefined') {
26                 v11 = "";
27             } else {
28                 v11 = Number(v11);
29             }
30
31             var message = v[12];
32             var type = "";
33             var node = "";
34             var slot = "";
35             if (v11 !== "") {
36                 if (!taskState.hasOwnProperty(v11)) {
37                     taskState[v11] = {};
38                     taskState.task_count += 1;
39                 }
40
41                 if (/^stderr /.test(message)) {
42                     message = message.substr(7);
43                     if (/^crunchstat: /.test(message)) {
44                         type = "crunchstat";
45                         message = message.substr(12);
46                     } else if (/^srun: /.test(message) || /^slurmd/.test(message)) {
47                         type = "task-dispatch";
48                     } else {
49                         type = "task-print";
50                     }
51                 } else {
52                     var m;
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;
57                     }
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;
64                         }
65                     }
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]);
71                         }
72                         for (var i in items) {
73                             if (i > 0) {
74                                 if (items[i].taskid === v11) {
75                                     items[i].node = m[1];
76                                     items[i].slot = m[2];
77                                 }
78                             }
79                         }
80                     }
81                     type = "task-dispatch";
82                 }
83                 node = taskState[v11].node;
84                 slot = taskState[v11].slot;
85             } else {
86                 type = "crunch";
87             }
88
89             items.push({
90                 id: count,
91                 ts: ts,
92                 timestamp: ts.toLocaleDateString() + " " + ts.toLocaleTimeString(),
93                 taskid: v11,
94                 node: node,
95                 slot: slot,
96                 message: message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'),
97                 type: type
98             });
99             count += 1;
100         } else {
101             console.log("Did not parse line " + a + ": " + lines[a]);
102         }
103     }
104     logViewer.add(items);
105 }
106
107 function sortById(a, b, opt) {
108     a = a.values();
109     b = b.values();
110
111     if (a["id"] > b["id"]) {
112         return 1;
113     }
114     if (a["id"] < b["id"]) {
115         return -1;
116     }
117     return 0;
118 }
119
120 function sortByTask(a, b, opt) {
121     var aa = a.values();
122     var bb = b.values();
123
124     if (aa["taskid"] === "" && bb["taskid"] !== "") {
125         return -1;
126     }
127     if (aa["taskid"] !== "" && bb["taskid"] === "") {
128         return 1;
129     }
130
131     if (aa["taskid"] !== "" && bb["taskid"] !== "") {
132         if (aa["taskid"] > bb["taskid"]) {
133             return 1;
134         }
135         if (aa["taskid"] < bb["taskid"]) {
136             return -1;
137         }
138     }
139
140     return sortById(a, b, opt);
141 }
142
143 function sortByNode(a, b, opt) {
144     var aa = a.values();
145     var bb = b.values();
146
147     if (aa["node"] === "" && bb["node"] !== "") {
148         return -1;
149     }
150     if (aa["node"] !== "" && bb["node"] === "") {
151         return 1;
152     }
153
154     if (aa["node"] !== "" && bb["node"] !== "") {
155         if (aa["node"] > bb["node"]) {
156             return 1;
157         }
158         if (aa["node"] < bb["node"]) {
159             return -1;
160         }
161     }
162
163     if (aa["slot"] !== "" && bb["slot"] !== "") {
164         if (aa["slot"] > bb["slot"]) {
165             return 1;
166         }
167         if (aa["slot"] < bb["slot"]) {
168             return -1;
169         }
170     }
171
172     return sortById(a, b, opt);
173 }
174
175
176 function dumbPluralize(n, s, p) {
177     if (typeof p === 'undefined') {
178         p = "s";
179     }
180     if (n == 0 || n > 1) {
181         return n + " " + (s + p);
182     } else {
183         return n + " " + s;
184     }
185 }
186
187 function generateJobOverview(id, logViewer, taskState) {
188     var html = "";
189
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;
194
195         var hours = 0;
196         var minutes = 0;
197         var seconds;
198
199         if (duration >= 3600) {
200             hours = Math.floor(duration / 3600);
201             duration -= (hours * 3600);
202         }
203         if (duration >= 60) {
204             minutes = Math.floor(duration / 60);
205             duration -= (minutes * 60);
206         }
207         seconds = duration;
208
209         var tcount = taskState.task_count;
210
211         html += "<p>";
212         html += "Started at " + first.values().timestamp + ".  ";
213         html += "Ran " + dumbPluralize(tcount, " task") + " over ";
214         if (hours > 0) {
215             html += dumbPluralize(hours, " hour");
216         }
217         if (minutes > 0) {
218             html += " " + dumbPluralize(minutes, " minute");
219         }
220         if (seconds > 0) {
221             html += " " + dumbPluralize(seconds, " second");
222         }
223
224         html += " using " + dumbPluralize(taskState.nodes.length, " node");
225
226         html += ".  " + dumbPluralize(taskState.complete_count, "task") + " completed";
227         html += ",  " + dumbPluralize(taskState.incomplete_count, "task") +  " incomplete";
228         html += " (" + dumbPluralize(taskState.failure_count, " failure") + ")";
229
230         html += ".  Finished at " + last.values().timestamp + ".";
231         html += "</p>";
232     } else {
233        html = "<p>Job log is empty or failed to load.</p>";
234     }
235
236     $(id).html(html);
237 }
238
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);
244 }
245
246 function updatePaging(id, logViewer, page) {
247     var p = "";
248     var i = logViewer.matchingItems.length;
249     var n;
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> ";
253         } else {
254             p += "<a href=\"#\" class='log-viewer-page-num log-viewer-page-" + n + "'>" + (n+1) + "</a> ";
255         }
256     }
257     $(id).html(p);
258     for (n = 0; (n*page) < i; n += 1) {
259         (function(n) {
260             $(".log-viewer-page-" + n).on("click", function() {
261                 gotoPage(n, logViewer, page, id);
262                 return false;
263             });
264         })(n);
265     }
266
267     if (logViewer.page_offset == 0) {
268         $(".log-viewer-page-up").addClass("text-muted");
269     } else {
270         $(".log-viewer-page-up").removeClass("text-muted");
271     }
272
273     if (logViewer.page_offset == (n-1)) {
274         $(".log-viewer-page-down").addClass("text-muted");
275     } else {
276         $(".log-viewer-page-down").removeClass("text-muted");
277     }
278 }
279
280 function nextPage(logViewer, page, id) {
281     gotoPage(logViewer.page_offset+1, logViewer, page, id);
282 }
283
284 function prevPage(logViewer, page, id) {
285     gotoPage(logViewer.page_offset-1, logViewer, page, id);
286 }