1 $(document
).ready(function () {
7 var nStr
= n
.toString();
8 var x
= nStr
.split('.');
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');
18 function formatSuffix(val
, opt_prec
) {
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";
31 return val
.toFixed(prec
) + " B";
35 function formatRate(val
, prec
) {
40 return formatSuffix(val
, prec
) + "/s";
43 function formatPercent(val
, opt_prec
) {
48 var prec
= opt_prec
|| 1;
49 return val
.toFixed(prec
) + " %";
52 // Set up polling interval control
53 var updateInterval
= 1000; // ms
54 $("#updateInterval").val(updateInterval
).change(function () {
55 updateInterval
= $(this).val();
58 // Allow the UI to be paused
60 $('#pause-ui').click(function () {
62 $(this).text("Pause UI");
65 $(this).text("Unpause UI");
71 function suffixFormatter(val
, axis
) {
72 return formatSuffix(val
, axis
.tickDecimals
);
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";
83 return val
.toFixed(axis
.tickDecimals
);
87 function rateFormatter(val
, axis
) {
88 return formatRate(val
, axis
.tickDecimals
);
91 function percentFormatter(val
, axis
) {
92 return formatPercent(val
, axis
.tickDecimals
);
95 // Fetch data periodically and notify interested parties.
98 function subscribe(fn
) {
102 function unsubscribe(fn
) {
103 listeners
= listeners
.filter(function (el
) {
110 var alertVisible
= false;
111 function fetchData() {
112 function onDataReceived(stats
) {
114 $(".alert-message").hide();
116 alertVisible
= false;
117 for (var i
= 0; i
< listeners
.length
; i
++) {
118 listeners
[i
](stats
, stats
.ekg
.server_timestamp_ms
.val
);
123 $(".alert-message").show();
130 success: onDataReceived
,
135 setTimeout(fetchData
, updateInterval
);
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"] }
144 var options
= $.extend(true, {}, defaultOptions
, opts
);
145 var data
= new Array(series
.length
);
147 for(var i
= 0; i
< series
.length
; i
++) {
151 var plot
= $.plot(elem
, [], options
);
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);
160 data
[i
].push([time
, series
[i
].fn(stats
, time
,
161 prev_stats
, prev_time
)]);
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]; });
167 // zip legends with data
169 for(var i
= 0; i
< series
.length
; i
++)
170 res
.push({ label: series
[i
].label
, data: data
[i
] });
181 subscribe(onDataReceived
);
182 return onDataReceived
;
185 function addCounter(elem
, fn
, formatter
) {
186 var prev_stats
, prev_time
;
187 function onDataReceived(stats
, time
) {
189 elem
.text(formatter(fn(stats
, time
, prev_stats
, prev_time
)));
194 subscribe(onDataReceived
);
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
);
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
} });
213 var plotContainer
= $("#" + plotId
);
214 var closeButton
= plotContainer
.find("img");
216 closeButton
.click(function () {
217 plotContainer
.remove();
219 unsubscribe(observer
);
232 function addMetrics(table
) {
235 var DISTRIBUTION
= "d";
238 function makeDataGetter(key
) {
239 var pieces
= key
.split(".");
240 function get(key
, stats
, time
, prev_stats
, prev_time
) {
242 $.each(pieces
, function(unused_index
, piece
) {
243 value
= value
[piece
];
245 if (value
.type
=== COUNTER
) {
246 if (prev_stats
== undefined)
248 var prev_value
= prev_stats
;
249 $.each(pieces
, function(unused_index
, piece
) {
250 prev_value
= prev_value
[piece
];
252 return 1000 * (value
.val
- prev_value
.val
) /
254 } else if (value
.type
=== DISTRIBUTION
) {
256 } else { // value.type === GAUGE || value.type === LABEL
263 function counterLabel(label
) {
267 function gaugeLabel(label
) {
271 /** Adds the table row. */
272 function addElem(key
, value
) {
274 if (key
in metrics
) {
278 table
.find("tbody:last").append(
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");
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
;
293 button
.click(function () {
294 addDynamicPlot(key
, button
, graph_fn
, label_fn
);
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';
307 } else { // COUNTER, GAUGE, LABEL
310 if ($.inArray(value
.type
, [COUNTER
, GAUGE
]) !== -1) {
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
;
325 build(prefix
+ suffix
+ '.', value
);
332 subscribe(onDataReceived
);
337 var current_bytes_used = function (stats
) {
338 return stats
.rts
.gc
.current_bytes_used
.val
;
340 var max_bytes_used = function (stats
) {
341 return stats
.rts
.gc
.max_bytes_used
.val
;
343 var max_bytes_slop = function (stats
) {
344 return stats
.rts
.gc
.max_bytes_slop
.val
;
346 var current_bytes_slop = function (stats
) {
347 return stats
.rts
.gc
.current_bytes_slop
.val
;
349 var productivity_wall_percent = function (stats
, time
, prev_stats
, prev_time
) {
350 if (prev_stats
== undefined)
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
);
358 var productivity_cpu_percent = function (stats
, time
, prev_stats
, prev_time
) {
359 if (prev_stats
== undefined)
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
);
367 var allocation_rate = function (stats
, time
, prev_stats
, prev_time
) {
368 if (prev_stats
== undefined)
370 return 1000 * (stats
.rts
.gc
.bytes_allocated
.val
-
371 prev_stats
.rts
.gc
.bytes_allocated
.val
) /
375 addMetrics($("#metric-table"));
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
} });
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
);