blob: 5b5a2f224081c2c28b37d076b92abb2b2b5c03e1 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var charts = [];
var last_value = [];
var last_update = Date.now();
var num_charts = 0;
// TODO allow setting this from the UI, e.g., add a pick-and-choose
// dialog box, or let a user click an 'x' next to a metric to delete
// it from the view.
var metrics_uri = makeMetricsUri(urlParams());
var MAX_SCALE_FACTOR = 1;
// Technically we can just pass the string through as-is without
// having to put it all into a map and re-encode it. However, this
// makes it easier to add new UI functionality (e.g., pick and
// choose).
function urlParams() {
var query_params = {};
var query = window.location.search.substring(1);
var query_vars = query.split('&');
for (var i = 0; i < query_vars.length; i++) {
var entry_pair = query_vars[i].split('=');
if (entry_pair.length == 1) {
query_params[decodeURIComponent(entry_pair[0])] = true;
} else {
query_params[decodeURIComponent(entry_pair[0])] =
decodeURIComponent(entry_pair[1]);
}
}
return query_params;
}
function makeMetricsUri(url_params) {
var BASE_METRICS_URI = "/metrics";
url_params['compact'] = 'true';
url_params['include_raw_histograms'] = 'true';
url_params['include_schema'] = 'true';
var components = [];
for (var key in url_params) {
var value = url_params[key];
var component = encodeURIComponent(key);
if (typeof value === 'string') {
component += '=' + encodeURIComponent(value);
}
components.push(component);
}
var ret = BASE_METRICS_URI;
if (components.length > 0) {
ret += '?' + components.join('&');
}
return ret;
}
function createChart(name, label, type, initial_data, raw_data) {
var span_id = 'chart_' + num_charts++;
var div = d3.select('#metrics').append('div');
div.attr('class', 'metrics-container');
var name_elem = div.append('div');
name_elem.attr('class', 'metric-name');
name_elem.text(name);
var display_elem = div.append('div');
display_elem.attr('id', span_id);
display_elem.attr('class', 'epoch category10');
display_elem.attr('style', 'height: 100px;');
var label_elem = div.append('div');
label_elem.attr('class', 'metric-label');
label_elem.html(label);
div.append('hr');
var data = [
{
'label': "foo",
'values': [ initial_data ]
}
];
if (type == 'histogram') {
charts[name] = $('#' + span_id).epoch({
type: 'time.heatmap',
data: data,
axes: ['right', 'bottom', 'left'],
bucketRange: [raw_data['min'], raw_data['percentile_99_9']],
buckets: 20,
ticks: { 'time': 3 },
});
} else {
charts[name] = $('#' + span_id).epoch({
type: 'time.line',
data: data,
axes: ['right', 'bottom', 'left'],
ticks: { 'y': 3, 'time': 3 },
});
}
}
function updateCharts(json, error) {
if (error) return console.warn(error);
if (! json) {
// Unclear why, but sometimes we seem to get a null
// here.
console.warn("null JSON response");
setTimeout(function() {
d3.json(metrics_uri, updateCharts);
}, 1000);
return;
}
var now = Date.now();
var delta_time = now - last_update;
var extra = {};
json.forEach(function(e) {
var entity_type = e['type'];
var entity_id = e['id'];
e["metrics"].forEach(function(m) {
var extra = m;
var name = entity_type + "_" + entity_id + "_" + m['name'];
var type = m['type'];
var unit = m['unit'];
var label = m['description'];
var data = { 'time': now / 1000 };
var first_run = false;
if (!(name in charts)) {
first_run = true;
last_value[name] = 0;
}
// For counters, we display a rate
if (type == "counter") {
label += "<br>(shown: rate in " + unit + " / second)";
var cur_value = m['value'];
if (first_run) {
last_value[name] = cur_value;
// display value is 0 to start
} else {
var delta_value = cur_value - last_value[name];
last_value[name] = cur_value;
var rate = delta_value / delta_time * 1000;
data['y'] = rate;
}
// For charts, we simply display the value.
} else if (type == "gauge") {
data['y'] = m['value'];
} else if (type == "histogram") {
// For histograms, we display a heat map
if (first_run) {
last_value[name] = {};
}
var values = m['values'];
var counts = m['counts'];
if (! values) { values = []; }
if (! counts) { counts = []; }
var hist = {};
var prev = last_value[name];
for (var i = 0; i < values.length; i++) {
var value = values[i];
var count = counts[i];
hist[value] = count;
}
last_value[name] = $.extend({}, hist);
for (value in hist) {
if (value in prev) {
hist[value] -= prev[value];
}
}
data['histogram'] = hist;
} else {
// For non-special-cased stuff just print the value field as well, if available.
if ("value" in m) {
data['y'] = m['value'];
}
}
if (first_run) {
createChart(name, label, type, data, extra);
} else {
charts[name].push([data]);
}
});
});
last_update = now;
setTimeout(function() {
d3.json(metrics_uri, updateCharts);
}, 1000);
}
function initialize() {
d3.json(metrics_uri, updateCharts);
}