mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Extended node discovery to include a display value. Improved state transfer from overview dashboard to node dashboard
This commit is contained in:
parent
741c0d8ded
commit
ac7ef26211
4 changed files with 399 additions and 319 deletions
|
@ -29,24 +29,24 @@ dashboard = {
|
|||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Node Statistics';
|
||||
dashboard.title = 'Marvel - Node Statistics';
|
||||
|
||||
// And the index options
|
||||
dashboard.failover = false;
|
||||
dashboard.index = {
|
||||
default: 'ADD_A_TIME_FILTER',
|
||||
pattern: '[marvel-]YYYY.MM.DD',
|
||||
interval: 'day'
|
||||
'default': 'ADD_A_TIME_FILTER',
|
||||
'pattern': '[marvel-]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) {
|
||||
queries = _.object(_.map(JSON.parse(ARGS.nodes), function (v, k) {
|
||||
return [k, {
|
||||
query: 'node.transport_address:"' + v + '"',
|
||||
alias: v,
|
||||
query: v.q,
|
||||
alias: v.a || v.q,
|
||||
pin: true,
|
||||
id: parseInt(k, 10)
|
||||
}];
|
||||
|
@ -61,22 +61,22 @@ if (!_.isUndefined(ARGS.nodes)) {
|
|||
};
|
||||
}
|
||||
|
||||
var show = ARGS.show.split(',') || [];
|
||||
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: (ARGS.from || "now-" + _d_timespan),
|
||||
to: "now",
|
||||
from: ARGS.from || "now-" + _d_timespan,
|
||||
to: ARGS.to || "now",
|
||||
field: "@timestamp",
|
||||
type: "time",
|
||||
active: true,
|
||||
|
@ -128,16 +128,6 @@ var rows = [
|
|||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false,
|
||||
"query": "*",
|
||||
"size": 20,
|
||||
"field": "_type",
|
||||
"sort": [
|
||||
"_score",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
|
@ -171,7 +161,17 @@ var rows = [
|
|||
{
|
||||
"time_field": "@timestamp",
|
||||
"value_field": "jvm.mem.heap_used_in_bytes",
|
||||
"title": "Heap"
|
||||
"title": "Heap",
|
||||
"annotate": {
|
||||
"enable": true,
|
||||
"query": "_type:shard_event",
|
||||
"size": 100,
|
||||
"field": "message",
|
||||
"sort": [
|
||||
"_score",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"value_field": "jvm.gc.collectors.ParNew.collection_time_in_millis",
|
||||
|
@ -231,7 +231,7 @@ var rows = [
|
|||
]
|
||||
},
|
||||
{
|
||||
"title": "Disk IO",
|
||||
"title": "Disk",
|
||||
"panels": [
|
||||
{
|
||||
"value_field": "fs.data.disk_read_size_in_bytes",
|
||||
|
@ -244,6 +244,10 @@ var rows = [
|
|||
"title": "Disk writes (bytes)",
|
||||
"derivative": true,
|
||||
"scaleSeconds": true
|
||||
},
|
||||
{
|
||||
"value_field": "fs.data.available_in_bytes",
|
||||
"title": "Disk Free space (bytes)"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
|
@ -370,7 +374,7 @@ dashboard.pulldowns = [
|
|||
"type": "query",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"enable": true
|
||||
},
|
||||
{
|
||||
"type": "filtering",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Overview",
|
||||
"title": "Marvel - Overview",
|
||||
"services": {
|
||||
"query": {
|
||||
"idQueue": [
|
||||
|
@ -98,7 +98,7 @@
|
|||
"linewidth": 2,
|
||||
"timezone": "browser",
|
||||
"spyable": true,
|
||||
"zoomlinks": true,
|
||||
"zoomlinks": false,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"points": false,
|
||||
|
@ -111,7 +111,7 @@
|
|||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"interactive": true,
|
||||
"options": true,
|
||||
"options": false,
|
||||
"derivative": false,
|
||||
"scale": 1,
|
||||
"tooltip": {
|
||||
|
@ -167,10 +167,10 @@
|
|||
"1y"
|
||||
],
|
||||
"fill": 0,
|
||||
"linewidth": 3,
|
||||
"linewidth": 2,
|
||||
"timezone": "browser",
|
||||
"spyable": true,
|
||||
"zoomlinks": true,
|
||||
"zoomlinks": false,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"points": false,
|
||||
|
@ -183,7 +183,7 @@
|
|||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"interactive": true,
|
||||
"options": true,
|
||||
"options": false,
|
||||
"derivative": true,
|
||||
"scaleSeconds": true,
|
||||
"scale": 1,
|
||||
|
@ -240,10 +240,10 @@
|
|||
"1y"
|
||||
],
|
||||
"fill": 0,
|
||||
"linewidth": 3,
|
||||
"linewidth": 2,
|
||||
"timezone": "browser",
|
||||
"spyable": true,
|
||||
"zoomlinks": true,
|
||||
"zoomlinks": false,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"points": false,
|
||||
|
@ -256,7 +256,7 @@
|
|||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"interactive": true,
|
||||
"options": true,
|
||||
"options": false,
|
||||
"derivative": true,
|
||||
"scaleSeconds": true,
|
||||
"scale": 1,
|
||||
|
@ -316,7 +316,7 @@
|
|||
"pulldowns": [
|
||||
{
|
||||
"type": "query",
|
||||
"collapse": false,
|
||||
"collapse": true,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"query": "*",
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.marvel-persistent-name {
|
||||
font-size: 9pt;
|
||||
}
|
||||
.marvel-extended {
|
||||
display: inline-block;
|
||||
font-size:9pt;
|
||||
|
@ -41,27 +44,28 @@
|
|||
|
||||
<table class="table table-bordered" ng-if="!panel.compact">
|
||||
<thead>
|
||||
<th>node <a ng-href="{{detailViewLink()}}" target="_blank" class="btn btn-mini btn-info" ng-disabled="!hasSelected(nodes)" bs-tooltip="detailViewTip()" data-placement="right">nodes dashboard</a></th>
|
||||
<th>node <a id="detail_view_link" ng-href="{{detailViewLink()}}" target="_top" class="btn btn-mini btn-info" ng-disabled="!hasSelected(nodes)" bs-tooltip="detailViewTip()" data-placement="right">nodes dashboard</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}}
|
||||
<input type="checkbox" ng-model="node.selected" ng-checked="node.selected">
|
||||
{{node.display_name}}
|
||||
<div class="marvel-persistent-name">{{node.id}}</div>
|
||||
</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>
|
||||
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node.id][metric.name])">
|
||||
<div class="marvel-mean pointer" ng-click="metricClick(node,metric)">
|
||||
{{data[node.id+"_"+metric.name].mean / metric.scale | number:metric.decimals}}<br>
|
||||
|
||||
<div class="marvel-nodes-health-chart" series="data[node.name+'_'+metric.name+'_history']"></div>
|
||||
<div class="marvel-nodes-health-chart" series="data[node.id+'_'+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 class="marvel-extended pointer" ng-click="metricClick(node,metric)">
|
||||
<span>min: {{data[node.id+"_"+metric.name].min / metric.scale | number:metric.decimals}}</span><br>
|
||||
<span>max: {{data[node.id+"_"+metric.name].max / metric.scale | number:metric.decimals}}</span>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
@ -77,14 +81,14 @@
|
|||
<td>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="nodes[$index].selected" ng-checked="nodes[$index].selected">
|
||||
{{node.name}}
|
||||
<input type="checkbox" ng-model="node.selected" ng-checked="node.selected">
|
||||
</label>
|
||||
{{ node.display_name }}
|
||||
</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>
|
||||
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node.id][metric.name])">
|
||||
<div class="pointer" ng-click="metricClick(node,metric)">{{data[node.id+"_"+metric.name].mean / metric.scale | number:metric.decimals}}
|
||||
<div class="marvel-nodes-health-chart pointer" ng-click="metricClick(node,metric)" series="data[node.id+'_'+metric.name+'_history']"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -5,300 +5,372 @@ define([
|
|||
'underscore',
|
||||
'jquery',
|
||||
'jquery.flot',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.time'
|
||||
],
|
||||
function (angular, app, kbn, _, $) {
|
||||
'use strict';
|
||||
function (angular, app, kbn, _, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.marvel.nodes_health', []);
|
||||
app.useModule(module);
|
||||
var module = angular.module('kibana.panels.marvel.nodes_health', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('marvel.nodes_health', function($scope, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals : [],
|
||||
editorTabs : [],
|
||||
status : "Experimental",
|
||||
description : "An overview of cluster health, by node."
|
||||
};
|
||||
module.controller('marvel.nodes_health', function ($scope, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals: [],
|
||||
editorTabs: [],
|
||||
status: "Experimental",
|
||||
description: "An overview of cluster health, by node."
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
compact : false
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
compact: false,
|
||||
node_display_field: "node.name", // used as primary display string for a node.
|
||||
node_persistent_field: "node.transport_address" // used as node identity - i.e., search queries, facets etc.
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.warnLevels = {};
|
||||
$scope.nodes = [];
|
||||
};
|
||||
_.defaults($scope.panel, _d);
|
||||
|
||||
$scope.$on('refresh',function(){
|
||||
$scope.get_nodes();
|
||||
});
|
||||
|
||||
$scope.get_nodes();
|
||||
|
||||
};
|
||||
|
||||
$scope.get_nodes = function () {
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var
|
||||
request,
|
||||
results;
|
||||
|
||||
request = $scope.ejs.Request().indices(dashboard.indices);
|
||||
request = request
|
||||
.facet($scope.ejs.TermsFacet('terms')
|
||||
.field("node.transport_address")
|
||||
.size(9999999)
|
||||
.order('term')
|
||||
.facetFilter(filterSrv.getBoolFilter(filterSrv.ids))).size(0);
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
results.then(function(r) {
|
||||
var newNodes = _.pluck(r.facets.terms.terms,'term');
|
||||
newNodes = _.map(newNodes, function(n) {
|
||||
return {
|
||||
name: n,
|
||||
selected: false
|
||||
};
|
||||
});
|
||||
$scope.get_data(newNodes);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.get_data = function(newNodes) {
|
||||
// Make sure we have everything for the request to complete
|
||||
|
||||
if (typeof newNodes === "undefined") {
|
||||
newNodes = $scope.nodes;
|
||||
|
||||
}
|
||||
|
||||
if(dashboard.indices.length === 0 || newNodes.length === 0) {
|
||||
$scope.nodes = newNodes;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
var
|
||||
request,
|
||||
results;
|
||||
|
||||
$scope.metrics = [{
|
||||
name:'CPU (%)',
|
||||
field:'process.cpu.percent',
|
||||
warning:60,
|
||||
error: 90,
|
||||
decimals: 2
|
||||
},{
|
||||
name:'Load (1m)',
|
||||
field:'os.load_average.1m',
|
||||
warning:8,
|
||||
error: 10,
|
||||
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
|
||||
}];
|
||||
|
||||
_.each($scope.metrics, function (m) {
|
||||
_.defaults(m, {scale : 1});
|
||||
if (_.isNumber(m.error)) {
|
||||
m.error = { threshold: m.error, type: "upper_bound"};
|
||||
}
|
||||
if (_.isNumber(m.warning)) {
|
||||
m.warning = { threshold: m.warning, type: "upper_bound"};
|
||||
}
|
||||
});
|
||||
|
||||
request = $scope.ejs.Request().indices(dashboard.indices);
|
||||
|
||||
var time = filterSrv.timeRange('last').to;
|
||||
time = kbn.parseDate(time).valueOf();
|
||||
// Terms mode
|
||||
_.each(_.pluck(newNodes,'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));
|
||||
|
||||
_.each($scope.metrics, function(m) {
|
||||
request = request
|
||||
.facet($scope.ejs.StatisticalFacet(n+"_"+m.name)
|
||||
.field(m.field || m.name)
|
||||
.facetFilter(filter));
|
||||
request = request.facet($scope.ejs.DateHistogramFacet(n+"_"+m.name+"_history")
|
||||
.keyField('@timestamp').valueField(m.field || m.name).interval('1m')
|
||||
.facetFilter(filter)).size(0);
|
||||
});
|
||||
});
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
$scope.nodes = newNodes;
|
||||
$scope.data = results.facets;
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.init = function () {
|
||||
$scope.warnLevels = {};
|
||||
$scope.calculateWarnings();
|
||||
});
|
||||
};
|
||||
$scope.nodes = [];
|
||||
|
||||
$scope.hasSelected = function(nodes) {
|
||||
return _.some(nodes, function(n){
|
||||
return n.selected;
|
||||
});
|
||||
};
|
||||
$scope.$on('refresh', function () {
|
||||
$scope.get_nodes();
|
||||
});
|
||||
|
||||
$scope.detailViewLink = function() {
|
||||
var nodes = _.pluck(_.where($scope.nodes,{selected:true}),'name');
|
||||
return "#/dashboard/script/marvel.node_stats.js?show=OS&nodes="+nodes.join(',');
|
||||
};
|
||||
$scope.get_nodes();
|
||||
|
||||
$scope.detailViewTip = function() {
|
||||
return $scope.hasSelected($scope.nodes) ? 'Open nodes dashboard for selected nodes' :
|
||||
'Select nodes and click top open the nodes dashboard';
|
||||
};
|
||||
};
|
||||
|
||||
$scope.calculateWarnings = function () {
|
||||
$scope.warnLevels = {_global_: {}};
|
||||
_.each($scope.metrics, function (metric) {
|
||||
$scope.warnLevels['_global_'][metric.name] = 0;
|
||||
_.each(_.pluck($scope.nodes,'name'), function (node) {
|
||||
var level = $scope.alertLevel(metric, $scope.data[node + '_' + metric.name].mean);
|
||||
$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;
|
||||
$scope.get_nodes = function () {
|
||||
if (dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var
|
||||
request,
|
||||
filter,
|
||||
results;
|
||||
|
||||
filter = filterSrv.getBoolFilter(filterSrv.ids);
|
||||
|
||||
request = $scope.ejs.Request().indices(dashboard.indices).size(0).searchType("count");
|
||||
request.facet(
|
||||
$scope.ejs.TermsFacet('terms')
|
||||
.field($scope.panel.node_persistent_field)
|
||||
.size(9999999)
|
||||
.order('term')
|
||||
.facetFilter(filter)
|
||||
);
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
results.then(function (r) {
|
||||
var newPersistentIds = _.pluck(r.facets.terms.terms, 'term');
|
||||
|
||||
if (newPersistentIds.length === 0) {
|
||||
$scope.get_data([]);
|
||||
return;
|
||||
}
|
||||
|
||||
var mrequest = $scope.ejs.MultiSearchRequest().indices(dashboard.indices);
|
||||
|
||||
_.each(newPersistentIds, function (persistentId) {
|
||||
var nodeReqeust = $scope.ejs.Request().filter(filter);
|
||||
nodeReqeust.query(
|
||||
$scope.ejs.ConstantScoreQuery().query(
|
||||
$scope.ejs.TermQuery($scope.panel.node_persistent_field, persistentId)
|
||||
)
|
||||
);
|
||||
nodeReqeust.size(1).fields([ $scope.panel.node_display_field, $scope.panel.node_persistent_field]);
|
||||
nodeReqeust.sort("@timestamp", "desc");
|
||||
mrequest.requests(nodeReqeust);
|
||||
});
|
||||
|
||||
mrequest.doSearch(function (r) {
|
||||
var newNodes = [];
|
||||
_.each(r.responses, function (nodeResponse) {
|
||||
if (nodeResponse.hits.hits.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hit = nodeResponse.hits.hits[0];
|
||||
var display_name = hit.fields[$scope.panel.node_display_field];
|
||||
var persistent_name = hit.fields[$scope.panel.node_persistent_field];
|
||||
newNodes.push({
|
||||
display_name: display_name || persistent_name,
|
||||
id: persistent_name,
|
||||
selected: ($scope.nodes[persistent_name] || {}).selected
|
||||
});
|
||||
});
|
||||
|
||||
$scope.get_data(newNodes);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.get_data = function (newNodes) {
|
||||
// Make sure we have everything for the request to complete
|
||||
|
||||
if (newNodes === undefined) {
|
||||
newNodes = $scope.nodes;
|
||||
}
|
||||
|
||||
if (dashboard.indices.length === 0 || newNodes.length === 0) {
|
||||
$scope.nodes = newNodes;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
var
|
||||
request,
|
||||
results;
|
||||
|
||||
$scope.metrics = [
|
||||
{
|
||||
name: 'CPU (%)',
|
||||
field: 'process.cpu.percent',
|
||||
warning: 60,
|
||||
error: 90,
|
||||
decimals: 2
|
||||
},
|
||||
{
|
||||
name: 'Load (1m)',
|
||||
field: 'os.load_average.1m',
|
||||
warning: 8,
|
||||
error: 10,
|
||||
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
|
||||
}
|
||||
];
|
||||
|
||||
_.each($scope.metrics, function (m) {
|
||||
_.defaults(m, {scale: 1});
|
||||
if (_.isNumber(m.error)) {
|
||||
m.error = { threshold: m.error, type: "upper_bound"};
|
||||
}
|
||||
if (_.isNumber(m.warning)) {
|
||||
m.warning = { threshold: m.warning, type: "upper_bound"};
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.alertLevel = function(metric,num) {
|
||||
var level = 0;
|
||||
request = $scope.ejs.Request().indices(dashboard.indices);
|
||||
|
||||
function testAlert(alert,num) {
|
||||
if (!alert) {
|
||||
return false;
|
||||
var time = filterSrv.timeRange('last').to;
|
||||
time = kbn.parseDate(time).valueOf();
|
||||
// Terms mode
|
||||
_.each(_.pluck(newNodes, 'id'), function (id) {
|
||||
var filter = $scope.ejs.BoolFilter()
|
||||
.must($scope.ejs.RangeFilter('@timestamp').from(time + '||-10m/m'))
|
||||
.must($scope.ejs.TermsFilter($scope.panel.node_persistent_field, id));
|
||||
|
||||
_.each($scope.metrics, function (m) {
|
||||
request = request
|
||||
.facet($scope.ejs.StatisticalFacet(id + "_" + m.name)
|
||||
.field(m.field || m.name)
|
||||
.facetFilter(filter));
|
||||
request = request.facet($scope.ejs.DateHistogramFacet(id + "_" + m.name + "_history")
|
||||
.keyField('@timestamp').valueField(m.field || m.name).interval('1m')
|
||||
.facetFilter(filter)).size(0);
|
||||
});
|
||||
});
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function (results) {
|
||||
$scope.nodes = newNodes;
|
||||
$scope.data = results.facets;
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.warnLevels = {};
|
||||
$scope.calculateWarnings();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.hasSelected = function (nodes) {
|
||||
return _.some(nodes, function (n) {
|
||||
return n.selected;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.metricClick = function (node, metric) {
|
||||
var current = window.location.href;
|
||||
var i = current.indexOf('#');
|
||||
if (i > 0) {
|
||||
current = current.substr(0, i);
|
||||
}
|
||||
return alert.type === "upper_bound" ? num>alert.threshold : num<alert.threshold;
|
||||
}
|
||||
current += $scope.detailViewLink([node], [metric.field]);
|
||||
window.location = current;
|
||||
};
|
||||
|
||||
num /= metric.scale;
|
||||
if (testAlert(metric.error, num)) {
|
||||
level = 2;
|
||||
} else if (testAlert(metric.warning, num)) {
|
||||
level = 1;
|
||||
}
|
||||
$scope.detailViewLink = function (nodes, fields) {
|
||||
if (nodes === undefined) {
|
||||
nodes = _.where($scope.nodes, {selected: true});
|
||||
}
|
||||
nodes = _.map(nodes, function (node) {
|
||||
var query = $scope.panel.node_persistent_field + ':"' + node.id + '"';
|
||||
return {
|
||||
q: query,
|
||||
a: node.display_name
|
||||
};
|
||||
});
|
||||
nodes = JSON.stringify(nodes);
|
||||
var time = filterSrv.timeRange(false);
|
||||
var show;
|
||||
if (fields !== undefined) {
|
||||
show = "&show=" + fields.join(",");
|
||||
} else {
|
||||
show = "";
|
||||
}
|
||||
return "#/dashboard/script/marvel.node_stats.js?nodes=" + encodeURI(nodes) + "&from=" + time.from + "&to=" + time.to + show;
|
||||
};
|
||||
|
||||
if (document.location.search.match(/panic_demo/)) {
|
||||
var r = Math.random();
|
||||
if (r>0.9) {
|
||||
$scope.detailViewTip = function () {
|
||||
return $scope.hasSelected($scope.nodes) ? 'Open nodes dashboard for selected nodes' :
|
||||
'Select nodes and click top open the nodes dashboard';
|
||||
};
|
||||
|
||||
$scope.calculateWarnings = function () {
|
||||
$scope.warnLevels = {_global_: {}};
|
||||
_.each($scope.metrics, function (metric) {
|
||||
$scope.warnLevels._global_[metric.name] = 0;
|
||||
_.each(_.pluck($scope.nodes, 'id'), function (nodeID) {
|
||||
var level = $scope.alertLevel(metric, $scope.data[nodeID + '_' + metric.name].mean);
|
||||
$scope.warnLevels[nodeID] = $scope.warnLevels[nodeID] || {};
|
||||
$scope.warnLevels[nodeID][metric.name] = level;
|
||||
if (level > $scope.warnLevels._global_[metric.name]) {
|
||||
$scope.warnLevels._global_[metric.name] = level;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.alertLevel = function (metric, num) {
|
||||
var level = 0;
|
||||
|
||||
function testAlert(alert, num) {
|
||||
if (!alert) {
|
||||
return false;
|
||||
}
|
||||
return alert.type === "upper_bound" ? num > alert.threshold : num < alert.threshold;
|
||||
}
|
||||
|
||||
num /= metric.scale;
|
||||
if (testAlert(metric.error, num)) {
|
||||
level = 2;
|
||||
} else if (r>0.8) {
|
||||
} else if (testAlert(metric.warning, num)) {
|
||||
level = 1;
|
||||
}
|
||||
|
||||
}
|
||||
return level;
|
||||
};
|
||||
|
||||
$scope.alertClass = function(level) {
|
||||
if (level >= 2) {
|
||||
return ['text-error'];
|
||||
}
|
||||
if (level >= 1) {
|
||||
return ['text-warning'];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('marvelNodesHealthChart', function() {
|
||||
return {
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
series: '=',
|
||||
panel: '='
|
||||
},
|
||||
template: '<div></div>',
|
||||
link: function(scope, elem) {
|
||||
|
||||
// Receive render events
|
||||
scope.$watch('series',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
fill: 0,
|
||||
lineWidth: 2,
|
||||
steps: false
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxis: {
|
||||
show: false
|
||||
},
|
||||
xaxis: {
|
||||
show: false,
|
||||
mode: "time"
|
||||
},
|
||||
grid: {
|
||||
hoverable: false,
|
||||
show: false
|
||||
}
|
||||
};
|
||||
|
||||
if(!_.isUndefined(scope.series)) {
|
||||
var _d = {
|
||||
data : _.map(scope.series.entries, function(p) {
|
||||
return [p.time,p.mean];
|
||||
}),
|
||||
color : elem.css('color'),
|
||||
};
|
||||
|
||||
$.plot(elem, [_d], options);
|
||||
if (document.location.search.match(/panic_demo/)) {
|
||||
var r = Math.random();
|
||||
if (r > 0.9) {
|
||||
level = 2;
|
||||
} else if (r > 0.8) {
|
||||
level = 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
return level;
|
||||
};
|
||||
|
||||
$scope.alertClass = function (level) {
|
||||
if (level >= 2) {
|
||||
return ['text-error'];
|
||||
}
|
||||
if (level >= 1) {
|
||||
return ['text-warning'];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('marvelNodesHealthChart', function () {
|
||||
return {
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
series: '=',
|
||||
panel: '='
|
||||
},
|
||||
template: '<div></div>',
|
||||
link: function (scope, elem) {
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
fill: 0,
|
||||
lineWidth: 2,
|
||||
steps: false
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxis: {
|
||||
show: false
|
||||
},
|
||||
xaxis: {
|
||||
show: false,
|
||||
mode: "time"
|
||||
},
|
||||
grid: {
|
||||
hoverable: false,
|
||||
show: false
|
||||
}
|
||||
};
|
||||
|
||||
if (!_.isUndefined(scope.series)) {
|
||||
var _d = {
|
||||
data: _.map(scope.series.entries, function (p) {
|
||||
return [p.time, p.mean];
|
||||
}),
|
||||
color: elem.css('color')
|
||||
};
|
||||
|
||||
$.plot(elem, [_d], options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Receive render events
|
||||
scope.$watch('series', function () {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function () {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue