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,
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',
}),
},
],
};

View File

@ -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,
},
};

View File

@ -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;

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 = {
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),
};
};

View File

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

View File

@ -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;
}

View File

@ -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'],

View File

@ -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/**/*.*": [

View File

@ -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;
}

View File

@ -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({}),
};
}

View File

@ -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) {

View File

@ -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]);
});

View File

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

View File

@ -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' });
});

View File

@ -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);

View File

@ -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} &rarr; {{GROUP}}`;
}
if (type == "downstream") {
if (type == 'downstream') {
// {{GROUP}} is a placeholder that is replaced by the downstream name
return `{{GROUP}} &rarr; ${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',
`<b>${subject}</b> 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 <b>${subject}</b> 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}`,
`<b>${subject}</b> ${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',
`<b>${subject}</b> 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',
`<b>${subject}</b> 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',
`<b>${subject}</b> 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',
`<b>${subject}</b> 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);
})();