www/metrics-epoch.js (149 lines of code) (raw):

// 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); }