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