mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Merge pull request #1 from rashidkpc/master
Compare nodes button and scripted dashboard
This commit is contained in:
commit
92a5f9d8db
3 changed files with 285 additions and 104 deletions
149
dashboards/node_stats.js
Normal file
149
dashboards/node_stats.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
/* global _ */
|
||||
|
||||
/*
|
||||
* Node statistics scripted dashboard
|
||||
* This script generates a dashboard object that Kibana can load.
|
||||
*
|
||||
* Parameters (all optional)
|
||||
* nodes :: By default, a comma seperated list of queries to run. Default: *
|
||||
* show :: The names of the rows to expand
|
||||
* from :: Search this amount of time back, eg 15m, 1h, 2d. Default: 1d
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, queries, _d_timespan;
|
||||
|
||||
// All url parameters are available via the ARGS object
|
||||
var ARGS;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
_d_timespan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
services : {}
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Node Statistics';
|
||||
|
||||
// And the index options
|
||||
dashboard.failover = false;
|
||||
dashboard.index = {
|
||||
default: 'ADD_A_TIME_FILTER',
|
||||
pattern: '[es_monitor-]YYYY.MM.DD',
|
||||
interval: 'day'
|
||||
};
|
||||
|
||||
// In this dashboard we let users pass nodes as comma seperated list to the query parameter.
|
||||
// If nodes are defined, split into a list of query objects, otherwise, show all
|
||||
// NOTE: ids must be integers, hence the parseInt()s
|
||||
if(!_.isUndefined(ARGS.nodes)) {
|
||||
queries = _.object(_.map(ARGS.nodes.split(','), function(v,k) {
|
||||
return [k,{
|
||||
query: 'node.transport_address:"'+v+'"',
|
||||
id: parseInt(k,10),
|
||||
alias: v
|
||||
}];
|
||||
}));
|
||||
} else {
|
||||
// No queries passed? Initialize a single query to match everything
|
||||
queries = {
|
||||
0: {
|
||||
query: '*',
|
||||
id: 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var show = ARGS.show.split(',') || [];
|
||||
|
||||
// Now populate the query service with our objects
|
||||
dashboard.services.query = {
|
||||
list : queries,
|
||||
ids : _.map(_.keys(queries),function(v){return parseInt(v,10);})
|
||||
};
|
||||
|
||||
// Lets also add a default time filter, the value of which can be specified by the user
|
||||
dashboard.services.filter = {
|
||||
list: {
|
||||
0: {
|
||||
from: "now-"+(ARGS.from||_d_timespan),
|
||||
to: "now",
|
||||
field: "@timestamp",
|
||||
type: "time",
|
||||
active: true,
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
ids: [0]
|
||||
};
|
||||
|
||||
// Ok, lets make some rows. Since all of our panels are similar, we can abstract this.
|
||||
// Obviously this is a partial list, feel free to expand on this.
|
||||
var rows = [
|
||||
{
|
||||
name:'OS',
|
||||
charts: [{
|
||||
field: 'os.cpu.user',
|
||||
derivative: false,
|
||||
},{
|
||||
field: 'os.mem.used_percent',
|
||||
derivative: false
|
||||
},{
|
||||
field: 'os.swap.used_in_bytes',
|
||||
derivative: true
|
||||
}]
|
||||
},
|
||||
{
|
||||
name: 'JVM',
|
||||
charts: [{
|
||||
field: 'jvm.gc.collectors.ParNew.collection_time_in_millis',
|
||||
derivative: true
|
||||
},{
|
||||
field: 'jvm.gc.collectors.ParNew.collection_count',
|
||||
derivative: true
|
||||
},{
|
||||
field: 'jvm.gc.collectors.ConcurrentMarkSweep.collection_time_in_millis',
|
||||
derivative: true
|
||||
}]
|
||||
}
|
||||
];
|
||||
|
||||
dashboard.rows = _.map(rows, function(r) {
|
||||
return {
|
||||
title: r.name,
|
||||
height: '150px',
|
||||
collapse: !_.contains(show,r.name),
|
||||
panels: _.map(r.charts,function(c) {
|
||||
// A bunch of histogram panels, with similar defaults
|
||||
return {
|
||||
title: c.field,
|
||||
type: 'histogram',
|
||||
span: 4,
|
||||
time_field: '@timestamp',
|
||||
value_field: c.field,
|
||||
derivative: c.derivative,
|
||||
bars: false,
|
||||
lines: true,
|
||||
stack: false,
|
||||
linewidth:2,
|
||||
mode: 'max', // Pretty sure we want max for all of these? No? Average for some?
|
||||
zoomlinks: false,
|
||||
options: false,
|
||||
legend: false, // Might want to enable this, cleaner without it though
|
||||
interactive: false // Because the filter pulldown is hidden
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
// No pulldowns shown, and they can't be enabled.
|
||||
dashboard.pulldowns = [];
|
||||
|
||||
// Now return the object and we're good!
|
||||
return dashboard;
|
|
@ -1,76 +1,93 @@
|
|||
<div ng-controller='marvel.nodes_health' ng-init="init()">
|
||||
<style>
|
||||
.marvel-table {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-mean {
|
||||
font-size: 20pt;
|
||||
font-weight: 200;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-extended {
|
||||
display: inline-block;
|
||||
font-size:9pt;
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-header .nodes{
|
||||
font-size: 20pt;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.marvel-nodes-health-chart {
|
||||
margin-top: 5px;
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.marvel-table {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-mean {
|
||||
font-size: 20pt;
|
||||
font-weight: 200;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-extended {
|
||||
display: inline-block;
|
||||
font-size:9pt;
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.marvel-header .nodes{
|
||||
font-size: 20pt;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.marvel-nodes-health-chart {
|
||||
margin-top: 5px;
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="pull-left marvel-header marvel-table" ng-show="nodes.length > 0">
|
||||
<span class="nodes">{{nodes.length}} nodes</span> / Last 10m</span>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a href="" ng-class="{strong:!panel.compact}" ng-click="panel.compact=false">Full</a> / <a href="" ng-class="{strong:panel.compact}"
|
||||
ng-click="panel.compact=true">Compact</a>
|
||||
</div>
|
||||
<div class="pull-left marvel-header marvel-table" ng-show="nodes.length > 0">
|
||||
<span class="nodes">{{nodes.length}} nodes</span> / Last 10m </span>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a href="" ng-class="{strong:!panel.compact}" ng-click="panel.compact=false">Full</a> /
|
||||
<a href="" ng-class="{strong:panel.compact}" ng-click="panel.compact=true">Compact</a>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered" ng-if="!panel.compact">
|
||||
<thead>
|
||||
<th>node</th>
|
||||
<th ng-repeat="metric in metrics" ng-class="alertClass(warnLevels['_global_'][metric.name])">{{metric.name}}</th>
|
||||
</thead>
|
||||
<tr ng-repeat="node in nodes">
|
||||
<td>{{node}}</td>
|
||||
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node][metric.name])">
|
||||
<div class="marvel-mean">
|
||||
{{data[node+"_"+metric.name].mean / metric.scale | number:metric.decimals}}<br>
|
||||
<table class="table table-bordered" ng-if="!panel.compact">
|
||||
<thead>
|
||||
<th>node <a ng-href="{{compareLink()}}" target="_blank" class="btn btn-mini btn-info" ng-disabled="!hasSelected(nodes)" bs-tooltip="compareTip()" data-placement="right">Compare</a></th>
|
||||
<th ng-repeat="metric in metrics" ng-class="alertClass(warnLevels['_global_'][metric.name])">{{metric.name}}</th>
|
||||
</thead>
|
||||
<tr ng-repeat="node in nodes">
|
||||
<td>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="nodes[$index].selected" ng-checked="nodes[$index].selected">
|
||||
{{node.name}}
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node][metric.name])">
|
||||
<div class="marvel-mean">
|
||||
{{data[node.name+"_"+metric.name].mean / metric.scale | number:metric.decimals}}<br>
|
||||
|
||||
<div class="marvel-nodes-health-chart" series="data[node+'_'+metric.name+'_history']"></div>
|
||||
</div>
|
||||
<div class="marvel-extended">
|
||||
<span>min: {{data[node+"_"+metric.name].min / metric.scale | number:metric.decimals}}</span><br>
|
||||
<span>max: {{data[node+"_"+metric.name].max / metric.scale | number:metric.decimals}}</span>
|
||||
<div class="marvel-nodes-health-chart" series="data[node.name+'_'+metric.name+'_history']"></div>
|
||||
</div>
|
||||
<div class="marvel-extended">
|
||||
<span>min: {{data[node.name+"_"+metric.name].min / metric.scale | number:metric.decimals}}</span><br>
|
||||
<span>max: {{data[node.name+"_"+metric.name].max / metric.scale | number:metric.decimals}}</span>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="table table-bordered table-condensed marvel-table" ng-if="panel.compact">
|
||||
<thead>
|
||||
<th>node</th>
|
||||
<th ng-repeat="metric in metrics" ng-class="alertClass(warnLevels['_global_'][metric.name])">{{metric.name}}</th>
|
||||
</thead>
|
||||
<tr ng-repeat="node in nodes">
|
||||
<td>{{node}}</td>
|
||||
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node][metric.name])">
|
||||
<div>{{data[node+"_"+metric.name].mean / metric.scale | number:metric.decimals}}
|
||||
<div class="marvel-nodes-health-chart" series="data[node+'_'+metric.name+'_history']"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table table-bordered table-condensed marvel-table" ng-if="panel.compact">
|
||||
<thead>
|
||||
<th>node <a ng-href="{{compareLink()}}" class="btn btn-mini btn-info" ng-disabled="!hasSelected(nodes)" bs-tooltip="compareTip()" data-placement="right">Compare</a></th>
|
||||
<th ng-repeat="metric in metrics" ng-class="alertClass(warnLevels['_global_'][metric.name])">{{metric.name}}</th>
|
||||
</thead>
|
||||
<tr ng-repeat="node in nodes">
|
||||
<td>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="nodes[$index].selected" ng-checked="nodes[$index].selected">
|
||||
{{node.name}}
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node.name][metric.name])">
|
||||
<div>{{data[node.name+"_"+metric.name].mean / metric.scale | number:metric.decimals}}
|
||||
<div class="marvel-nodes-health-chart" series="data[node.name+'_'+metric.name+'_history']"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
|
@ -27,8 +27,6 @@ function (angular, app, kbn, _, $) {
|
|||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.warnLevels = [];
|
||||
|
||||
|
@ -56,14 +54,20 @@ function (angular, app, kbn, _, $) {
|
|||
request = request
|
||||
.facet($scope.ejs.TermsFacet('terms')
|
||||
.field("node.transport_address")
|
||||
.allTerms(true)
|
||||
.size(9999999)
|
||||
.order('term')
|
||||
.facetFilter(filterSrv.getBoolFilter(filterSrv.ids))).size(0);
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
results.then(function(r) {
|
||||
$scope.nodes = _.pluck(r.facets.terms.terms,'term');
|
||||
var newNodes = _.difference(_.pluck(r.facets.terms.terms,'term'),_.pluck($scope.nodes,'name'));
|
||||
$scope.nodes = _.map(newNodes, function(n) {
|
||||
return {
|
||||
name: n,
|
||||
selected: false
|
||||
};
|
||||
});
|
||||
$scope.get_data();
|
||||
});
|
||||
|
||||
|
@ -93,32 +97,32 @@ function (angular, app, kbn, _, $) {
|
|||
error: 10,
|
||||
decimals: 2
|
||||
},{
|
||||
name: 'System Mem (%)',
|
||||
field: 'os.mem.used_percent',
|
||||
warning: 90,
|
||||
error: 97,
|
||||
decimals: 2
|
||||
name: 'System Mem (%)',
|
||||
field: 'os.mem.used_percent',
|
||||
warning: 90,
|
||||
error: 97,
|
||||
decimals: 2
|
||||
},{
|
||||
name: 'Jvm Mem (%)',
|
||||
field: 'os.mem.used_percent',
|
||||
warning: 95,
|
||||
error: 98,
|
||||
decimals: 2
|
||||
},{
|
||||
name: 'Free disk space (GB)',
|
||||
field: 'fs.data.available_in_bytes',
|
||||
scale: 1024 * 1024 * 1024,
|
||||
warning: { threshold: 5, type: "lower_bound" },
|
||||
error: { threshold: 2, type: "lower_bound" },
|
||||
decimals: 2
|
||||
}];
|
||||
name: 'Jvm Mem (%)',
|
||||
field: 'os.mem.used_percent',
|
||||
warning: 95,
|
||||
error: 98,
|
||||
decimals: 2
|
||||
},{
|
||||
name: 'Free disk space (GB)',
|
||||
field: 'fs.data.available_in_bytes',
|
||||
scale: 1024 * 1024 * 1024,
|
||||
warning: { threshold: 5, type: "lower_bound" },
|
||||
error: { threshold: 2, type: "lower_bound" },
|
||||
decimals: 2
|
||||
}];
|
||||
|
||||
_.each($scope.metrics, function (m) {
|
||||
_.defaults(m, { scale : 1})
|
||||
if (typeof m.error === 'number') {
|
||||
_.defaults(m, {scale : 1});
|
||||
if (_.isNumber(m.error)) {
|
||||
m.error = { threshold: m.error, type: "upper_bound"};
|
||||
}
|
||||
if (typeof m.warning === 'number') {
|
||||
if (_.isNumber(m.warning)) {
|
||||
m.warning = { threshold: m.warning, type: "upper_bound"};
|
||||
}
|
||||
});
|
||||
|
@ -128,7 +132,7 @@ function (angular, app, kbn, _, $) {
|
|||
var time = filterSrv.timeRange('last').to;
|
||||
time = kbn.parseDate(time).valueOf();
|
||||
// Terms mode
|
||||
_.each($scope.nodes,function(n) {
|
||||
_.each(_.pluck($scope.nodes,'name'),function(n) {
|
||||
var filter = $scope.ejs.BoolFilter()
|
||||
.must($scope.ejs.RangeFilter('@timestamp').from(time + '||-10m/m'))
|
||||
.must($scope.ejs.TermsFilter('node.transport_address',n));
|
||||
|
@ -152,17 +156,31 @@ function (angular, app, kbn, _, $) {
|
|||
$scope.panelMeta.loading = false;
|
||||
$scope.warnLevels = {};
|
||||
$scope.calculateWarnings();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
$scope.hasSelected = function(nodes) {
|
||||
return _.some(nodes, function(n){
|
||||
return n.selected;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.compareLink = function() {
|
||||
var nodes = _.pluck(_.where($scope.nodes,{selected:true}),'name');
|
||||
return "#/dashboard/script/node_stats.js?show=OS&nodes="+nodes.join(',');
|
||||
};
|
||||
|
||||
$scope.compareTip = function() {
|
||||
return $scope.hasSelected($scope.nodes) ? false : 'Select nodes to compare';
|
||||
};
|
||||
|
||||
$scope.calculateWarnings = function () {
|
||||
$scope.warnLevels = {_global_: {}};
|
||||
_.each($scope.metrics, function (metric) {
|
||||
$scope.warnLevels['_global_'][metric.name] = 0;
|
||||
_.each($scope.nodes, function (node) {
|
||||
_.each(_.pluck($scope.nodes,'name'), function (node) {
|
||||
var level = $scope.alertLevel(metric, $scope.data[node + '_' + metric.name].mean);
|
||||
if (!$scope.warnLevels[node]) $scope.warnLevels[node] = {};
|
||||
$scope.warnLevels[node] = $scope.warnLevels[node] || {};
|
||||
$scope.warnLevels[node][metric.name] = level;
|
||||
if (level > $scope.warnLevels['_global_'][metric.name]) {
|
||||
$scope.warnLevels['_global_'][metric.name] = level;
|
||||
|
@ -171,11 +189,13 @@ function (angular, app, kbn, _, $) {
|
|||
});
|
||||
};
|
||||
|
||||
$scope.alertLevel = function(metric,num) {
|
||||
$scope.alertLevel = function(metric,num) {
|
||||
var level = 0;
|
||||
|
||||
function testAlert(alert,num) {
|
||||
if (!alert) return false;
|
||||
if (!alert) {
|
||||
return false;
|
||||
}
|
||||
return alert.type === "upper_bound" ? num>alert.threshold : num<alert.threshold;
|
||||
}
|
||||
|
||||
|
@ -233,7 +253,6 @@ function (angular, app, kbn, _, $) {
|
|||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// Populate element
|
||||
//try {
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
|
@ -266,13 +285,9 @@ function (angular, app, kbn, _, $) {
|
|||
color : elem.css('color'),
|
||||
};
|
||||
|
||||
|
||||
$.plot(elem, [_d], options);
|
||||
}
|
||||
|
||||
//} catch(e) {
|
||||
// console.log(e);
|
||||
//}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue