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