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:
Jonathan Budzenski 2017-02-16 10:30:36 -06:00 committed by GitHub
parent 408a2e6506
commit 9bc4d9158b
14 changed files with 165 additions and 103 deletions

View file

@ -10,4 +10,14 @@ your application to Kibana 6.0.
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.

View file

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

View file

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

View file

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

View file

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

View file

@ -36,6 +36,7 @@
border: 0;
.content {
display: block;
text-align: right;
padding: 15px;
padding-right: 20px;

View file

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

View file

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

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

View file

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

View file

@ -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;
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
});
kbnServer.metrics = getMetrics({ event, config });
});
};
}
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])
};
}

View file

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