mirror of https://github.com/status-im/consul.git
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:
parent
71da0209bf
commit
6423a2c10d
|
@ -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',
|
||||||
})
|
}),
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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),
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'],
|
||||||
|
|
|
@ -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/**/*.*": [
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({}),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' });
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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} → {{GROUP}}`;
|
return `${serviceName} → {{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}} → ${serviceName}`;
|
return `{{GROUP}} → ${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);
|
||||||
|
})();
|
||||||
}());
|
|
||||||
|
|
Loading…
Reference in New Issue