Updates to status API, re-align status page (#10180)
* [status] Only provide latest metrics * [status] Snake case requests object * [status] Add build snapshot flag, last updated time * [status] Re-add requests per second * [status] Add uptime * [status] collection_time -> collection_interval, add memory object * [status] Add api tests * [status] Add breaking changes docs * [status] Remove metrics config * [status] nest load under cpu, shorten memory and average * [status] collection_time -> collection_interval * [status api] Unnest heap, rename load to load average
This commit is contained in:
parent
408a2e6506
commit
9bc4d9158b
|
@ -10,4 +10,14 @@ your application to Kibana 6.0.
|
||||||
Kibana 6.0 will only support Painless and Lucene expression based scripts.
|
Kibana 6.0 will only support Painless and Lucene expression based scripts.
|
||||||
|
|
||||||
|
|
||||||
*Impact:* You will need to migrate your groovy, python, javascript, etc. scripted fields to Painless or Lucene expressions.
|
*Impact:* You will need to migrate your groovy, python, javascript, etc. scripted fields to Painless or Lucene expressions.
|
||||||
|
|
||||||
|
[float]
|
||||||
|
=== Changed response format of status API
|
||||||
|
*Details:* In an effort to align with our style guidelines and provide a digestible response,
|
||||||
|
the status API has changed:
|
||||||
|
|
||||||
|
* Properties are now snake cased and several have been renamed
|
||||||
|
* Metrics now provide the latest available data instead of samples over time
|
||||||
|
|
||||||
|
*Impact:* You will need to update anything using the status API and expecting the previous response format.
|
|
@ -14,6 +14,9 @@ module.exports = function formatNumber(num, which) {
|
||||||
case 'ms':
|
case 'ms':
|
||||||
postfix = ' ms';
|
postfix = ' ms';
|
||||||
break;
|
break;
|
||||||
|
case 'integer':
|
||||||
|
format = '0';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return numeral(num).format(format) + postfix;
|
return numeral(num).format(format) + postfix;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
// Turns thisIsASentence to
|
|
||||||
// This Is A Sentence
|
|
||||||
module.exports = function toTitleCase(name) {
|
|
||||||
return name
|
|
||||||
.split(/(?=[A-Z])/)
|
|
||||||
.map(function (word) { return word[0].toUpperCase() + _.rest(word).join(''); })
|
|
||||||
.join(' ');
|
|
||||||
};
|
|
|
@ -10,8 +10,8 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="row metrics_wrapper">
|
<div class="row metrics_wrapper">
|
||||||
<div ng-repeat="(name, data) in ui.metrics">
|
<div ng-repeat="metric in ui.metrics">
|
||||||
<status-page-metric name="{{name}}" data="data"></status-page-metric>
|
<status-page-metric metric="metric"></status-page-metric>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,39 @@ const chrome = require('ui/chrome')
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = resp.data;
|
const data = resp.data;
|
||||||
ui.metrics = data.metrics;
|
const metrics = data.metrics;
|
||||||
ui.name = data.name;
|
if (metrics) {
|
||||||
|
ui.metrics = [{
|
||||||
|
name: 'Heap Total',
|
||||||
|
value: _.get(metrics, 'process.mem.heap_max_in_bytes'),
|
||||||
|
type: 'byte'
|
||||||
|
}, {
|
||||||
|
name: 'Heap Used',
|
||||||
|
value: _.get(metrics, 'process.mem.heap_used_in_bytes'),
|
||||||
|
type: 'byte'
|
||||||
|
}, {
|
||||||
|
name: 'Load',
|
||||||
|
value: [
|
||||||
|
_.get(metrics, 'os.cpu.load_average.1m'),
|
||||||
|
_.get(metrics, 'os.cpu.load_average.5m'),
|
||||||
|
_.get(metrics, 'os.cpu.load_average.15m')
|
||||||
|
],
|
||||||
|
type: 'float'
|
||||||
|
}, {
|
||||||
|
name: 'Response Time Avg',
|
||||||
|
value: _.get(metrics, 'response_times.avg_in_millis'),
|
||||||
|
type: 'ms'
|
||||||
|
}, {
|
||||||
|
name: 'Response Time Max',
|
||||||
|
value: _.get(metrics, 'response_times.max_in_millis'),
|
||||||
|
type: 'ms'
|
||||||
|
}, {
|
||||||
|
name: 'Requests Per Second',
|
||||||
|
value: _.get(metrics, 'requests.total') * 1000 / _.get(metrics, 'collection_interval_in_millis')
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.name = data.name;
|
||||||
ui.statuses = data.status.statuses;
|
ui.statuses = data.status.statuses;
|
||||||
|
|
||||||
const overall = data.status.overall;
|
const overall = data.status.overall;
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
display: block;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="status_metric_wrapper col-md-4">
|
<div class="status_metric_wrapper col-md-4">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h3 class="title">{{metric.extendedTitle}}</h3>
|
<h3 class="title">{{ metric.name }}</h3>
|
||||||
<h4 class="average">{{ metric.averages.join(', ') }}</h4>
|
<h4 class="average">{{ metric.value | statusMetric: metric.type}}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,74 +2,28 @@ import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import numeral from 'numeral';
|
import numeral from 'numeral';
|
||||||
|
|
||||||
import toTitleCase from './lib/to_title_case';
|
|
||||||
import formatNumber from './lib/format_number';
|
import formatNumber from './lib/format_number';
|
||||||
import readStatData from './lib/read_stat_data';
|
import readStatData from './lib/read_stat_data';
|
||||||
import uiModules from 'ui/modules';
|
import uiModules from 'ui/modules';
|
||||||
import statusPageMetricTemplate from 'plugins/status_page/status_page_metric.html';
|
import statusPageMetricTemplate from 'plugins/status_page/status_page_metric.html';
|
||||||
|
|
||||||
function calcAvg(metricList, metricNumberType) {
|
|
||||||
return metricList.map(function (data) {
|
|
||||||
const uglySum = data.values.reduce(function (sumSoFar, vector) {
|
|
||||||
return sumSoFar + vector.y;
|
|
||||||
}, 0);
|
|
||||||
return formatNumber(uglySum / data.values.length, metricNumberType);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
uiModules
|
uiModules
|
||||||
.get('kibana', [])
|
.get('kibana', [])
|
||||||
|
.filter('statusMetric', function () {
|
||||||
|
return function (input, type) {
|
||||||
|
const metrics = [].concat(input);
|
||||||
|
return metrics.map(function (metric) {
|
||||||
|
return formatNumber(metric, type);
|
||||||
|
}).join(', ');
|
||||||
|
};
|
||||||
|
})
|
||||||
.directive('statusPageMetric', function () {
|
.directive('statusPageMetric', function () {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
template: statusPageMetricTemplate,
|
template: statusPageMetricTemplate,
|
||||||
scope: {
|
scope: {
|
||||||
name: '@',
|
metric: '=',
|
||||||
data: '='
|
|
||||||
},
|
},
|
||||||
controllerAs: 'metric',
|
controllerAs: 'metric'
|
||||||
controller: function ($scope) {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
self.name = $scope.name;
|
|
||||||
self.title = toTitleCase(self.name);
|
|
||||||
self.extendedTitle = self.title;
|
|
||||||
self.numberType = 'precise';
|
|
||||||
self.seriesNames = [];
|
|
||||||
|
|
||||||
switch (self.name) {
|
|
||||||
case 'heapTotal':
|
|
||||||
case 'heapUsed':
|
|
||||||
self.numberType = 'byte';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'responseTimeAvg':
|
|
||||||
case 'responseTimeMax':
|
|
||||||
self.numberType = 'ms';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'load':
|
|
||||||
self.seriesNames = ['1min', '5min', '15min'];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$watch('data', function (data) {
|
|
||||||
self.rawData = data;
|
|
||||||
self.chartData = readStatData(self.rawData, self.seriesNames);
|
|
||||||
self.averages = calcAvg(self.chartData, self.numberType);
|
|
||||||
|
|
||||||
let unit = '';
|
|
||||||
self.averages = self.averages.map(function (average) {
|
|
||||||
const parts = average.split(' ');
|
|
||||||
const value = parts.shift();
|
|
||||||
unit = parts.join(' ');
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
self.extendedTitle = self.title;
|
|
||||||
if (unit) {
|
|
||||||
self.extendedTitle = `${self.extendedTitle} (${unit})`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
56
src/server/status/__tests__/metrics.js
Normal file
56
src/server/status/__tests__/metrics.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
import expect from 'expect.js';
|
||||||
|
|
||||||
|
import { getMetrics } from '../metrics';
|
||||||
|
|
||||||
|
describe('Metrics', function () {
|
||||||
|
const mockOps = {
|
||||||
|
'requests': { '5603': { 'total': 22, 'disconnects': 0, 'statusCodes': { '200': 22 } } },
|
||||||
|
'responseTimes': { '5603': { 'avg': 1.8636363636363635, 'max': 4 } },
|
||||||
|
'sockets': {
|
||||||
|
'http': { 'total': 0 },
|
||||||
|
'https': { 'total': 0 }
|
||||||
|
},
|
||||||
|
'osload': [2.20751953125, 2.02294921875, 1.89794921875],
|
||||||
|
'osmem': { 'total': 17179869184, 'free': 102318080 },
|
||||||
|
'osup': 1008991,
|
||||||
|
'psup': 7.168,
|
||||||
|
'psmem': { 'rss': 193716224, 'heapTotal': 168194048, 'heapUsed': 130553400 },
|
||||||
|
'concurrents': { '5603': 0 },
|
||||||
|
'psdelay': 1.6091690063476562,
|
||||||
|
'host': '123'
|
||||||
|
};
|
||||||
|
const config = {
|
||||||
|
ops: {
|
||||||
|
interval: 5000
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5603
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let metrics;
|
||||||
|
beforeEach(() => {
|
||||||
|
metrics = getMetrics({
|
||||||
|
event: _.cloneDeep(mockOps),
|
||||||
|
config: {
|
||||||
|
get: path => _.get(config, path)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should snake case the request object', () => {
|
||||||
|
expect(metrics.requests.status_codes).not.to.be(undefined);
|
||||||
|
expect(metrics.requests.statusCodes).to.be(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide defined metrics', () => {
|
||||||
|
(function checkMetrics(currentMetric) {
|
||||||
|
_.forOwn(currentMetric, value => {
|
||||||
|
if (typeof value === 'object') return checkMetrics(value);
|
||||||
|
expect(currentMetric).not.to.be(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
}(metrics));
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,24 +7,29 @@ export default function (kbnServer, server, config) {
|
||||||
kbnServer.status = new ServerStatus(kbnServer.server);
|
kbnServer.status = new ServerStatus(kbnServer.server);
|
||||||
|
|
||||||
if (server.plugins['even-better']) {
|
if (server.plugins['even-better']) {
|
||||||
kbnServer.mixin(require('./metrics'));
|
kbnServer.mixin(require('./metrics').collectMetrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapAuth = wrapAuthConfig(config.get('status.allowAnonymous'));
|
const wrapAuth = wrapAuthConfig(config.get('status.allowAnonymous'));
|
||||||
|
const matchSnapshot = /-SNAPSHOT$/;
|
||||||
server.route(wrapAuth({
|
server.route(wrapAuth({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/api/status',
|
path: '/api/status',
|
||||||
handler: function (request, reply) {
|
handler: function (request, reply) {
|
||||||
return reply({
|
const status = {
|
||||||
name: config.get('server.name'),
|
name: config.get('server.name'),
|
||||||
version: config.get('pkg.version'),
|
|
||||||
buildNum: config.get('pkg.buildNum'),
|
|
||||||
buildSha: config.get('pkg.buildSha'),
|
|
||||||
uuid: config.get('server.uuid'),
|
uuid: config.get('server.uuid'),
|
||||||
|
version: {
|
||||||
|
number: config.get('pkg.version').replace(matchSnapshot, ''),
|
||||||
|
build_hash: config.get('pkg.buildSha'),
|
||||||
|
build_number: config.get('pkg.buildNum'),
|
||||||
|
build_snapshot: matchSnapshot.test(config.get('pkg.version'))
|
||||||
|
},
|
||||||
status: kbnServer.status.toJSON(),
|
status: kbnServer.status.toJSON(),
|
||||||
metrics: kbnServer.metrics
|
metrics: kbnServer.metrics
|
||||||
});
|
};
|
||||||
|
|
||||||
|
return reply(status);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,40 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Samples from './samples';
|
import Samples from './samples';
|
||||||
module.exports = function (kbnServer, server, config) {
|
import { keysToSnakeCaseShallow } from '../../utils/case_conversion';
|
||||||
let lastReport = Date.now();
|
|
||||||
|
|
||||||
kbnServer.metrics = new Samples(12);
|
|
||||||
|
|
||||||
|
export function collectMetrics(kbnServer, server, config) {
|
||||||
server.plugins['even-better'].monitor.on('ops', function (event) {
|
server.plugins['even-better'].monitor.on('ops', function (event) {
|
||||||
const now = Date.now();
|
kbnServer.metrics = getMetrics({ event, config });
|
||||||
const secSinceLast = (now - lastReport) / 1000;
|
|
||||||
lastReport = now;
|
|
||||||
|
|
||||||
const port = config.get('server.port');
|
|
||||||
const requests = _.get(event, ['requests', port, 'total'], 0);
|
|
||||||
const requestsPerSecond = requests / secSinceLast;
|
|
||||||
|
|
||||||
kbnServer.metrics.add({
|
|
||||||
heapTotal: _.get(event, 'psmem.heapTotal'),
|
|
||||||
heapUsed: _.get(event, 'psmem.heapUsed'),
|
|
||||||
load: event.osload,
|
|
||||||
responseTimeAvg: _.get(event, ['responseTimes', port, 'avg']),
|
|
||||||
responseTimeMax: _.get(event, ['responseTimes', port, 'max']),
|
|
||||||
requestsPerSecond: requestsPerSecond
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export function getMetrics({ event, config }) {
|
||||||
|
const port = config.get('server.port');
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
return {
|
||||||
|
last_updated: timestamp,
|
||||||
|
collection_interval_in_millis: config.get('ops.interval'),
|
||||||
|
uptime_in_millis: process.uptime() * 1000,
|
||||||
|
process: {
|
||||||
|
mem: {
|
||||||
|
heap_max_in_bytes: _.get(event, 'psmem.heapTotal'),
|
||||||
|
heap_used_in_bytes: _.get(event, 'psmem.heapUsed')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
os: {
|
||||||
|
cpu: {
|
||||||
|
load_average: {
|
||||||
|
'1m': _.get(event, 'osload.0'),
|
||||||
|
'5m': _.get(event, 'osload.1'),
|
||||||
|
'15m': _.get(event, 'osload.1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response_times: {
|
||||||
|
avg_in_millis: _.get(event, ['responseTimes', port, 'avg']),
|
||||||
|
max_in_millis: _.get(event, ['responseTimes', port, 'max'])
|
||||||
|
},
|
||||||
|
requests: keysToSnakeCaseShallow(_.get(event, ['requests', port])),
|
||||||
|
concurrent_connections: _.get(event, ['concurrents', port])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import RefreshKibanaIndexProvider from 'plugins/kibana/management/sections/indices/_refresh_kibana_index';
|
import RefreshKibanaIndexProvider from 'plugins/kibana/management/sections/indices/_refresh_kibana_index';
|
||||||
import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../core_plugins/kibana/common/lib/case_conversion';
|
import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../utils/case_conversion';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import chrome from 'ui/chrome';
|
import chrome from 'ui/chrome';
|
||||||
|
|
Loading…
Reference in a new issue