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