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
|
@ -11,3 +11,13 @@ 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.
|
||||
|
||||
[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':
|
||||
postfix = ' ms';
|
||||
break;
|
||||
case 'integer':
|
||||
format = '0';
|
||||
break;
|
||||
}
|
||||
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>
|
||||
|
||||
<div class="row metrics_wrapper">
|
||||
<div ng-repeat="(name, data) in ui.metrics">
|
||||
<status-page-metric name="{{name}}" data="data"></status-page-metric>
|
||||
<div ng-repeat="metric in ui.metrics">
|
||||
<status-page-metric metric="metric"></status-page-metric>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -27,9 +27,39 @@ const chrome = require('ui/chrome')
|
|||
}
|
||||
|
||||
const data = resp.data;
|
||||
ui.metrics = data.metrics;
|
||||
ui.name = data.name;
|
||||
const metrics = data.metrics;
|
||||
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;
|
||||
|
||||
const overall = data.status.overall;
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
border: 0;
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
text-align: right;
|
||||
padding: 15px;
|
||||
padding-right: 20px;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="status_metric_wrapper col-md-4">
|
||||
<div class="content">
|
||||
<h3 class="title">{{metric.extendedTitle}}</h3>
|
||||
<h4 class="average">{{ metric.averages.join(', ') }}</h4>
|
||||
<h3 class="title">{{ metric.name }}</h3>
|
||||
<h4 class="average">{{ metric.value | statusMetric: metric.type}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,74 +2,28 @@ import _ from 'lodash';
|
|||
import moment from 'moment';
|
||||
import numeral from 'numeral';
|
||||
|
||||
import toTitleCase from './lib/to_title_case';
|
||||
import formatNumber from './lib/format_number';
|
||||
import readStatData from './lib/read_stat_data';
|
||||
import uiModules from 'ui/modules';
|
||||
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
|
||||
.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 () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: statusPageMetricTemplate,
|
||||
scope: {
|
||||
name: '@',
|
||||
data: '='
|
||||
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})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
controllerAs: 'metric'
|
||||
};
|
||||
});
|
||||
|
|
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);
|
||||
|
||||
if (server.plugins['even-better']) {
|
||||
kbnServer.mixin(require('./metrics'));
|
||||
kbnServer.mixin(require('./metrics').collectMetrics);
|
||||
}
|
||||
|
||||
const wrapAuth = wrapAuthConfig(config.get('status.allowAnonymous'));
|
||||
|
||||
const matchSnapshot = /-SNAPSHOT$/;
|
||||
server.route(wrapAuth({
|
||||
method: 'GET',
|
||||
path: '/api/status',
|
||||
handler: function (request, reply) {
|
||||
return reply({
|
||||
const status = {
|
||||
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'),
|
||||
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(),
|
||||
metrics: kbnServer.metrics
|
||||
});
|
||||
};
|
||||
|
||||
return reply(status);
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,27 +1,40 @@
|
|||
import _ from 'lodash';
|
||||
import Samples from './samples';
|
||||
module.exports = function (kbnServer, server, config) {
|
||||
let lastReport = Date.now();
|
||||
|
||||
kbnServer.metrics = new Samples(12);
|
||||
import { keysToSnakeCaseShallow } from '../../utils/case_conversion';
|
||||
|
||||
export function collectMetrics(kbnServer, server, config) {
|
||||
server.plugins['even-better'].monitor.on('ops', function (event) {
|
||||
const now = Date.now();
|
||||
const secSinceLast = (now - lastReport) / 1000;
|
||||
lastReport = now;
|
||||
kbnServer.metrics = getMetrics({ event, config });
|
||||
});
|
||||
}
|
||||
|
||||
export function getMetrics({ event, config }) {
|
||||
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
|
||||
});
|
||||
|
||||
});
|
||||
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 { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../core_plugins/kibana/common/lib/case_conversion';
|
||||
import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../utils/case_conversion';
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import chrome from 'ui/chrome';
|
||||
|
|
Loading…
Reference in a new issue