diff --git a/ui/packages/consul-ui/.eslintrc.js b/ui/packages/consul-ui/.eslintrc.js index ce7e9cee4a..c5aba82625 100644 --- a/ui/packages/consul-ui/.eslintrc.js +++ b/ui/packages/consul-ui/.eslintrc.js @@ -5,8 +5,8 @@ module.exports = { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { - legacyDecorators: true - } + legacyDecorators: true, + }, }, plugins: ['ember'], extends: ['eslint:recommended', 'plugin:ember/recommended'], @@ -17,7 +17,7 @@ module.exports = { 'no-unused-vars': ['error', { args: 'none' }], 'ember/no-new-mixins': ['warn'], 'ember/no-jquery': 'warn', - 'ember/no-global-jquery': 'warn' + 'ember/no-global-jquery': 'warn', }, overrides: [ // node files @@ -31,14 +31,14 @@ module.exports = { 'blueprints/*/index.js', 'config/**/*.js', 'lib/*/index.js', - 'server/**/*.js' + 'server/**/*.js', ], parserOptions: { - sourceType: 'script' + sourceType: 'script', }, env: { browser: false, - node: true + node: true, }, plugins: ['node'], 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 // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off' - }) - } - ] + 'node/no-unpublished-require': 'off', + }), + }, + ], }; diff --git a/ui/packages/consul-ui/.template-lintrc.js b/ui/packages/consul-ui/.template-lintrc.js index a87bfda6cc..df2f4f9e07 100644 --- a/ui/packages/consul-ui/.template-lintrc.js +++ b/ui/packages/consul-ui/.template-lintrc.js @@ -14,7 +14,7 @@ module.exports = { 'no-nested-interactive': false, 'block-indentation': false, - 'quotes': false, + quotes: false, 'no-inline-styles': false, 'no-triple-curlies': false, @@ -29,6 +29,6 @@ module.exports = { 'no-invalid-role': false, 'no-unnecessary-component-helper': false, - 'link-href-attributes': false + 'link-href-attributes': false, }, }; diff --git a/ui/packages/consul-ui/app/components/consul/intention/list/index.js b/ui/packages/consul-ui/app/components/consul/intention/list/index.js index 167ea1730e..01d0181bac 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/list/index.js +++ b/ui/packages/consul-ui/app/components/consul/intention/list/index.js @@ -5,7 +5,6 @@ import { tracked } from '@glimmer/tracking'; import { sort } from '@ember/object/computed'; export default class ConsulIntentionList extends Component { - @service('filter') filter; @service('sort') sort; @service('search') search; @@ -24,10 +23,10 @@ export default class ConsulIntentionList extends Component { } get filtered() { 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() { - if(typeof this.args.search === 'undefined') { + if (typeof this.args.search === 'undefined') { return this.filtered; } const predicate = this.search.predicate('intention'); @@ -37,7 +36,7 @@ export default class ConsulIntentionList extends Component { return [this.args.sort]; } 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 null; diff --git a/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js b/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js index 3159a09f57..c8c60d6f43 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js +++ b/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js @@ -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 = { source: attribute('data-test-intention-source', '[data-test-intention-source]'), destination: attribute('data-test-intention-destination', '[data-test-intention-destination]'), @@ -10,6 +12,6 @@ export default (collection, clickable, attribute, isPresent, deletable) => (scop return { scope: scope, customResourceNotice: isPresent('.consul-intention-notice-custom-resource'), - intentions: collection('[data-test-tabular-row]', row) - } + intentions: collection('[data-test-tabular-row]', row), + }; }; diff --git a/ui/packages/consul-ui/app/search/predicates/intention.js b/ui/packages/consul-ui/app/search/predicates/intention.js index 43fa80528a..12da95a264 100644 --- a/ui/packages/consul-ui/app/search/predicates/intention.js +++ b/ui/packages/consul-ui/app/search/predicates/intention.js @@ -1,4 +1,4 @@ -export default () => (term) => (item) => { +export default () => term => item => { const source = item.SourceName.toLowerCase(); const destination = item.DestinationName.toLowerCase(); const allLabel = 'All Services (*)'.toLowerCase(); @@ -9,4 +9,4 @@ export default () => (term) => (item) => { (source === '*' && allLabel.indexOf(lowerTerm) !== -1) || (destination === '*' && allLabel.indexOf(lowerTerm) !== -1) ); -} +}; diff --git a/ui/packages/consul-ui/app/services/repository/intention.js b/ui/packages/consul-ui/app/services/repository/intention.js index 1c4d627dd3..7489b3bebc 100644 --- a/ui/packages/consul-ui/app/services/repository/intention.js +++ b/ui/packages/consul-ui/app/services/repository/intention.js @@ -4,7 +4,6 @@ import { PRIMARY_KEY } from 'consul-ui/models/intention'; const modelName = 'intention'; export default class IntentionRepository extends RepositoryService { - managedByCRDs = false; getModelName() { @@ -24,9 +23,11 @@ export default class IntentionRepository extends RepositoryService { } isManagedByCRDs() { - if(!this.managedByCRDs) { - this.managedByCRDs = this.store.peekAll(this.getModelName()) - .toArray().some(item => item.IsManagedByCRD); + if (!this.managedByCRDs) { + this.managedByCRDs = this.store + .peekAll(this.getModelName()) + .toArray() + .some(item => item.IsManagedByCRD); } return this.managedByCRDs; } diff --git a/ui/packages/consul-ui/ember-cli-build.js b/ui/packages/consul-ui/ember-cli-build.js index 3df8ce3808..ab756fc284 100644 --- a/ui/packages/consul-ui/ember-cli-build.js +++ b/ui/packages/consul-ui/ember-cli-build.js @@ -8,18 +8,15 @@ module.exports = function(defaults) { const isProdLike = prodlike.indexOf(env) > -1; const sourcemaps = !isProd; let trees = {}; - if(isProdLike) { + if (isProdLike) { // exclude any component/pageobject.js files from production-like environments - trees.app = new Funnel( - 'app', - { - exclude: [ - 'components/**/pageobject.js', - 'components/**/*.test-support.js', - 'components/**/*.test.js' - ] - } - ); + trees.app = new Funnel('app', { + exclude: [ + 'components/**/pageobject.js', + 'components/**/*.test-support.js', + 'components/**/*.test.js', + ], + }); } let app = new EmberApp( Object.assign({}, defaults, { @@ -31,14 +28,7 @@ module.exports = function(defaults) { includePolyfill: true, }, 'ember-cli-string-helpers': { - only: [ - 'capitalize', - 'lowercase', - 'truncate', - 'uppercase', - 'humanize', - 'titleize' - ], + only: ['capitalize', 'lowercase', 'truncate', 'uppercase', 'humanize', 'titleize'], }, 'ember-cli-math-helpers': { only: ['div'], diff --git a/ui/packages/consul-ui/package.json b/ui/packages/consul-ui/package.json index 84f74b2dc0..eb6226a972 100644 --- a/ui/packages/consul-ui/package.json +++ b/ui/packages/consul-ui/package.json @@ -15,7 +15,7 @@ "lint:dev:js": "eslint -c .dev.eslintrc.js --fix ./*.js ./.*.js app config lib server tests", "lint:hbs": "ember-template-lint .", "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/**/*.*\"", "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", @@ -42,7 +42,7 @@ } }, "lint-staged": { - "{app,config,lib,server,tests}/**/*.js": [ + "{app,config,lib,server,vendor,tests}/**/*.js": [ "prettier --write" ], "app/styles/**/*.*": [ diff --git a/ui/packages/consul-ui/testem.js b/ui/packages/consul-ui/testem.js index 88ff011ccc..3a84839e85 100644 --- a/ui/packages/consul-ui/testem.js +++ b/ui/packages/consul-ui/testem.js @@ -1,12 +1,8 @@ module.exports = { test_page: 'tests/index.html?hidepassed', disable_watching: true, - launch_in_ci: [ - 'Chrome' - ], - launch_in_dev: [ - 'Chrome' - ], + launch_in_ci: ['Chrome'], + launch_in_dev: ['Chrome'], browser_start_timeout: 120, browser_args: { Chrome: { @@ -39,5 +35,5 @@ if (process.env.EMBER_TEST_REPORT) { * https://github.com/trentmwillis/ember-exam/issues/108 */ if (process.env.EMBER_EXAM_PARALLEL) { - module.exports.parallel = -1 + module.exports.parallel = -1; } diff --git a/ui/packages/consul-ui/tests/pages/dc/intentions/index.js b/ui/packages/consul-ui/tests/pages/dc/intentions/index.js index 65b3b8bdaf..9e3b1176ba 100644 --- a/ui/packages/consul-ui/tests/pages/dc/intentions/index.js +++ b/ui/packages/consul-ui/tests/pages/dc/intentions/index.js @@ -3,6 +3,6 @@ export default function(visitable, creatable, clickable, intentions, popoverSele visit: visitable('/:dc/intentions'), intentionList: intentions(), sort: popoverSelect('[data-test-sort-control]'), - ...creatable({}) - } + ...creatable({}), + }; } diff --git a/ui/packages/consul-ui/tests/steps/assertions/model.js b/ui/packages/consul-ui/tests/steps/assertions/model.js index 227f39ed2b..220bdfc163 100644 --- a/ui/packages/consul-ui/tests/steps/assertions/model.js +++ b/ui/packages/consul-ui/tests/steps/assertions/model.js @@ -11,7 +11,11 @@ export default function(scenario, assert, find, currentPage, pauseUntil, plurali return retry(); }, `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) { const obj = find(component); const len = obj[pluralize(model)].filter(function(item) { diff --git a/ui/packages/consul-ui/tests/steps/doubles/model.js b/ui/packages/consul-ui/tests/steps/doubles/model.js index f02bdbf50a..75a0cb981e 100644 --- a/ui/packages/consul-ui/tests/steps/doubles/model.js +++ b/ui/packages/consul-ui/tests/steps/doubles/model.js @@ -14,7 +14,8 @@ export default function(scenario, create) { function(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) { window.localStorage[key] = JSON.stringify(data[key]); }); diff --git a/ui/packages/consul-ui/tests/steps/interactions/click.js b/ui/packages/consul-ui/tests/steps/interactions/click.js index 65a93f8ec1..d1ef934f43 100644 --- a/ui/packages/consul-ui/tests/steps/interactions/click.js +++ b/ui/packages/consul-ui/tests/steps/interactions/click.js @@ -4,22 +4,21 @@ export default function(scenario, find, click) { return click(selector); }) // TODO: Probably nicer to think of better vocab than having the 'without " rule' - .when([ - 'I click (?!")$property(?!")', - 'I click $property on the $component', - 'I click $property on the $component component' - ], function( - property, - component, - next - ) { - try { - if (typeof component === 'string') { - property = `${component}.${property}`; + .when( + [ + 'I click (?!")$property(?!")', + 'I click $property on the $component', + 'I click $property on the $component component', + ], + function(property, component, next) { + try { + if (typeof component === 'string') { + property = `${component}.${property}`; + } + return find(property)(); + } catch (e) { + throw e; } - return find(property)(); - } catch (e) { - throw e; } - }); + ); } diff --git a/ui/packages/consul-ui/tests/unit/helpers/selectable-key-values-test.js b/ui/packages/consul-ui/tests/unit/helpers/selectable-key-values-test.js index 76090b3072..82ef447811 100644 --- a/ui/packages/consul-ui/tests/unit/helpers/selectable-key-values-test.js +++ b/ui/packages/consul-ui/tests/unit/helpers/selectable-key-values-test.js @@ -3,21 +3,36 @@ import { module, test } from 'qunit'; module('Unit | Helper | selectable-key-values', function() { 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.deepEqual(actual.selected, { key: 'key-1', value: 'value-1' }); }); 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']], { - selected: 'key-2', - }); + const actual = selectableKeyValues( + [ + ['key-1', 'value-1'], + ['key-2', 'value-2'], + ], + { + selected: 'key-2', + } + ); assert.equal(actual.items.length, 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) { - const actual = selectableKeyValues([['key-1', 'value-1'], ['key-2', 'value-2']], { - selected: 1, - }); + const actual = selectableKeyValues( + [ + ['key-1', 'value-1'], + ['key-2', 'value-2'], + ], + { + selected: 1, + } + ); assert.equal(actual.items.length, 2); assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' }); }); diff --git a/ui/packages/consul-ui/vendor/metrics-providers/consul.js b/ui/packages/consul-ui/vendor/metrics-providers/consul.js index 9ba2bdc9f2..a77f64e907 100644 --- a/ui/packages/consul-ui/vendor/metrics-providers/consul.js +++ b/ui/packages/consul-ui/vendor/metrics-providers/consul.js @@ -1,51 +1,47 @@ -( - function(global) { - // Current interface is these three methods. - const requiredMethods = [ - 'init', - 'serviceRecentSummarySeries', - 'serviceRecentSummaryStats', - 'upstreamRecentSummaryStats', - 'downstreamRecentSummaryStats', - ]; +(function(global) { + // Current interface is these three methods. + const requiredMethods = [ + 'init', + 'serviceRecentSummarySeries', + 'serviceRecentSummaryStats', + 'upstreamRecentSummaryStats', + 'downstreamRecentSummaryStats', + ]; - // 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 - // transpiling stack. So for now we use a window global as a thin registry for - // these providers. - class Consul { - constructor() { - this.registry = {}; - 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]; - } + // 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 + // transpiling stack. So for now we use a window global as a thin registry for + // these providers. + class Consul { + constructor() { + this.registry = {}; + this.providers = {}; } - 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); diff --git a/ui/packages/consul-ui/vendor/metrics-providers/prometheus.js b/ui/packages/consul-ui/vendor/metrics-providers/prometheus.js index d48f2a9a5e..586edbef48 100644 --- a/ui/packages/consul-ui/vendor/metrics-providers/prometheus.js +++ b/ui/packages/consul-ui/vendor/metrics-providers/prometheus.js @@ -1,6 +1,6 @@ /*eslint no-console: "off"*/ -(function () { - var emptySeries = { unitSuffix: "", labels: {}, data: [] } +(function() { + var emptySeries = { unitSuffix: '', labels: {}, data: [] }; var prometheusProvider = { options: {}, @@ -26,7 +26,9 @@ init: function(options) { this.options = options; 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) { if (queryParams) { var separator = url.indexOf('?') !== -1 ? '&' : '?'; - var qs = Object.keys(queryParams). - map(function(key) { - return encodeURIComponent(key) + "=" + encodeURIComponent(queryParams[key]); - }). - join("&"); + var qs = Object.keys(queryParams) + .map(function(key) { + return encodeURIComponent(key) + '=' + encodeURIComponent(queryParams[key]); + }) + .join('&'); url = url + separator + qs; } // fetch the url along with any headers - return this.options.fetch(url, {headers: headers || {}}).then( - function(response) { - if(response.ok) { - return response.json(); - } else { - // throw a statusCode error if any errors are received - var e = new Error('HTTP Error: ' + response.statusText); - e.statusCode = response.status; - throw e; - } + return this.options.fetch(url, { headers: headers || {} }).then(function(response) { + if (response.ok) { + return response.json(); + } else { + // throw a statusCode error if any errors are received + var e = new Error('HTTP Error: ' + response.statusText); + e.statusCode = response.status; + throw e; } - ); + }); }, /** @@ -114,13 +114,13 @@ */ serviceRecentSummarySeries: function(serviceDC, namespace, serviceName, protocol, options) { // Fetch time-series - var series = [] - var labels = [] + var series = []; + var labels = []; // Set the start and end range here so that all queries end up with // identical time axes. Later we might accept these as options. - var now = (new Date()).getTime()/1000; - options.start = now - (15*60); + var now = new Date().getTime() / 1000; + options.start = now - 15 * 60; options.end = now; if (this.hasL7Metrics(protocol)) { @@ -169,18 +169,18 @@ // Fetch stats var stats = []; if (this.hasL7Metrics(protocol)) { - stats.push(this.fetchRPS(serviceName, "service", options)) - stats.push(this.fetchER(serviceName, "service", options)) - stats.push(this.fetchPercentile(50, serviceName, "service", options)) - stats.push(this.fetchPercentile(99, serviceName, "service", options)) + stats.push(this.fetchRPS(serviceName, 'service', options)); + stats.push(this.fetchER(serviceName, 'service', options)); + stats.push(this.fetchPercentile(50, serviceName, 'service', options)); + stats.push(this.fetchPercentile(99, serviceName, 'service', options)); } else { // Fallback to just L4 metrics. - stats.push(this.fetchConnRate(serviceName, "service", options)) - stats.push(this.fetchServiceRx(serviceName, "service", options)) - stats.push(this.fetchServiceTx(serviceName, "service", options)) - stats.push(this.fetchServiceNoRoute(serviceName, "service", options)) + stats.push(this.fetchConnRate(serviceName, 'service', options)); + stats.push(this.fetchServiceRx(serviceName, 'service', options)); + stats.push(this.fetchServiceTx(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) { - return this.fetchRecentSummaryStats(serviceName, "upstream", options) + return this.fetchRecentSummaryStats(serviceName, 'upstream', options); }, /** @@ -260,7 +260,7 @@ * } */ downstreamRecentSummaryStats: function(serviceDC, namespace, serviceName, options) { - return this.fetchRecentSummaryStats(serviceName, "downstream", options) + return this.fetchRecentSummaryStats(serviceName, 'downstream', 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. // HTTP - stats.push(this.fetchRPS(serviceName, type, options)) - stats.push(this.fetchER(serviceName, type, options)) - stats.push(this.fetchPercentile(50, serviceName, type, options)) - stats.push(this.fetchPercentile(99, serviceName, type, options)) + stats.push(this.fetchRPS(serviceName, type, options)); + stats.push(this.fetchER(serviceName, type, options)); + stats.push(this.fetchPercentile(50, serviceName, type, options)); + stats.push(this.fetchPercentile(99, serviceName, type, options)); // L4 - stats.push(this.fetchConnRate(serviceName, type, options)) - stats.push(this.fetchServiceRx(serviceName, type, options)) - stats.push(this.fetchServiceTx(serviceName, type, options)) - stats.push(this.fetchServiceNoRoute(serviceName, type, options)) + stats.push(this.fetchConnRate(serviceName, type, options)); + stats.push(this.fetchServiceRx(serviceName, type, options)); + stats.push(this.fetchServiceTx(serviceName, type, options)); + stats.push(this.fetchServiceNoRoute(serviceName, type, options)); - return this.fetchStatsGrouped(stats) + return this.fetchStatsGrouped(stats); }, hasL7Metrics: function(protocol) { - return protocol === "http" || protocol === "http2" || protocol === "grpc" + return protocol === 'http' || protocol === 'http2' || protocol === 'grpc'; }, fetchStats: function(statsPromises) { - var all = Promise.all(statsPromises). - then(function(results){ + var all = Promise.all(statsPromises).then(function(results) { var data = { - stats: [] - } + stats: [], + }; // Add all non-empty stats for (var i = 0; i < statsPromises.length; i++) { if (results[i].value) { data.stats.push(results[i]); } } - return data - }) + return data; + }); // Fetch the metrics async, and return a promise to the result. - return all + return all; }, fetchStatsGrouped: function(statsPromises) { - var all = Promise.all(statsPromises). - then(function(results){ + var all = Promise.all(statsPromises).then(function(results) { var data = { - stats: {} - } + stats: {}, + }; // Add all non-empty stats for (var i = 0; i < statsPromises.length; i++) { if (results[i]) { for (var group in results[i]) { if (!results[i].hasOwnProperty(group)) continue; 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. - return all + return all; }, reformatSeries: function(unitSuffix, labelMap) { return function(response) { // Handle empty result sets gracefully. - if (!response.data || !response.data.result || response.data.result.length == 0 - || !response.data.result[0].values - || response.data.result[0].values.length == 0) { + if ( + !response.data || + !response.data.result || + response.data.result.length == 0 || + !response.data.result[0].values || + response.data.result[0].values.length == 0 + ) { return emptySeries; } // Reformat the prometheus data to be the format we want with stacked @@ -362,306 +364,315 @@ return { unitSuffix: unitSuffix, 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 // error rate as a separate series so that they stack to show the full // request rate. Some creative label replacement makes this possible in // one query. - var q = `sum by (label) (`+ + var q = + `sum by (label) (` + // The outer label_replace catches 5xx error and relabels them as // err=yes - `label_replace(`+ - // The inner label_replace relabels all !5xx rates as err=no so they - // will get summed together. - `label_replace(`+ - // 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])`+ - // ... inner replacement matches all code classes except "5" and - // applies err=no - `, "label", "Successes", "envoy_response_code_class", "[^5]")`+ - // ... outer replacement matches code=5 and applies err=yes - `, "label", "Errors", "envoy_response_code_class", "5")`+ - `)` + `label_replace(` + + // The inner label_replace relabels all !5xx rates as err=no so they + // will get summed together. + `label_replace(` + + // 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])` + + // ... inner replacement matches all code classes except "5" and + // applies err=no + `, "label", "Successes", "envoy_response_code_class", "[^5]")` + + // ... outer replacement matches code=5 and applies err=yes + `, "label", "Errors", "envoy_response_code_class", "5")` + + `)`; var labelMap = { 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.', }; - return this.fetchSeries(q, options) - .then(this.reformatSeries(" rps", labelMap)) + return this.fetchSeries(q, options).then(this.reformatSeries(' rps', labelMap)); }, - fetchDataRateSeries: function(serviceName, options){ + fetchDataRateSeries: function(serviceName, options) { // 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 // being summed together. - `label_replace(`+ - // Get the tx rate - `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 - `, "label", "Outbound", "__name__", ".*"`+ + `label_replace(` + + // Get the tx rate + `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 + `, "label", "Outbound", "__name__", ".*"` + // Union those vectors with the RX ones - `) or label_replace(`+ - // Get the rx rate - `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 - `, "label", "Inbound", "__name__", ".*"`+ - `)`+ - `)` + `) or label_replace(` + + // Get the rx rate + `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 + `, "label", "Inbound", "__name__", ".*"` + + `)` + + `)`; var labelMap = { Total: 'Total bandwidth', 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.', }; - return this.fetchSeries(q, options) - .then(this.reformatSeries("bps", labelMap)) + return this.fetchSeries(q, options).then(this.reformatSeries('bps', labelMap)); }, makeSubject: function(serviceName, type) { - if (type == "upstream") { + if (type == 'upstream') { // {{GROUP}} is a placeholder that is replaced by the upstream name return `${serviceName} → {{GROUP}}`; } - if (type == "downstream") { + if (type == 'downstream') { // {{GROUP}} is a placeholder that is replaced by the downstream name return `{{GROUP}} → ${serviceName}`; } - return serviceName + return serviceName; }, makeHTTPSelector: function(serviceName, type) { // Downstreams are totally different - if (type == "downstream") { - return `consul_service="${serviceName}"` + if (type == 'downstream') { + return `consul_service="${serviceName}"`; } - var lc = `local_cluster="${serviceName}"` - if (type == "upstream") { + var lc = `local_cluster="${serviceName}"`; + if (type == 'upstream') { lc += `,envoy_http_conn_manager_prefix=~"upstream_.*"`; } else { // 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) { // Downstreams are totally different - if (type == "downstream") { - return `consul_service="${serviceName}"` + if (type == 'downstream') { + return `consul_service="${serviceName}"`; } - var lc = `local_cluster="${serviceName}"` - if (type == "upstream") { + var lc = `local_cluster="${serviceName}"`; + if (type == 'upstream') { lc += `,envoy_tcp_prefix=~"upstream_.*"`; } else { // 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) { - if (type == "upstream") { - q += " by (envoy_http_conn_manager_prefix)" + if (type == 'upstream') { + q += ' by (envoy_http_conn_manager_prefix)'; // Extract the raw upstream service name to group results by - q = this.upstreamRelabelQueryHTTP(q) - } else if (type == "downstream") { - q += " by (local_cluster)" - q = this.downstreamRelabelQuery(q) + q = this.upstreamRelabelQueryHTTP(q); + } else if (type == 'downstream') { + q += ' by (local_cluster)'; + q = this.downstreamRelabelQuery(q); } - return q + return q; }, groupQueryTCP: function(type, q) { - if (type == "upstream") { - q += " by (envoy_tcp_prefix)" + if (type == 'upstream') { + q += ' by (envoy_tcp_prefix)'; // Extract the raw upstream service name to group results by - q = this.upstreamRelabelQueryTCP(q) - } else if (type == "downstream") { - q += " by (local_cluster)" - q = this.downstreamRelabelQuery(q) + q = this.upstreamRelabelQueryTCP(q); + } else if (type == 'downstream') { + q += ' by (local_cluster)'; + q = this.downstreamRelabelQuery(q); } - return q + return 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) { - 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) { - return `label_replace(${q}, "downstream", "$1", "local_cluster", "(.*)")` + return `label_replace(${q}, "downstream", "$1", "local_cluster", "(.*)")`; }, groupBy: function(type) { - if (type == "service") { - return false + if (type == 'service') { + return false; } return type; }, metricPrefixHTTP: function(type) { - if (type == "downstream") { - return "envoy_cluster_upstream_rq" + if (type == 'downstream') { + return 'envoy_cluster_upstream_rq'; } - return "envoy_http_downstream_rq"; + return 'envoy_http_downstream_rq'; }, metricPrefixTCP: function(type) { - if (type == "downstream") { - return "envoy_cluster_upstream_cx" + if (type == 'downstream') { + return 'envoy_cluster_upstream_cx'; } - return "envoy_tcp_downstream_cx"; + return 'envoy_tcp_downstream_cx'; }, - fetchRPS: function(serviceName, type, options){ - var sel = this.makeHTTPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var metricPfx = this.metricPrefixHTTP(type) - var q = `sum(rate(${metricPfx}_completed{${sel}}[15m]))` - return this.fetchStat(this.groupQueryHTTP(type, q), - "RPS", + fetchRPS: function(serviceName, type, options) { + var sel = this.makeHTTPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var metricPfx = this.metricPrefixHTTP(type); + var q = `sum(rate(${metricPfx}_completed{${sel}}[15m]))`; + return this.fetchStat( + this.groupQueryHTTP(type, q), + 'RPS', `${subject} request rate averaged over the last 15 minutes`, shortNumStr, this.groupBy(type) - ) + ); }, - fetchER: function(serviceName, type, options){ - var sel = this.makeHTTPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var groupBy = "" - if (type == "upstream") { - groupBy += " by (envoy_http_conn_manager_prefix)" - } else if (type == "downstream") { - groupBy += " by (local_cluster)" + fetchER: function(serviceName, type, options) { + var sel = this.makeHTTPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var groupBy = ''; + if (type == 'upstream') { + groupBy += ' by (envoy_http_conn_manager_prefix)'; + } else if (type == 'downstream') { + groupBy += ' by (local_cluster)'; } - 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}` - if (type == "upstream") { - q = this.upstreamRelabelQueryHTTP(q) - } else if (type == "downstream") { - q = this.downstreamRelabelQuery(q) + 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}`; + if (type == 'upstream') { + q = this.upstreamRelabelQueryHTTP(q); + } else if (type == 'downstream') { + q = this.downstreamRelabelQuery(q); } - return this.fetchStat(q, - "ER", + return this.fetchStat( + q, + 'ER', `Percentage of ${subject} requests which were 5xx status over the last 15 minutes`, - function(val){ - return shortNumStr(val)+"%" + function(val) { + return shortNumStr(val) + '%'; }, this.groupBy(type) - ) + ); }, - fetchPercentile: function(percentile, serviceName, type, options){ - var sel = this.makeHTTPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var groupBy = "le" - if (type == "upstream") { - groupBy += ",envoy_http_conn_manager_prefix" - } else if (type == "downstream") { - groupBy += ",local_cluster" + fetchPercentile: function(percentile, serviceName, type, options) { + var sel = this.makeHTTPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var groupBy = 'le'; + if (type == 'upstream') { + groupBy += ',envoy_http_conn_manager_prefix'; + } else if (type == 'downstream') { + groupBy += ',local_cluster'; } - var metricPfx = this.metricPrefixHTTP(type) - var q = `histogram_quantile(${percentile/100}, sum by(${groupBy}) (rate(${metricPfx}_time_bucket{${sel}}[15m])))` - if (type == "upstream") { - q = this.upstreamRelabelQueryHTTP(q) - } else if (type == "downstream") { - q = this.downstreamRelabelQuery(q) + var metricPfx = this.metricPrefixHTTP(type); + var q = `histogram_quantile(${percentile / + 100}, sum by(${groupBy}) (rate(${metricPfx}_time_bucket{${sel}}[15m])))`; + if (type == 'upstream') { + q = this.upstreamRelabelQueryHTTP(q); + } else if (type == 'downstream') { + q = this.downstreamRelabelQuery(q); } - return this.fetchStat(q, + return this.fetchStat( + q, `P${percentile}`, `${subject} ${percentile}th percentile request service time over the last 15 minutes`, shortTimeStr, this.groupBy(type) - ) + ); }, fetchConnRate: function(serviceName, type, options) { - var sel = this.makeTCPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var metricPfx = this.metricPrefixTCP(type) - var q = `sum(rate(${metricPfx}_total{${sel}}[15m]))` - return this.fetchStat(this.groupQueryTCP(type, q), - "CR", + var sel = this.makeTCPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var metricPfx = this.metricPrefixTCP(type); + var q = `sum(rate(${metricPfx}_total{${sel}}[15m]))`; + return this.fetchStat( + this.groupQueryTCP(type, q), + 'CR', `${subject} inbound TCP connections per second averaged over the last 15 minutes`, shortNumStr, this.groupBy(type) - ) + ); }, fetchServiceRx: function(serviceName, type, options) { - var sel = this.makeTCPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var metricPfx = this.metricPrefixTCP(type) - var q = `8 * sum(rate(${metricPfx}_rx_bytes_total{${sel}}[15m]))` - return this.fetchStat(this.groupQueryTCP(type, q), - "RX", + var sel = this.makeTCPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var metricPfx = this.metricPrefixTCP(type); + var q = `8 * sum(rate(${metricPfx}_rx_bytes_total{${sel}}[15m]))`; + return this.fetchStat( + this.groupQueryTCP(type, q), + 'RX', `${subject} received bits per second averaged over the last 15 minutes`, shortNumStr, this.groupBy(type) - ) + ); }, fetchServiceTx: function(serviceName, type, options) { - var sel = this.makeTCPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var metricPfx = this.metricPrefixTCP(type) - var q = `8 * sum(rate(${metricPfx}_tx_bytes_total{${sel}}[15m]))` - var self = this - return this.fetchStat(this.groupQueryTCP(type, q), - "TX", + var sel = this.makeTCPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var metricPfx = this.metricPrefixTCP(type); + var q = `8 * sum(rate(${metricPfx}_tx_bytes_total{${sel}}[15m]))`; + var self = this; + return this.fetchStat( + this.groupQueryTCP(type, q), + 'TX', `${subject} transmitted bits per second averaged over the last 15 minutes`, shortNumStr, this.groupBy(type) - ) + ); }, fetchServiceNoRoute: function(serviceName, type, options) { - var sel = this.makeTCPSelector(serviceName, type) - var subject = this.makeSubject(serviceName, type) - var metricPfx = this.metricPrefixTCP(type) - var metric = "_no_route" - if (type == "downstream") { - metric = "_connect_fail" + var sel = this.makeTCPSelector(serviceName, type); + var subject = this.makeSubject(serviceName, type); + var metricPfx = this.metricPrefixTCP(type); + var metric = '_no_route'; + if (type == 'downstream') { + metric = '_connect_fail'; } - var q = `sum(rate(${metricPfx}${metric}{${sel}}[15m]))` - return this.fetchStat(this.groupQueryTCP(type, q), - "NR", + var q = `sum(rate(${metricPfx}${metric}{${sel}}[15m]))`; + return this.fetchStat( + this.groupQueryTCP(type, q), + 'NR', `${subject} unroutable (failed) connections per second averaged over the last 15 minutes`, shortNumStr, this.groupBy(type) - ) + ); }, fetchStat: function(promql, label, desc, formatter, groupBy) { if (!groupBy) { // If we don't have a grouped result and its just a single stat, return // no result as a zero not a missing stat. - promql += " OR on() vector(0)"; + promql += ' OR on() vector(0)'; } //console.log(promql) var params = { query: promql, - time: (new Date).getTime()/1000 - } - return this.httpGet("/api/v1/query", params).then(function(response){ + time: new Date().getTime() / 1000, + }; + return this.httpGet('/api/v1/query', params).then(function(response) { if (!groupBy) { // 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 { label: label, desc: desc, - value: formatter(v) - } + value: formatter(v), + }; } var data = {}; @@ -672,11 +683,11 @@ data[groupName] = { label: label, desc: desc.replace('{{GROUP}}', groupName), - value: formatter(v) - } + value: formatter(v), + }; } return data; - }) + }); }, fetchSeries: function(promql, options) { @@ -684,23 +695,23 @@ query: promql, start: options.start, end: options.end, - step: "10s", - timeout: "8s" - } - return this.httpGet("/api/v1/query_range", params) + step: '10s', + timeout: '8s', + }; + return this.httpGet('/api/v1/query_range', params); }, - - } + }; // Helper functions function shortNumStr(n) { if (n < 1e3) { - if (Number.isInteger(n)) return ""+n + if (Number.isInteger(n)) return '' + n; if (n >= 100) { // Go to 3 significant figures but wrap it in Number to avoid scientific // notation lie 2.3e+2 for 230. - return Number(n.toPrecision(3)) - } if (n < 1) { + return Number(n.toPrecision(3)); + } + if (n < 1) { // Very small numbers show with limited precision to prevent long string // of 0.000000. return Number(n.toFixed(2)); @@ -709,29 +720,28 @@ return Number(n.toPrecision(2)); } } - if (n >= 1e3 && n < 1e6) return +(n / 1e3).toPrecision(3) + "k"; - if (n >= 1e6 && n < 1e9) return +(n / 1e6).toPrecision(3) + "m"; - if (n >= 1e9 && n < 1e12) return +(n / 1e9).toPrecision(3) + "g"; - if (n >= 1e12) return +(n / 1e12).toFixed(0) + "t"; + if (n >= 1e3 && n < 1e6) return +(n / 1e3).toPrecision(3) + 'k'; + if (n >= 1e6 && n < 1e9) return +(n / 1e6).toPrecision(3) + 'm'; + if (n >= 1e9 && n < 1e12) return +(n / 1e9).toPrecision(3) + 'g'; + if (n >= 1e12) return +(n / 1e12).toFixed(0) + 't'; } function shortTimeStr(n) { - if (n < 1e3) return Math.round(n) + "ms"; + if (n < 1e3) return Math.round(n) + 'ms'; - var secs = n / 1e3 - if (secs < 60) return secs.toFixed(1) + "s" + var secs = n / 1e3; + if (secs < 60) return secs.toFixed(1) + 's'; - var mins = secs/60 - if (mins < 60) return mins.toFixed(1) + "m" + var mins = secs / 60; + if (mins < 60) return mins.toFixed(1) + 'm'; - var hours = mins/60 - if (hours < 24) return hours.toFixed(1) + "h" + var hours = mins / 60; + if (hours < 24) return hours.toFixed(1) + 'h'; - var days = hours/24 - return days.toFixed(1) + "d" + var days = hours / 24; + return days.toFixed(1) + 'd'; } /* global consul:writable */ - window.consul.registerMetricsProvider("prometheus", prometheusProvider) - -}()); + window.consul.registerMetricsProvider('prometheus', prometheusProvider); +})();