ui: Add vendor directory as a target for JS linting and lint (#9157)

* ui: Add vendor for js linting

* Lint all the things
This commit is contained in:
John Cowen 2020-11-11 16:59:15 +00:00 committed by GitHub
parent 2badb01d30
commit 67b70878f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 385 additions and 372 deletions

View File

@ -5,8 +5,8 @@ module.exports = {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
ecmaFeatures: { ecmaFeatures: {
legacyDecorators: true legacyDecorators: true,
} },
}, },
plugins: ['ember'], plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended'], extends: ['eslint:recommended', 'plugin:ember/recommended'],
@ -17,7 +17,7 @@ module.exports = {
'no-unused-vars': ['error', { args: 'none' }], 'no-unused-vars': ['error', { args: 'none' }],
'ember/no-new-mixins': ['warn'], 'ember/no-new-mixins': ['warn'],
'ember/no-jquery': 'warn', 'ember/no-jquery': 'warn',
'ember/no-global-jquery': 'warn' 'ember/no-global-jquery': 'warn',
}, },
overrides: [ overrides: [
// node files // node files
@ -31,14 +31,14 @@ module.exports = {
'blueprints/*/index.js', 'blueprints/*/index.js',
'config/**/*.js', 'config/**/*.js',
'lib/*/index.js', 'lib/*/index.js',
'server/**/*.js' 'server/**/*.js',
], ],
parserOptions: { parserOptions: {
sourceType: 'script' sourceType: 'script',
}, },
env: { env: {
browser: false, browser: false,
node: true node: true,
}, },
plugins: ['node'], plugins: ['node'],
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
@ -46,8 +46,8 @@ module.exports = {
// this can be removed once the following is fixed // this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77 // https://github.com/mysticatea/eslint-plugin-node/issues/77
'node/no-unpublished-require': 'off' 'node/no-unpublished-require': 'off',
}) }),
} },
] ],
}; };

View File

@ -14,7 +14,7 @@ module.exports = {
'no-nested-interactive': false, 'no-nested-interactive': false,
'block-indentation': false, 'block-indentation': false,
'quotes': false, quotes: false,
'no-inline-styles': false, 'no-inline-styles': false,
'no-triple-curlies': false, 'no-triple-curlies': false,
@ -29,6 +29,6 @@ module.exports = {
'no-invalid-role': false, 'no-invalid-role': false,
'no-unnecessary-component-helper': false, 'no-unnecessary-component-helper': false,
'link-href-attributes': false 'link-href-attributes': false,
}, },
}; };

View File

@ -5,7 +5,6 @@ import { tracked } from '@glimmer/tracking';
import { sort } from '@ember/object/computed'; import { sort } from '@ember/object/computed';
export default class ConsulIntentionList extends Component { export default class ConsulIntentionList extends Component {
@service('filter') filter; @service('filter') filter;
@service('sort') sort; @service('sort') sort;
@service('search') search; @service('search') search;
@ -24,10 +23,10 @@ export default class ConsulIntentionList extends Component {
} }
get filtered() { get filtered() {
const predicate = this.filter.predicate('intention'); const predicate = this.filter.predicate('intention');
return this.args.items.filter(predicate(this.args.filters)) return this.args.items.filter(predicate(this.args.filters));
} }
get searched() { get searched() {
if(typeof this.args.search === 'undefined') { if (typeof this.args.search === 'undefined') {
return this.filtered; return this.filtered;
} }
const predicate = this.search.predicate('intention'); const predicate = this.search.predicate('intention');
@ -37,7 +36,7 @@ export default class ConsulIntentionList extends Component {
return [this.args.sort]; return [this.args.sort];
} }
get checkedItem() { get checkedItem() {
if(this.searched.length === 1) { if (this.searched.length === 1) {
return this.searched[0].SourceName === this.args.search ? this.searched[0] : null; return this.searched[0].SourceName === this.args.search ? this.searched[0] : null;
} }
return null; return null;

View File

@ -1,4 +1,6 @@
export default (collection, clickable, attribute, isPresent, deletable) => (scope = '.consul-intention-list') => { export default (collection, clickable, attribute, isPresent, deletable) => (
scope = '.consul-intention-list'
) => {
const row = { const row = {
source: attribute('data-test-intention-source', '[data-test-intention-source]'), source: attribute('data-test-intention-source', '[data-test-intention-source]'),
destination: attribute('data-test-intention-destination', '[data-test-intention-destination]'), destination: attribute('data-test-intention-destination', '[data-test-intention-destination]'),
@ -10,6 +12,6 @@ export default (collection, clickable, attribute, isPresent, deletable) => (scop
return { return {
scope: scope, scope: scope,
customResourceNotice: isPresent('.consul-intention-notice-custom-resource'), customResourceNotice: isPresent('.consul-intention-notice-custom-resource'),
intentions: collection('[data-test-tabular-row]', row) intentions: collection('[data-test-tabular-row]', row),
} };
}; };

View File

@ -1,4 +1,4 @@
export default () => (term) => (item) => { export default () => term => item => {
const source = item.SourceName.toLowerCase(); const source = item.SourceName.toLowerCase();
const destination = item.DestinationName.toLowerCase(); const destination = item.DestinationName.toLowerCase();
const allLabel = 'All Services (*)'.toLowerCase(); const allLabel = 'All Services (*)'.toLowerCase();
@ -9,4 +9,4 @@ export default () => (term) => (item) => {
(source === '*' && allLabel.indexOf(lowerTerm) !== -1) || (source === '*' && allLabel.indexOf(lowerTerm) !== -1) ||
(destination === '*' && allLabel.indexOf(lowerTerm) !== -1) (destination === '*' && allLabel.indexOf(lowerTerm) !== -1)
); );
} };

View File

@ -4,7 +4,6 @@ import { PRIMARY_KEY } from 'consul-ui/models/intention';
const modelName = 'intention'; const modelName = 'intention';
export default class IntentionRepository extends RepositoryService { export default class IntentionRepository extends RepositoryService {
managedByCRDs = false; managedByCRDs = false;
getModelName() { getModelName() {
@ -24,9 +23,11 @@ export default class IntentionRepository extends RepositoryService {
} }
isManagedByCRDs() { isManagedByCRDs() {
if(!this.managedByCRDs) { if (!this.managedByCRDs) {
this.managedByCRDs = this.store.peekAll(this.getModelName()) this.managedByCRDs = this.store
.toArray().some(item => item.IsManagedByCRD); .peekAll(this.getModelName())
.toArray()
.some(item => item.IsManagedByCRD);
} }
return this.managedByCRDs; return this.managedByCRDs;
} }

View File

@ -8,18 +8,15 @@ module.exports = function(defaults) {
const isProdLike = prodlike.indexOf(env) > -1; const isProdLike = prodlike.indexOf(env) > -1;
const sourcemaps = !isProd; const sourcemaps = !isProd;
let trees = {}; let trees = {};
if(isProdLike) { if (isProdLike) {
// exclude any component/pageobject.js files from production-like environments // exclude any component/pageobject.js files from production-like environments
trees.app = new Funnel( trees.app = new Funnel('app', {
'app', exclude: [
{ 'components/**/pageobject.js',
exclude: [ 'components/**/*.test-support.js',
'components/**/pageobject.js', 'components/**/*.test.js',
'components/**/*.test-support.js', ],
'components/**/*.test.js' });
]
}
);
} }
let app = new EmberApp( let app = new EmberApp(
Object.assign({}, defaults, { Object.assign({}, defaults, {
@ -31,14 +28,7 @@ module.exports = function(defaults) {
includePolyfill: true, includePolyfill: true,
}, },
'ember-cli-string-helpers': { 'ember-cli-string-helpers': {
only: [ only: ['capitalize', 'lowercase', 'truncate', 'uppercase', 'humanize', 'titleize'],
'capitalize',
'lowercase',
'truncate',
'uppercase',
'humanize',
'titleize'
],
}, },
'ember-cli-math-helpers': { 'ember-cli-math-helpers': {
only: ['div'], only: ['div'],

View File

@ -15,7 +15,7 @@
"lint:dev:js": "eslint -c .dev.eslintrc.js --fix ./*.js ./.*.js app config lib server tests", "lint:dev:js": "eslint -c .dev.eslintrc.js --fix ./*.js ./.*.js app config lib server tests",
"lint:hbs": "ember-template-lint .", "lint:hbs": "ember-template-lint .",
"lint:js": "eslint .", "lint:js": "eslint .",
"format:js": "prettier --write \"{app,config,lib,server,tests}/**/*.js\" ./*.js ./.*.js", "format:js": "prettier --write \"{app,config,lib,server,vendor,tests}/**/*.js\" ./*.js ./.*.js",
"format:css": "prettier --write \"app/styles/**/*.*\"", "format:css": "prettier --write \"app/styles/**/*.*\"",
"start": "ember serve --port=${EMBER_SERVE_PORT:-4200} --live-reload-port=${EMBER_LIVE_RELOAD_PORT:-7020}", "start": "ember serve --port=${EMBER_SERVE_PORT:-4200} --live-reload-port=${EMBER_LIVE_RELOAD_PORT:-7020}",
"start:staging": "ember serve --port=${EMBER_SERVE_PORT:-4200} --live-reload-port=${EMBER_LIVE_RELOAD_PORT:-7020} --environment staging", "start:staging": "ember serve --port=${EMBER_SERVE_PORT:-4200} --live-reload-port=${EMBER_LIVE_RELOAD_PORT:-7020} --environment staging",
@ -42,7 +42,7 @@
} }
}, },
"lint-staged": { "lint-staged": {
"{app,config,lib,server,tests}/**/*.js": [ "{app,config,lib,server,vendor,tests}/**/*.js": [
"prettier --write" "prettier --write"
], ],
"app/styles/**/*.*": [ "app/styles/**/*.*": [

View File

@ -1,12 +1,8 @@
module.exports = { module.exports = {
test_page: 'tests/index.html?hidepassed', test_page: 'tests/index.html?hidepassed',
disable_watching: true, disable_watching: true,
launch_in_ci: [ launch_in_ci: ['Chrome'],
'Chrome' launch_in_dev: ['Chrome'],
],
launch_in_dev: [
'Chrome'
],
browser_start_timeout: 120, browser_start_timeout: 120,
browser_args: { browser_args: {
Chrome: { Chrome: {
@ -39,5 +35,5 @@ if (process.env.EMBER_TEST_REPORT) {
* https://github.com/trentmwillis/ember-exam/issues/108 * https://github.com/trentmwillis/ember-exam/issues/108
*/ */
if (process.env.EMBER_EXAM_PARALLEL) { if (process.env.EMBER_EXAM_PARALLEL) {
module.exports.parallel = -1 module.exports.parallel = -1;
} }

View File

@ -3,6 +3,6 @@ export default function(visitable, creatable, clickable, intentions, popoverSele
visit: visitable('/:dc/intentions'), visit: visitable('/:dc/intentions'),
intentionList: intentions(), intentionList: intentions(),
sort: popoverSelect('[data-test-sort-control]'), sort: popoverSelect('[data-test-sort-control]'),
...creatable({}) ...creatable({}),
} };
} }

View File

@ -11,7 +11,11 @@ export default function(scenario, assert, find, currentPage, pauseUntil, plurali
return retry(); return retry();
}, `Expected ${num} ${model}s`); }, `Expected ${num} ${model}s`);
}) })
.then('pause until I see $number $model model[s]? on the $component component', function(num, model, component) { .then('pause until I see $number $model model[s]? on the $component component', function(
num,
model,
component
) {
return pauseUntil(function(resolve, reject, retry) { return pauseUntil(function(resolve, reject, retry) {
const obj = find(component); const obj = find(component);
const len = obj[pluralize(model)].filter(function(item) { const len = obj[pluralize(model)].filter(function(item) {

View File

@ -14,7 +14,8 @@ export default function(scenario, create) {
function(number, model, data) { function(number, model, data) {
return create(number, model, data); return create(number, model, data);
} }
).given(['settings from yaml\n$yaml'], function(data) { )
.given(['settings from yaml\n$yaml'], function(data) {
return Object.keys(data).forEach(function(key) { return Object.keys(data).forEach(function(key) {
window.localStorage[key] = JSON.stringify(data[key]); window.localStorage[key] = JSON.stringify(data[key]);
}); });

View File

@ -4,22 +4,21 @@ export default function(scenario, find, click) {
return click(selector); return click(selector);
}) })
// TODO: Probably nicer to think of better vocab than having the 'without " rule' // TODO: Probably nicer to think of better vocab than having the 'without " rule'
.when([ .when(
'I click (?!")$property(?!")', [
'I click $property on the $component', 'I click (?!")$property(?!")',
'I click $property on the $component component' 'I click $property on the $component',
], function( 'I click $property on the $component component',
property, ],
component, function(property, component, next) {
next try {
) { if (typeof component === 'string') {
try { property = `${component}.${property}`;
if (typeof component === 'string') { }
property = `${component}.${property}`; return find(property)();
} catch (e) {
throw e;
} }
return find(property)();
} catch (e) {
throw e;
} }
}); );
} }

View File

@ -3,21 +3,36 @@ import { module, test } from 'qunit';
module('Unit | Helper | selectable-key-values', function() { module('Unit | Helper | selectable-key-values', function() {
test('it turns arrays into key values and selects the first item by default', function(assert) { test('it turns arrays into key values and selects the first item by default', function(assert) {
const actual = selectableKeyValues([['key-1', 'value-1'], ['key-2', 'value-2']]); const actual = selectableKeyValues([
['key-1', 'value-1'],
['key-2', 'value-2'],
]);
assert.equal(actual.items.length, 2); assert.equal(actual.items.length, 2);
assert.deepEqual(actual.selected, { key: 'key-1', value: 'value-1' }); assert.deepEqual(actual.selected, { key: 'key-1', value: 'value-1' });
}); });
test('it turns arrays into key values and selects the defined key', function(assert) { test('it turns arrays into key values and selects the defined key', function(assert) {
const actual = selectableKeyValues([['key-1', 'value-1'], ['key-2', 'value-2']], { const actual = selectableKeyValues(
selected: 'key-2', [
}); ['key-1', 'value-1'],
['key-2', 'value-2'],
],
{
selected: 'key-2',
}
);
assert.equal(actual.items.length, 2); assert.equal(actual.items.length, 2);
assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' }); assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' });
}); });
test('it turns arrays into key values and selects the defined index', function(assert) { test('it turns arrays into key values and selects the defined index', function(assert) {
const actual = selectableKeyValues([['key-1', 'value-1'], ['key-2', 'value-2']], { const actual = selectableKeyValues(
selected: 1, [
}); ['key-1', 'value-1'],
['key-2', 'value-2'],
],
{
selected: 1,
}
);
assert.equal(actual.items.length, 2); assert.equal(actual.items.length, 2);
assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' }); assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' });
}); });

View File

@ -1,51 +1,47 @@
( (function(global) {
function(global) { // Current interface is these three methods.
// Current interface is these three methods. const requiredMethods = [
const requiredMethods = [ 'init',
'init', 'serviceRecentSummarySeries',
'serviceRecentSummarySeries', 'serviceRecentSummaryStats',
'serviceRecentSummaryStats', 'upstreamRecentSummaryStats',
'upstreamRecentSummaryStats', 'downstreamRecentSummaryStats',
'downstreamRecentSummaryStats', ];
];
// This is a bit gross but we want to support simple extensibility by // This is a bit gross but we want to support simple extensibility by
// including JS in the browser without forcing operators to setup a whole // including JS in the browser without forcing operators to setup a whole
// transpiling stack. So for now we use a window global as a thin registry for // transpiling stack. So for now we use a window global as a thin registry for
// these providers. // these providers.
class Consul { class Consul {
constructor() { constructor() {
this.registry = {}; this.registry = {};
this.providers = {}; this.providers = {};
}
registerMetricsProvider(name, provider) {
// Basic check that it matches the type we expect
for (var m of requiredMethods) {
if (typeof provider[m] !== 'function') {
throw new Error(`Can't register metrics provider '${name}': missing ${m} method.`);
}
}
this.registry[name] = provider;
}
getMetricsProvider(name, options) {
if (!(name in this.registry)) {
throw new Error(`Metrics Provider '${name}' is not registered.`);
}
if (name in this.providers) {
return this.providers[name];
}
this.providers[name] = Object.create(this.registry[name]);
this.providers[name].init(options);
return this.providers[name];
}
} }
global.consul = new Consul(); registerMetricsProvider(name, provider) {
// Basic check that it matches the type we expect
for (var m of requiredMethods) {
if (typeof provider[m] !== 'function') {
throw new Error(`Can't register metrics provider '${name}': missing ${m} method.`);
}
}
this.registry[name] = provider;
}
getMetricsProvider(name, options) {
if (!(name in this.registry)) {
throw new Error(`Metrics Provider '${name}' is not registered.`);
}
if (name in this.providers) {
return this.providers[name];
}
this.providers[name] = Object.create(this.registry[name]);
this.providers[name].init(options);
return this.providers[name];
}
} }
)(window);
global.consul = new Consul();
})(window);

View File

@ -1,6 +1,6 @@
/*eslint no-console: "off"*/ /*eslint no-console: "off"*/
(function () { (function() {
var emptySeries = { unitSuffix: "", labels: {}, data: [] } var emptySeries = { unitSuffix: '', labels: {}, data: [] };
var prometheusProvider = { var prometheusProvider = {
options: {}, options: {},
@ -26,7 +26,9 @@
init: function(options) { init: function(options) {
this.options = options; this.options = options;
if (!this.options.metrics_proxy_enabled) { if (!this.options.metrics_proxy_enabled) {
throw new Error("prometheus metrics provider currently requires the ui_config.metrics_proxy to be configured in the Consul agent."); throw new Error(
'prometheus metrics provider currently requires the ui_config.metrics_proxy to be configured in the Consul agent.'
);
} }
}, },
@ -36,26 +38,24 @@
httpGet: function(url, queryParams, headers) { httpGet: function(url, queryParams, headers) {
if (queryParams) { if (queryParams) {
var separator = url.indexOf('?') !== -1 ? '&' : '?'; var separator = url.indexOf('?') !== -1 ? '&' : '?';
var qs = Object.keys(queryParams). var qs = Object.keys(queryParams)
map(function(key) { .map(function(key) {
return encodeURIComponent(key) + "=" + encodeURIComponent(queryParams[key]); return encodeURIComponent(key) + '=' + encodeURIComponent(queryParams[key]);
}). })
join("&"); .join('&');
url = url + separator + qs; url = url + separator + qs;
} }
// fetch the url along with any headers // fetch the url along with any headers
return this.options.fetch(url, {headers: headers || {}}).then( return this.options.fetch(url, { headers: headers || {} }).then(function(response) {
function(response) { if (response.ok) {
if(response.ok) { return response.json();
return response.json(); } else {
} else { // throw a statusCode error if any errors are received
// throw a statusCode error if any errors are received var e = new Error('HTTP Error: ' + response.statusText);
var e = new Error('HTTP Error: ' + response.statusText); e.statusCode = response.status;
e.statusCode = response.status; throw e;
throw e;
}
} }
); });
}, },
/** /**
@ -114,13 +114,13 @@
*/ */
serviceRecentSummarySeries: function(serviceDC, namespace, serviceName, protocol, options) { serviceRecentSummarySeries: function(serviceDC, namespace, serviceName, protocol, options) {
// Fetch time-series // Fetch time-series
var series = [] var series = [];
var labels = [] var labels = [];
// Set the start and end range here so that all queries end up with // Set the start and end range here so that all queries end up with
// identical time axes. Later we might accept these as options. // identical time axes. Later we might accept these as options.
var now = (new Date()).getTime()/1000; var now = new Date().getTime() / 1000;
options.start = now - (15*60); options.start = now - 15 * 60;
options.end = now; options.end = now;
if (this.hasL7Metrics(protocol)) { if (this.hasL7Metrics(protocol)) {
@ -169,18 +169,18 @@
// Fetch stats // Fetch stats
var stats = []; var stats = [];
if (this.hasL7Metrics(protocol)) { if (this.hasL7Metrics(protocol)) {
stats.push(this.fetchRPS(serviceName, "service", options)) stats.push(this.fetchRPS(serviceName, 'service', options));
stats.push(this.fetchER(serviceName, "service", options)) stats.push(this.fetchER(serviceName, 'service', options));
stats.push(this.fetchPercentile(50, serviceName, "service", options)) stats.push(this.fetchPercentile(50, serviceName, 'service', options));
stats.push(this.fetchPercentile(99, serviceName, "service", options)) stats.push(this.fetchPercentile(99, serviceName, 'service', options));
} else { } else {
// Fallback to just L4 metrics. // Fallback to just L4 metrics.
stats.push(this.fetchConnRate(serviceName, "service", options)) stats.push(this.fetchConnRate(serviceName, 'service', options));
stats.push(this.fetchServiceRx(serviceName, "service", options)) stats.push(this.fetchServiceRx(serviceName, 'service', options));
stats.push(this.fetchServiceTx(serviceName, "service", options)) stats.push(this.fetchServiceTx(serviceName, 'service', options));
stats.push(this.fetchServiceNoRoute(serviceName, "service", options)) stats.push(this.fetchServiceNoRoute(serviceName, 'service', options));
} }
return this.fetchStats(stats) return this.fetchStats(stats);
}, },
/** /**
@ -217,7 +217,7 @@
* } * }
*/ */
upstreamRecentSummaryStats: function(serviceDC, namespace, serviceName, upstreamName, options) { upstreamRecentSummaryStats: function(serviceDC, namespace, serviceName, upstreamName, options) {
return this.fetchRecentSummaryStats(serviceName, "upstream", options) return this.fetchRecentSummaryStats(serviceName, 'upstream', options);
}, },
/** /**
@ -260,7 +260,7 @@
* } * }
*/ */
downstreamRecentSummaryStats: function(serviceDC, namespace, serviceName, options) { downstreamRecentSummaryStats: function(serviceDC, namespace, serviceName, options) {
return this.fetchRecentSummaryStats(serviceName, "downstream", options) return this.fetchRecentSummaryStats(serviceName, 'downstream', options);
}, },
fetchRecentSummaryStats: function(serviceName, type, options) { fetchRecentSummaryStats: function(serviceName, type, options) {
@ -270,74 +270,76 @@
// We don't know which upstreams are HTTP/TCP so just fetch all of them. // We don't know which upstreams are HTTP/TCP so just fetch all of them.
// HTTP // HTTP
stats.push(this.fetchRPS(serviceName, type, options)) stats.push(this.fetchRPS(serviceName, type, options));
stats.push(this.fetchER(serviceName, type, options)) stats.push(this.fetchER(serviceName, type, options));
stats.push(this.fetchPercentile(50, serviceName, type, options)) stats.push(this.fetchPercentile(50, serviceName, type, options));
stats.push(this.fetchPercentile(99, serviceName, type, options)) stats.push(this.fetchPercentile(99, serviceName, type, options));
// L4 // L4
stats.push(this.fetchConnRate(serviceName, type, options)) stats.push(this.fetchConnRate(serviceName, type, options));
stats.push(this.fetchServiceRx(serviceName, type, options)) stats.push(this.fetchServiceRx(serviceName, type, options));
stats.push(this.fetchServiceTx(serviceName, type, options)) stats.push(this.fetchServiceTx(serviceName, type, options));
stats.push(this.fetchServiceNoRoute(serviceName, type, options)) stats.push(this.fetchServiceNoRoute(serviceName, type, options));
return this.fetchStatsGrouped(stats) return this.fetchStatsGrouped(stats);
}, },
hasL7Metrics: function(protocol) { hasL7Metrics: function(protocol) {
return protocol === "http" || protocol === "http2" || protocol === "grpc" return protocol === 'http' || protocol === 'http2' || protocol === 'grpc';
}, },
fetchStats: function(statsPromises) { fetchStats: function(statsPromises) {
var all = Promise.all(statsPromises). var all = Promise.all(statsPromises).then(function(results) {
then(function(results){
var data = { var data = {
stats: [] stats: [],
} };
// Add all non-empty stats // Add all non-empty stats
for (var i = 0; i < statsPromises.length; i++) { for (var i = 0; i < statsPromises.length; i++) {
if (results[i].value) { if (results[i].value) {
data.stats.push(results[i]); data.stats.push(results[i]);
} }
} }
return data return data;
}) });
// Fetch the metrics async, and return a promise to the result. // Fetch the metrics async, and return a promise to the result.
return all return all;
}, },
fetchStatsGrouped: function(statsPromises) { fetchStatsGrouped: function(statsPromises) {
var all = Promise.all(statsPromises). var all = Promise.all(statsPromises).then(function(results) {
then(function(results){
var data = { var data = {
stats: {} stats: {},
} };
// Add all non-empty stats // Add all non-empty stats
for (var i = 0; i < statsPromises.length; i++) { for (var i = 0; i < statsPromises.length; i++) {
if (results[i]) { if (results[i]) {
for (var group in results[i]) { for (var group in results[i]) {
if (!results[i].hasOwnProperty(group)) continue; if (!results[i].hasOwnProperty(group)) continue;
if (!data.stats[group]) { if (!data.stats[group]) {
data.stats[group] = [] data.stats[group] = [];
} }
data.stats[group].push(results[i][group]) data.stats[group].push(results[i][group]);
} }
} }
} }
return data return data;
}) });
// Fetch the metrics async, and return a promise to the result. // Fetch the metrics async, and return a promise to the result.
return all return all;
}, },
reformatSeries: function(unitSuffix, labelMap) { reformatSeries: function(unitSuffix, labelMap) {
return function(response) { return function(response) {
// Handle empty result sets gracefully. // Handle empty result sets gracefully.
if (!response.data || !response.data.result || response.data.result.length == 0 if (
|| !response.data.result[0].values !response.data ||
|| response.data.result[0].values.length == 0) { !response.data.result ||
response.data.result.length == 0 ||
!response.data.result[0].values ||
response.data.result[0].values.length == 0
) {
return emptySeries; return emptySeries;
} }
// Reformat the prometheus data to be the format we want with stacked // Reformat the prometheus data to be the format we want with stacked
@ -362,306 +364,315 @@
return { return {
unitSuffix: unitSuffix, unitSuffix: unitSuffix,
labels: labelMap, labels: labelMap,
data: series data: series,
}; };
}; };
}, },
fetchRequestRateSeries: function(serviceName, options){ fetchRequestRateSeries: function(serviceName, options) {
// We need the sum of all non-500 error rates as one value and the 500 // We need the sum of all non-500 error rates as one value and the 500
// error rate as a separate series so that they stack to show the full // error rate as a separate series so that they stack to show the full
// request rate. Some creative label replacement makes this possible in // request rate. Some creative label replacement makes this possible in
// one query. // one query.
var q = `sum by (label) (`+ var q =
`sum by (label) (` +
// The outer label_replace catches 5xx error and relabels them as // The outer label_replace catches 5xx error and relabels them as
// err=yes // err=yes
`label_replace(`+ `label_replace(` +
// The inner label_replace relabels all !5xx rates as err=no so they // The inner label_replace relabels all !5xx rates as err=no so they
// will get summed together. // will get summed together.
`label_replace(`+ `label_replace(` +
// Get rate of requests to the service // Get rate of requests to the service
`irate(envoy_listener_http_downstream_rq_xx{local_cluster="${serviceName}",envoy_http_conn_manager_prefix="public_listener_http"}[10m])`+ `irate(envoy_listener_http_downstream_rq_xx{local_cluster="${serviceName}",envoy_http_conn_manager_prefix="public_listener_http"}[10m])` +
// ... inner replacement matches all code classes except "5" and // ... inner replacement matches all code classes except "5" and
// applies err=no // applies err=no
`, "label", "Successes", "envoy_response_code_class", "[^5]")`+ `, "label", "Successes", "envoy_response_code_class", "[^5]")` +
// ... outer replacement matches code=5 and applies err=yes // ... outer replacement matches code=5 and applies err=yes
`, "label", "Errors", "envoy_response_code_class", "5")`+ `, "label", "Errors", "envoy_response_code_class", "5")` +
`)` `)`;
var labelMap = { var labelMap = {
Total: 'Total inbound requests per second', Total: 'Total inbound requests per second',
Successes: 'Successful responses (with an HTTP response code not in the 5xx range) per second.', Successes:
'Successful responses (with an HTTP response code not in the 5xx range) per second.',
Errors: 'Error responses (with an HTTP response code in the 5xx range) per second.', Errors: 'Error responses (with an HTTP response code in the 5xx range) per second.',
}; };
return this.fetchSeries(q, options) return this.fetchSeries(q, options).then(this.reformatSeries(' rps', labelMap));
.then(this.reformatSeries(" rps", labelMap))
}, },
fetchDataRateSeries: function(serviceName, options){ fetchDataRateSeries: function(serviceName, options) {
// 8 * converts from bytes/second to bits/second // 8 * converts from bytes/second to bits/second
var q = `8 * sum by (label) (`+ var q =
`8 * sum by (label) (` +
// Label replace generates a unique label per rx/tx metric to stop them // Label replace generates a unique label per rx/tx metric to stop them
// being summed together. // being summed together.
`label_replace(`+ `label_replace(` +
// Get the tx rate // Get the tx rate
`irate(envoy_tcp_downstream_cx_tx_bytes_total{local_cluster="${serviceName}",envoy_tcp_prefix="public_listener_tcp"}[10m])`+ `irate(envoy_tcp_downstream_cx_tx_bytes_total{local_cluster="${serviceName}",envoy_tcp_prefix="public_listener_tcp"}[10m])` +
// Match all and apply the tx label // Match all and apply the tx label
`, "label", "Outbound", "__name__", ".*"`+ `, "label", "Outbound", "__name__", ".*"` +
// Union those vectors with the RX ones // Union those vectors with the RX ones
`) or label_replace(`+ `) or label_replace(` +
// Get the rx rate // Get the rx rate
`irate(envoy_tcp_downstream_cx_rx_bytes_total{local_cluster="${serviceName}",envoy_tcp_prefix="public_listener_tcp"}[10m])`+ `irate(envoy_tcp_downstream_cx_rx_bytes_total{local_cluster="${serviceName}",envoy_tcp_prefix="public_listener_tcp"}[10m])` +
// Match all and apply the rx label // Match all and apply the rx label
`, "label", "Inbound", "__name__", ".*"`+ `, "label", "Inbound", "__name__", ".*"` +
`)`+ `)` +
`)` `)`;
var labelMap = { var labelMap = {
Total: 'Total bandwidth', Total: 'Total bandwidth',
Inbound: 'Inbound data rate (data recieved) from the network in bits per second.', Inbound: 'Inbound data rate (data recieved) from the network in bits per second.',
Outbound: 'Outbound data rate (data transmitted) from the network in bits per second.', Outbound: 'Outbound data rate (data transmitted) from the network in bits per second.',
}; };
return this.fetchSeries(q, options) return this.fetchSeries(q, options).then(this.reformatSeries('bps', labelMap));
.then(this.reformatSeries("bps", labelMap))
}, },
makeSubject: function(serviceName, type) { makeSubject: function(serviceName, type) {
if (type == "upstream") { if (type == 'upstream') {
// {{GROUP}} is a placeholder that is replaced by the upstream name // {{GROUP}} is a placeholder that is replaced by the upstream name
return `${serviceName} &rarr; {{GROUP}}`; return `${serviceName} &rarr; {{GROUP}}`;
} }
if (type == "downstream") { if (type == 'downstream') {
// {{GROUP}} is a placeholder that is replaced by the downstream name // {{GROUP}} is a placeholder that is replaced by the downstream name
return `{{GROUP}} &rarr; ${serviceName}`; return `{{GROUP}} &rarr; ${serviceName}`;
} }
return serviceName return serviceName;
}, },
makeHTTPSelector: function(serviceName, type) { makeHTTPSelector: function(serviceName, type) {
// Downstreams are totally different // Downstreams are totally different
if (type == "downstream") { if (type == 'downstream') {
return `consul_service="${serviceName}"` return `consul_service="${serviceName}"`;
} }
var lc = `local_cluster="${serviceName}"` var lc = `local_cluster="${serviceName}"`;
if (type == "upstream") { if (type == 'upstream') {
lc += `,envoy_http_conn_manager_prefix=~"upstream_.*"`; lc += `,envoy_http_conn_manager_prefix=~"upstream_.*"`;
} else { } else {
// Only care about inbound public listener // Only care about inbound public listener
lc += `,envoy_http_conn_manager_prefix="public_listener_http"` lc += `,envoy_http_conn_manager_prefix="public_listener_http"`;
} }
return lc return lc;
}, },
makeTCPSelector: function(serviceName, type) { makeTCPSelector: function(serviceName, type) {
// Downstreams are totally different // Downstreams are totally different
if (type == "downstream") { if (type == 'downstream') {
return `consul_service="${serviceName}"` return `consul_service="${serviceName}"`;
} }
var lc = `local_cluster="${serviceName}"` var lc = `local_cluster="${serviceName}"`;
if (type == "upstream") { if (type == 'upstream') {
lc += `,envoy_tcp_prefix=~"upstream_.*"`; lc += `,envoy_tcp_prefix=~"upstream_.*"`;
} else { } else {
// Only care about inbound public listener // Only care about inbound public listener
lc += `,envoy_tcp_prefix="public_listener_tcp"` lc += `,envoy_tcp_prefix="public_listener_tcp"`;
} }
return lc return lc;
}, },
groupQueryHTTP: function(type, q) { groupQueryHTTP: function(type, q) {
if (type == "upstream") { if (type == 'upstream') {
q += " by (envoy_http_conn_manager_prefix)" q += ' by (envoy_http_conn_manager_prefix)';
// Extract the raw upstream service name to group results by // Extract the raw upstream service name to group results by
q = this.upstreamRelabelQueryHTTP(q) q = this.upstreamRelabelQueryHTTP(q);
} else if (type == "downstream") { } else if (type == 'downstream') {
q += " by (local_cluster)" q += ' by (local_cluster)';
q = this.downstreamRelabelQuery(q) q = this.downstreamRelabelQuery(q);
} }
return q return q;
}, },
groupQueryTCP: function(type, q) { groupQueryTCP: function(type, q) {
if (type == "upstream") { if (type == 'upstream') {
q += " by (envoy_tcp_prefix)" q += ' by (envoy_tcp_prefix)';
// Extract the raw upstream service name to group results by // Extract the raw upstream service name to group results by
q = this.upstreamRelabelQueryTCP(q) q = this.upstreamRelabelQueryTCP(q);
} else if (type == "downstream") { } else if (type == 'downstream') {
q += " by (local_cluster)" q += ' by (local_cluster)';
q = this.downstreamRelabelQuery(q) q = this.downstreamRelabelQuery(q);
} }
return q return q;
}, },
upstreamRelabelQueryHTTP: function(q) { upstreamRelabelQueryHTTP: function(q) {
return `label_replace(${q}, "upstream", "$1", "envoy_http_conn_manager_prefix", "upstream_(.*)_http")` return `label_replace(${q}, "upstream", "$1", "envoy_http_conn_manager_prefix", "upstream_(.*)_http")`;
}, },
upstreamRelabelQueryTCP: function(q) { upstreamRelabelQueryTCP: function(q) {
return `label_replace(${q}, "upstream", "$1", "envoy_tcp_prefix", "upstream_(.*)_tcp")` return `label_replace(${q}, "upstream", "$1", "envoy_tcp_prefix", "upstream_(.*)_tcp")`;
}, },
downstreamRelabelQuery: function(q) { downstreamRelabelQuery: function(q) {
return `label_replace(${q}, "downstream", "$1", "local_cluster", "(.*)")` return `label_replace(${q}, "downstream", "$1", "local_cluster", "(.*)")`;
}, },
groupBy: function(type) { groupBy: function(type) {
if (type == "service") { if (type == 'service') {
return false return false;
} }
return type; return type;
}, },
metricPrefixHTTP: function(type) { metricPrefixHTTP: function(type) {
if (type == "downstream") { if (type == 'downstream') {
return "envoy_cluster_upstream_rq" return 'envoy_cluster_upstream_rq';
} }
return "envoy_http_downstream_rq"; return 'envoy_http_downstream_rq';
}, },
metricPrefixTCP: function(type) { metricPrefixTCP: function(type) {
if (type == "downstream") { if (type == 'downstream') {
return "envoy_cluster_upstream_cx" return 'envoy_cluster_upstream_cx';
} }
return "envoy_tcp_downstream_cx"; return 'envoy_tcp_downstream_cx';
}, },
fetchRPS: function(serviceName, type, options){ fetchRPS: function(serviceName, type, options) {
var sel = this.makeHTTPSelector(serviceName, type) var sel = this.makeHTTPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var metricPfx = this.metricPrefixHTTP(type) var metricPfx = this.metricPrefixHTTP(type);
var q = `sum(rate(${metricPfx}_completed{${sel}}[15m]))` var q = `sum(rate(${metricPfx}_completed{${sel}}[15m]))`;
return this.fetchStat(this.groupQueryHTTP(type, q), return this.fetchStat(
"RPS", this.groupQueryHTTP(type, q),
'RPS',
`<b>${subject}</b> request rate averaged over the last 15 minutes`, `<b>${subject}</b> request rate averaged over the last 15 minutes`,
shortNumStr, shortNumStr,
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchER: function(serviceName, type, options){ fetchER: function(serviceName, type, options) {
var sel = this.makeHTTPSelector(serviceName, type) var sel = this.makeHTTPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var groupBy = "" var groupBy = '';
if (type == "upstream") { if (type == 'upstream') {
groupBy += " by (envoy_http_conn_manager_prefix)" groupBy += ' by (envoy_http_conn_manager_prefix)';
} else if (type == "downstream") { } else if (type == 'downstream') {
groupBy += " by (local_cluster)" groupBy += ' by (local_cluster)';
} }
var metricPfx = this.metricPrefixHTTP(type) var metricPfx = this.metricPrefixHTTP(type);
var q = `sum(rate(${metricPfx}_xx{${sel},envoy_response_code_class="5"}[15m]))${groupBy}/sum(rate(${metricPfx}_xx{${sel}}[15m]))${groupBy}` var q = `sum(rate(${metricPfx}_xx{${sel},envoy_response_code_class="5"}[15m]))${groupBy}/sum(rate(${metricPfx}_xx{${sel}}[15m]))${groupBy}`;
if (type == "upstream") { if (type == 'upstream') {
q = this.upstreamRelabelQueryHTTP(q) q = this.upstreamRelabelQueryHTTP(q);
} else if (type == "downstream") { } else if (type == 'downstream') {
q = this.downstreamRelabelQuery(q) q = this.downstreamRelabelQuery(q);
} }
return this.fetchStat(q, return this.fetchStat(
"ER", q,
'ER',
`Percentage of <b>${subject}</b> requests which were 5xx status over the last 15 minutes`, `Percentage of <b>${subject}</b> requests which were 5xx status over the last 15 minutes`,
function(val){ function(val) {
return shortNumStr(val)+"%" return shortNumStr(val) + '%';
}, },
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchPercentile: function(percentile, serviceName, type, options){ fetchPercentile: function(percentile, serviceName, type, options) {
var sel = this.makeHTTPSelector(serviceName, type) var sel = this.makeHTTPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var groupBy = "le" var groupBy = 'le';
if (type == "upstream") { if (type == 'upstream') {
groupBy += ",envoy_http_conn_manager_prefix" groupBy += ',envoy_http_conn_manager_prefix';
} else if (type == "downstream") { } else if (type == 'downstream') {
groupBy += ",local_cluster" groupBy += ',local_cluster';
} }
var metricPfx = this.metricPrefixHTTP(type) var metricPfx = this.metricPrefixHTTP(type);
var q = `histogram_quantile(${percentile/100}, sum by(${groupBy}) (rate(${metricPfx}_time_bucket{${sel}}[15m])))` var q = `histogram_quantile(${percentile /
if (type == "upstream") { 100}, sum by(${groupBy}) (rate(${metricPfx}_time_bucket{${sel}}[15m])))`;
q = this.upstreamRelabelQueryHTTP(q) if (type == 'upstream') {
} else if (type == "downstream") { q = this.upstreamRelabelQueryHTTP(q);
q = this.downstreamRelabelQuery(q) } else if (type == 'downstream') {
q = this.downstreamRelabelQuery(q);
} }
return this.fetchStat(q, return this.fetchStat(
q,
`P${percentile}`, `P${percentile}`,
`<b>${subject}</b> ${percentile}th percentile request service time over the last 15 minutes`, `<b>${subject}</b> ${percentile}th percentile request service time over the last 15 minutes`,
shortTimeStr, shortTimeStr,
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchConnRate: function(serviceName, type, options) { fetchConnRate: function(serviceName, type, options) {
var sel = this.makeTCPSelector(serviceName, type) var sel = this.makeTCPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var metricPfx = this.metricPrefixTCP(type) var metricPfx = this.metricPrefixTCP(type);
var q = `sum(rate(${metricPfx}_total{${sel}}[15m]))` var q = `sum(rate(${metricPfx}_total{${sel}}[15m]))`;
return this.fetchStat(this.groupQueryTCP(type, q), return this.fetchStat(
"CR", this.groupQueryTCP(type, q),
'CR',
`<b>${subject}</b> inbound TCP connections per second averaged over the last 15 minutes`, `<b>${subject}</b> inbound TCP connections per second averaged over the last 15 minutes`,
shortNumStr, shortNumStr,
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchServiceRx: function(serviceName, type, options) { fetchServiceRx: function(serviceName, type, options) {
var sel = this.makeTCPSelector(serviceName, type) var sel = this.makeTCPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var metricPfx = this.metricPrefixTCP(type) var metricPfx = this.metricPrefixTCP(type);
var q = `8 * sum(rate(${metricPfx}_rx_bytes_total{${sel}}[15m]))` var q = `8 * sum(rate(${metricPfx}_rx_bytes_total{${sel}}[15m]))`;
return this.fetchStat(this.groupQueryTCP(type, q), return this.fetchStat(
"RX", this.groupQueryTCP(type, q),
'RX',
`<b>${subject}</b> received bits per second averaged over the last 15 minutes`, `<b>${subject}</b> received bits per second averaged over the last 15 minutes`,
shortNumStr, shortNumStr,
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchServiceTx: function(serviceName, type, options) { fetchServiceTx: function(serviceName, type, options) {
var sel = this.makeTCPSelector(serviceName, type) var sel = this.makeTCPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var metricPfx = this.metricPrefixTCP(type) var metricPfx = this.metricPrefixTCP(type);
var q = `8 * sum(rate(${metricPfx}_tx_bytes_total{${sel}}[15m]))` var q = `8 * sum(rate(${metricPfx}_tx_bytes_total{${sel}}[15m]))`;
var self = this var self = this;
return this.fetchStat(this.groupQueryTCP(type, q), return this.fetchStat(
"TX", this.groupQueryTCP(type, q),
'TX',
`<b>${subject}</b> transmitted bits per second averaged over the last 15 minutes`, `<b>${subject}</b> transmitted bits per second averaged over the last 15 minutes`,
shortNumStr, shortNumStr,
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchServiceNoRoute: function(serviceName, type, options) { fetchServiceNoRoute: function(serviceName, type, options) {
var sel = this.makeTCPSelector(serviceName, type) var sel = this.makeTCPSelector(serviceName, type);
var subject = this.makeSubject(serviceName, type) var subject = this.makeSubject(serviceName, type);
var metricPfx = this.metricPrefixTCP(type) var metricPfx = this.metricPrefixTCP(type);
var metric = "_no_route" var metric = '_no_route';
if (type == "downstream") { if (type == 'downstream') {
metric = "_connect_fail" metric = '_connect_fail';
} }
var q = `sum(rate(${metricPfx}${metric}{${sel}}[15m]))` var q = `sum(rate(${metricPfx}${metric}{${sel}}[15m]))`;
return this.fetchStat(this.groupQueryTCP(type, q), return this.fetchStat(
"NR", this.groupQueryTCP(type, q),
'NR',
`<b>${subject}</b> unroutable (failed) connections per second averaged over the last 15 minutes`, `<b>${subject}</b> unroutable (failed) connections per second averaged over the last 15 minutes`,
shortNumStr, shortNumStr,
this.groupBy(type) this.groupBy(type)
) );
}, },
fetchStat: function(promql, label, desc, formatter, groupBy) { fetchStat: function(promql, label, desc, formatter, groupBy) {
if (!groupBy) { if (!groupBy) {
// If we don't have a grouped result and its just a single stat, return // If we don't have a grouped result and its just a single stat, return
// no result as a zero not a missing stat. // no result as a zero not a missing stat.
promql += " OR on() vector(0)"; promql += ' OR on() vector(0)';
} }
//console.log(promql) //console.log(promql)
var params = { var params = {
query: promql, query: promql,
time: (new Date).getTime()/1000 time: new Date().getTime() / 1000,
} };
return this.httpGet("/api/v1/query", params).then(function(response){ return this.httpGet('/api/v1/query', params).then(function(response) {
if (!groupBy) { if (!groupBy) {
// Not grouped, expect just one stat value return that // Not grouped, expect just one stat value return that
var v = parseFloat(response.data.result[0].value[1]) var v = parseFloat(response.data.result[0].value[1]);
return { return {
label: label, label: label,
desc: desc, desc: desc,
value: formatter(v) value: formatter(v),
} };
} }
var data = {}; var data = {};
@ -672,11 +683,11 @@
data[groupName] = { data[groupName] = {
label: label, label: label,
desc: desc.replace('{{GROUP}}', groupName), desc: desc.replace('{{GROUP}}', groupName),
value: formatter(v) value: formatter(v),
} };
} }
return data; return data;
}) });
}, },
fetchSeries: function(promql, options) { fetchSeries: function(promql, options) {
@ -684,23 +695,23 @@
query: promql, query: promql,
start: options.start, start: options.start,
end: options.end, end: options.end,
step: "10s", step: '10s',
timeout: "8s" timeout: '8s',
} };
return this.httpGet("/api/v1/query_range", params) return this.httpGet('/api/v1/query_range', params);
}, },
};
}
// Helper functions // Helper functions
function shortNumStr(n) { function shortNumStr(n) {
if (n < 1e3) { if (n < 1e3) {
if (Number.isInteger(n)) return ""+n if (Number.isInteger(n)) return '' + n;
if (n >= 100) { if (n >= 100) {
// Go to 3 significant figures but wrap it in Number to avoid scientific // Go to 3 significant figures but wrap it in Number to avoid scientific
// notation lie 2.3e+2 for 230. // notation lie 2.3e+2 for 230.
return Number(n.toPrecision(3)) return Number(n.toPrecision(3));
} if (n < 1) { }
if (n < 1) {
// Very small numbers show with limited precision to prevent long string // Very small numbers show with limited precision to prevent long string
// of 0.000000. // of 0.000000.
return Number(n.toFixed(2)); return Number(n.toFixed(2));
@ -709,29 +720,28 @@
return Number(n.toPrecision(2)); return Number(n.toPrecision(2));
} }
} }
if (n >= 1e3 && n < 1e6) return +(n / 1e3).toPrecision(3) + "k"; if (n >= 1e3 && n < 1e6) return +(n / 1e3).toPrecision(3) + 'k';
if (n >= 1e6 && n < 1e9) return +(n / 1e6).toPrecision(3) + "m"; if (n >= 1e6 && n < 1e9) return +(n / 1e6).toPrecision(3) + 'm';
if (n >= 1e9 && n < 1e12) return +(n / 1e9).toPrecision(3) + "g"; if (n >= 1e9 && n < 1e12) return +(n / 1e9).toPrecision(3) + 'g';
if (n >= 1e12) return +(n / 1e12).toFixed(0) + "t"; if (n >= 1e12) return +(n / 1e12).toFixed(0) + 't';
} }
function shortTimeStr(n) { function shortTimeStr(n) {
if (n < 1e3) return Math.round(n) + "ms"; if (n < 1e3) return Math.round(n) + 'ms';
var secs = n / 1e3 var secs = n / 1e3;
if (secs < 60) return secs.toFixed(1) + "s" if (secs < 60) return secs.toFixed(1) + 's';
var mins = secs/60 var mins = secs / 60;
if (mins < 60) return mins.toFixed(1) + "m" if (mins < 60) return mins.toFixed(1) + 'm';
var hours = mins/60 var hours = mins / 60;
if (hours < 24) return hours.toFixed(1) + "h" if (hours < 24) return hours.toFixed(1) + 'h';
var days = hours/24 var days = hours / 24;
return days.toFixed(1) + "d" return days.toFixed(1) + 'd';
} }
/* global consul:writable */ /* global consul:writable */
window.consul.registerMetricsProvider("prometheus", prometheusProvider) window.consul.registerMetricsProvider('prometheus', prometheusProvider);
})();
}());