]> Git — Sourcephile - gargantext.git/blob - ekg-assets/monitor.js
[MERGE] merging prelude
[gargantext.git] / ekg-assets / monitor.js
1 $(document).ready(function () {
2 "use strict";
3
4 // Number formatters
5 function commaify(n)
6 {
7 var nStr = n.toString();
8 var x = nStr.split('.');
9 var x1 = x[0];
10 var x2 = x.length > 1 ? '.' + x[1] : '';
11 var rgx = /(\d+)(\d{3})/;
12 while (rgx.test(x1)) {
13 x1 = x1.replace(rgx, '$1' + ',' + '$2');
14 }
15 return x1 + x2;
16 }
17
18 function formatSuffix(val, opt_prec) {
19 if (val === null) {
20 return "N/A";
21 }
22
23 var prec = opt_prec || 1;
24 if (val >= 1000000000) {
25 return (val / 1000000000).toFixed(prec) + " GB";
26 } else if (val >= 1000000) {
27 return (val / 1000000).toFixed(prec) + " MB";
28 } else if (val >= 1000) {
29 return (val / 1000).toFixed(prec) + " kB";
30 } else {
31 return val.toFixed(prec) + " B";
32 }
33 }
34
35 function formatRate(val, prec) {
36 if (val === null) {
37 return "N/A";
38 }
39
40 return formatSuffix(val, prec) + "/s";
41 }
42
43 function formatPercent(val, opt_prec) {
44 if (val === null) {
45 return "N/A";
46 }
47
48 var prec = opt_prec || 1;
49 return val.toFixed(prec) + " %";
50 }
51
52 // Set up polling interval control
53 var updateInterval = 1000; // ms
54 $("#updateInterval").val(updateInterval).change(function () {
55 updateInterval = $(this).val();
56 });
57
58 // Allow the UI to be paused
59 var paused = false;
60 $('#pause-ui').click(function () {
61 if (paused) {
62 $(this).text("Pause UI");
63 paused = false;
64 } else {
65 $(this).text("Unpause UI");
66 paused = true;
67 }
68 });
69
70 // Plot formatters
71 function suffixFormatter(val, axis) {
72 return formatSuffix(val, axis.tickDecimals);
73 }
74
75 function suffixFormatterGeneric(val, axis) {
76 if (val >= 1000000000) {
77 return (val / 1000000000).toFixed(axis.tickDecimals) + " G";
78 } else if (val >= 1000000) {
79 return (val / 1000000).toFixed(axis.tickDecimals) + " M";
80 } else if (val >= 1000) {
81 return (val / 1000).toFixed(axis.tickDecimals) + " k";
82 } else {
83 return val.toFixed(axis.tickDecimals);
84 }
85 }
86
87 function rateFormatter(val, axis) {
88 return formatRate(val, axis.tickDecimals);
89 }
90
91 function percentFormatter(val, axis) {
92 return formatPercent(val, axis.tickDecimals);
93 }
94
95 // Fetch data periodically and notify interested parties.
96 var listeners = [];
97
98 function subscribe(fn) {
99 listeners.push(fn);
100 }
101
102 function unsubscribe(fn) {
103 listeners = listeners.filter(function (el) {
104 if (el !== fn) {
105 return el;
106 }
107 });
108 }
109
110 var alertVisible = false;
111 function fetchData() {
112 function onDataReceived(stats) {
113 if (alertVisible) {
114 $(".alert-message").hide();
115 }
116 alertVisible = false;
117 for (var i = 0; i < listeners.length; i++) {
118 listeners[i](stats, stats.ekg.server_timestamp_ms.val);
119 }
120 }
121
122 function onError() {
123 $(".alert-message").show();
124 alertVisible = true;
125 }
126
127 $.ajax({
128 url: '/ekg/api',
129 dataType: 'json',
130 success: onDataReceived,
131 error: onError,
132 cache: false
133 });
134
135 setTimeout(fetchData, updateInterval);
136 }
137 fetchData();
138
139 function addPlot(elem, series, opts) {
140 var defaultOptions = {
141 series: { shadowSize: 0 }, // drawing is faster without shadows
142 xaxis: { mode: "time", tickSize: [10, "second"] }
143 };
144 var options = $.extend(true, {}, defaultOptions, opts);
145 var data = new Array(series.length);
146 var maxPoints = 60;
147 for(var i = 0; i < series.length; i++) {
148 data[i] = [];
149 }
150
151 var plot = $.plot(elem, [], options);
152
153 var prev_stats, prev_time;
154 function onDataReceived(stats, time) {
155 for(var i = 0; i < series.length; i++) {
156 if (data[i].length >= maxPoints) {
157 data[i] = data[i].slice(1);
158 }
159
160 data[i].push([time, series[i].fn(stats, time,
161 prev_stats, prev_time)]);
162
163 // the data may arrive out-of-order, so sort by time stamp first
164 data[i].sort(function (a, b) { return a[0] - b[0]; });
165 }
166
167 // zip legends with data
168 var res = [];
169 for(var i = 0; i < series.length; i++)
170 res.push({ label: series[i].label, data: data[i] });
171
172 if (!paused) {
173 plot.setData(res);
174 plot.setupGrid();
175 plot.draw();
176 }
177 prev_stats = stats;
178 prev_time = time;
179 }
180
181 subscribe(onDataReceived);
182 return onDataReceived;
183 }
184
185 function addCounter(elem, fn, formatter) {
186 var prev_stats, prev_time;
187 function onDataReceived(stats, time) {
188 if (!paused)
189 elem.text(formatter(fn(stats, time, prev_stats, prev_time)));
190 prev_stats = stats;
191 prev_time = time;
192 }
193
194 subscribe(onDataReceived);
195 }
196
197 function addDynamicPlot(key, button, graph_fn, label_fn) {
198 function getStats(stats, time, prev_stats, prev_time) {
199 return graph_fn(key, stats, time, prev_stats, prev_time);
200 }
201
202 // jQuery has problem with IDs containing dots.
203 var plotId = key.replace(/\./g, "-") + "-plot";
204 $("#plots:last").append(
205 '<div id="' + plotId + '" class="plot-container">' +
206 '<img src="cross.png" class="close-button"><h3>' + key +
207 '</h3><div class="plot"></div></div>');
208 var plot = $("#plots > .plot-container:last > div");
209 var observer = addPlot(plot,
210 [{ label: label_fn(key), fn: getStats }],
211 { yaxis: { tickFormatter: suffixFormatterGeneric } });
212
213 var plotContainer = $("#" + plotId);
214 var closeButton = plotContainer.find("img");
215 closeButton.hide();
216 closeButton.click(function () {
217 plotContainer.remove();
218 button.show();
219 unsubscribe(observer);
220 });
221
222 plotContainer.hover(
223 function () {
224 closeButton.show();
225 },
226 function () {
227 closeButton.hide();
228 }
229 );
230 }
231
232 function addMetrics(table) {
233 var COUNTER = "c";
234 var GAUGE = "g";
235 var DISTRIBUTION = "d";
236 var metrics = {};
237
238 function makeDataGetter(key) {
239 var pieces = key.split(".");
240 function get(key, stats, time, prev_stats, prev_time) {
241 var value = stats;
242 $.each(pieces, function(unused_index, piece) {
243 value = value[piece];
244 });
245 if (value.type === COUNTER) {
246 if (prev_stats == undefined)
247 return null;
248 var prev_value = prev_stats;
249 $.each(pieces, function(unused_index, piece) {
250 prev_value = prev_value[piece];
251 });
252 return 1000 * (value.val - prev_value.val) /
253 (time - prev_time);
254 } else if (value.type === DISTRIBUTION) {
255 return value.mean;
256 } else { // value.type === GAUGE || value.type === LABEL
257 return value.val;
258 }
259 }
260 return get;
261 }
262
263 function counterLabel(label) {
264 return label + "/s";
265 }
266
267 function gaugeLabel(label) {
268 return label;
269 }
270
271 /** Adds the table row. */
272 function addElem(key, value) {
273 var elem;
274 if (key in metrics) {
275 elem = metrics[key];
276 } else {
277 // Add UI element
278 table.find("tbody:last").append(
279 '<tr><td>' + key +
280 ' <img src="chart_line_add.png" class="graph-button"' +
281 ' width="16" height="16"' +
282 ' alt="Add graph" title="Add graph"></td>' +
283 '<td class="value">N/A</td></tr>');
284 elem = table.find("tbody > tr > td:last");
285 metrics[key] = elem;
286
287 var button = table.find("tbody > tr:last > td:first > img");
288 var graph_fn = makeDataGetter(key);
289 var label_fn = gaugeLabel;
290 if (value.type === COUNTER) {
291 label_fn = counterLabel;
292 }
293 button.click(function () {
294 addDynamicPlot(key, button, graph_fn, label_fn);
295 $(this).hide();
296 });
297 }
298 if (!paused) {
299 if (value.type === DISTRIBUTION) {
300 if (value.mean !== null) {
301 var val = value.mean.toPrecision(8) + '\n+/-' +
302 Math.sqrt(value.variance).toPrecision(8) + ' sd';
303 }
304 else {
305 var val = "N/A";
306 }
307 } else { // COUNTER, GAUGE, LABEL
308 var val = value.val;
309 }
310 if ($.inArray(value.type, [COUNTER, GAUGE]) !== -1) {
311 val = commaify(val);
312 }
313 elem.text(val);
314 }
315 }
316
317 /** Updates UI for all metrics. */
318 function onDataReceived(stats, time) {
319 function build(prefix, obj) {
320 $.each(obj, function (suffix, value) {
321 if (value.hasOwnProperty("type")) {
322 var key = prefix + suffix;
323 addElem(key, value);
324 } else {
325 build(prefix + suffix + '.', value);
326 }
327 });
328 }
329 build('', stats);
330 }
331
332 subscribe(onDataReceived);
333 }
334
335 function initAll() {
336 // Metrics
337 var current_bytes_used = function (stats) {
338 return stats.rts.gc.current_bytes_used.val;
339 };
340 var max_bytes_used = function (stats) {
341 return stats.rts.gc.max_bytes_used.val;
342 };
343 var max_bytes_slop = function (stats) {
344 return stats.rts.gc.max_bytes_slop.val;
345 };
346 var current_bytes_slop = function (stats) {
347 return stats.rts.gc.current_bytes_slop.val;
348 };
349 var productivity_wall_percent = function (stats, time, prev_stats, prev_time) {
350 if (prev_stats == undefined)
351 return null;
352 var mutator_ms = stats.rts.gc.mutator_wall_ms.val -
353 prev_stats.rts.gc.mutator_wall_ms.val;
354 var gc_ms = stats.rts.gc.gc_wall_ms.val -
355 prev_stats.rts.gc.gc_wall_ms.val;
356 return 100 * mutator_ms / (mutator_ms + gc_ms);
357 };
358 var productivity_cpu_percent = function (stats, time, prev_stats, prev_time) {
359 if (prev_stats == undefined)
360 return null;
361 var mutator_ms = stats.rts.gc.mutator_cpu_ms.val -
362 prev_stats.rts.gc.mutator_cpu_ms.val;
363 var gc_ms = stats.rts.gc.gc_cpu_ms.val -
364 prev_stats.rts.gc.gc_cpu_ms.val;
365 return 100 * mutator_ms / (mutator_ms + gc_ms);
366 };
367 var allocation_rate = function (stats, time, prev_stats, prev_time) {
368 if (prev_stats == undefined)
369 return null;
370 return 1000 * (stats.rts.gc.bytes_allocated.val -
371 prev_stats.rts.gc.bytes_allocated.val) /
372 (time - prev_time);
373 };
374
375 addMetrics($("#metric-table"));
376
377 // Plots
378 addPlot($("#current-bytes-used-plot > div"),
379 [{ label: "residency", fn: current_bytes_used }],
380 { yaxis: { tickFormatter: suffixFormatter } });
381 addPlot($("#allocation-rate-plot > div"),
382 [{ label: "rate", fn: allocation_rate }],
383 { yaxis: { tickFormatter: rateFormatter } });
384 addPlot($("#productivity-plot > div"),
385 [{ label: "wall clock time", fn: productivity_wall_percent },
386 { label: "cpu time", fn: productivity_cpu_percent }],
387 { yaxis: { tickDecimals: 1, tickFormatter: percentFormatter } });
388
389 // GC and memory statistics
390 addCounter($("#max-bytes-used"), max_bytes_used, formatSuffix);
391 addCounter($("#current-bytes-used"), current_bytes_used, formatSuffix);
392 addCounter($("#max-bytes-slop"), max_bytes_slop, formatSuffix);
393 addCounter($("#current-bytes-slop"), current_bytes_slop, formatSuffix);
394 addCounter($("#productivity-wall"), productivity_wall_percent, formatPercent);
395 addCounter($("#productivity-cpu"), productivity_cpu_percent, formatPercent);
396 addCounter($("#allocation-rate"), allocation_rate, formatRate);
397 }
398
399 initAll();
400 });