2883: Improved pagination control a bit, added search.
[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     var first = logViewer.items[1];
185     var last = logViewer.items[logViewer.items.length-1];
186
187     {
188         html += "<div>";
189         html += "Started at " + first.values().timestamp;
190
191         var duration = (last.values().ts.getTime() - first.values().ts.getTime()) / 1000;
192
193         var hours = 0;
194         var minutes = 0;
195         var seconds;
196
197         if (duration >= 3600) {
198             hours = Math.floor(duration / 3600);
199             duration -= (hours * 3600);
200         }
201         if (duration >= 60) {
202             minutes = Math.floor(duration / 60);
203             duration -= (minutes * 60);
204         }
205         seconds = duration;
206
207         var tcount = taskState.task_count;
208
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 += "</div>";
228     }
229
230     $(id).html(html);
231 }
232
233 function gotoPage(n, logViewer, page, id) {
234     if (n < 0) { return; }
235     if (n*page > logViewer.matchingItems.length) { return; }
236     logViewer.page_offset = n;
237     logViewer.show(n*page, page);
238 }
239
240 function updatePaging(id, logViewer, page) {
241     var p = "";
242     var i = logViewer.matchingItems.length;
243     var n;
244     for (n = 0; (n*page) < i; n += 1) {
245         if (n == logViewer.page_offset) {
246             p += "<span class='log-viewer-page-num'>" + (n+1) + "</span> ";
247         } else {
248             p += "<a href=\"#\" class='log-viewer-page-num log-viewer-page-" + n + "'>" + (n+1) + "</a> ";
249         }
250     }
251     $(id).html(p);
252     for (n = 0; (n*page) < i; n += 1) {
253         (function(n) {
254             $(".log-viewer-page-" + n).on("click", function() {
255                 gotoPage(n, logViewer, page, id);
256                 return false;
257             });
258         })(n);
259     }
260
261     if (logViewer.page_offset == 0) {
262         $(".log-viewer-page-up").addClass("text-muted");
263     } else {
264         $(".log-viewer-page-up").removeClass("text-muted");
265     }
266
267     if (logViewer.page_offset == (n-1)) {
268         $(".log-viewer-page-down").addClass("text-muted");
269     } else {
270         $(".log-viewer-page-down").removeClass("text-muted");
271     }
272 }
273
274 function nextPage(logViewer, page, id) {
275     gotoPage(logViewer.page_offset+1, logViewer, page, id);
276 }
277
278 function prevPage(logViewer, page, id) {
279     gotoPage(logViewer.page_offset-1, logViewer, page, id);
280 }