Moved metrics to panel settings and added an initial implementation for an editor

This commit is contained in:
Boaz Leskes 2013-11-12 16:26:57 +01:00
parent 5577058be1
commit b82b159fc0
3 changed files with 187 additions and 59 deletions

View file

@ -1,2 +1,66 @@
<h5>General</h5>
<div class="row-fluid">
<div class="span6">
<label class="small">Persistent id field
<i class="icon-question-sign" bs-tooltip="'choose a field that doesn't change on node restart'"></i></label>
<input type="text" bs-typeahead="fields.list" class="input-large" ng-model="panel.node_persistent_field"/>
</div>
</div>
<div class="row-fluid">
<div class="span6">
<label class="small">Display field
<i class="icon-question-sign" bs-tooltip="'will be used as the name of each row'"></i></label>
<input type="text" bs-typeahead="fields.list" class="input-large" ng-model="panel.node_display_field"/>
</div>
</div>
<h5>Metrics</h5>
<div class="row-fluid">
<div class="span12">
<table class="table table-condensed table-striped">
<thead>
<th>Name</th>
<th>Field</th>
<th>Warning</th>
<th>Error</th>
<th>Prec.</th>
<th/>
<th/>
</thead>
<tr ng-repeat="metric in panel.metrics">
<td ng-show="editedMetricIndex != $index">{{metric.name}}</td>
<td ng-show="editedMetricIndex != $index">{{metric.field}}</td>
<td ng-show="editedMetricIndex != $index">{{formatAlert(metric.warning)}}</td>
<td ng-show="editedMetricIndex != $index">{{formatAlert(metric.error)}}</td>
<td ng-show="editedMetricIndex != $index">{{metric.decimals}}</td>
<td ng-show="editedMetricIndex != $index">
<i ng-show="editedMetricIndex < 0" ng-click="editedMetricIndex=$index" class="pointer icon-edit"></i></td>
<td ng-show="editedMetricIndex != $index">
<i ng-show="editedMetricIndex < 0 && !$first" ng-click="_.move(panel.metrics,$index,$index-1)" class="pointer icon-arrow-up"></i>
</td>
<td ng-show="editedMetricIndex != $index">
<i ng-show="editedMetricIndex < 0 && !$last" ng-click="_.move(panel.metrics,$index,$index+1)" class="pointer icon-arrow-down"></i>
</td>
<td ng-show="editedMetricIndex == $index">
<input type="text" class="input-medium" ng-model="metric.name" ng-required/></td>
<td ng-show="editedMetricIndex == $index">
<input type="text" ng-model="metric.field" bs-typeahead="fields.list" ng-required/>
</td>
<td ng-show="editedMetricIndex == $index">
<input type="text" style="width: 30px;" alert-value ng-model="metric.warning"/>
</td>
<td ng-show="editedMetricIndex == $index">
<input type="text" style="width: 30px;" alert-value ng-model="metric.error"></td>
<td ng-show="editedMetricIndex == $index">
<input type="text" style="width: 30px;" ng-model="metric.decimals" value="2" ng-required/></td>
<td ng-show="editedMetricIndex == $index">
<i class="pointer icon-check" ng-click="editedMetricIndex=-1"></i>
</td>
<td ng-show="editedMetricIndex == $index">
<i ng-click="deleteMetric($index)" class="pointer icon-remove"></i>
<td ng-show="editedMetricIndex == $index">
<i class="icon"></i></td>
</tr>
</table>
<button ng-hide="editedMetric.index >= 0" type='button' ng-click="addMetric()" class="btn btn-success">Add Metric</button>
</div>
</div>

View file

@ -45,7 +45,7 @@
<table class="table table-bordered" ng-if="!panel.compact">
<thead>
<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>
<th ng-repeat="metric in panel.metrics" ng-class="alertClass(warnLevels['_global_'][metric.name])">{{metric.name}}</th>
</thead>
<tr ng-repeat="node in nodes">
<td>
@ -57,7 +57,7 @@
</label>
</div>
</td>
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node.id][metric.name])">
<td ng-repeat="metric in panel.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>
@ -66,7 +66,6 @@
<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>
</tr>
@ -75,7 +74,7 @@
<table class="table table-bordered table-condensed marvel-table" ng-if="panel.compact">
<thead>
<th>node <a ng-href="{{detailViewLink()}}" 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>
<th ng-repeat="metric in panel.metrics" ng-class="alertClass(warnLevels['_global_'][metric.name])">{{metric.name}}</th>
</thead>
<tr ng-repeat="node in nodes">
<td>
@ -86,7 +85,7 @@
{{ node.display_name }}
</div>
</td>
<td ng-repeat="metric in metrics" ng-class="alertClass(warnLevels[node.id][metric.name])">
<td ng-repeat="metric in panel.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>

View file

@ -25,11 +25,63 @@ define([
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.
node_persistent_field: "node.transport_address", // used as node identity - i.e., search queries, facets etc.
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
}
]
};
_.defaults($scope.panel, _d);
$scope.editedMetricIndex = -1;
var _metric_defaults = {name: "", decimals: 2, scale: 1};
_.each($scope.panel.metrics, function (m) {
_.defaults(m, _metric_defaults);
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.init = function () {
$scope.warnLevels = {};
$scope.nodes = [];
@ -127,55 +179,6 @@ define([
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;
@ -186,7 +189,7 @@ define([
.must($scope.ejs.RangeFilter('@timestamp').from(time + '||-10m/m'))
.must($scope.ejs.TermsFilter($scope.panel.node_persistent_field, id));
_.each($scope.metrics, function (m) {
_.each($scope.panel.metrics, function (m) {
request = request
.facet($scope.ejs.StatisticalFacet(id + "_" + m.name)
.field(m.field || m.name)
@ -225,6 +228,13 @@ define([
window.location = current;
};
$scope.formatAlert = function (a) {
if (!a) {
return "";
}
return (a.type === "upper_bound" ? ">" : "<") + a.threshold;
};
$scope.detailViewLink = function (nodes, fields) {
if (nodes === undefined) {
nodes = _.where($scope.nodes, {selected: true});
@ -254,10 +264,10 @@ define([
$scope.calculateWarnings = function () {
$scope.warnLevels = {_global_: {}};
_.each($scope.metrics, function (metric) {
_.each($scope.panel.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);
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]) {
@ -306,6 +316,61 @@ define([
return [];
};
$scope.parseAlert = function (s) {
if (!s) {
return null;
}
var ret = { type: "upper_bound"};
if (s[0] === '<') {
ret.type = "lower_bound";
s = s.substr(1);
} else if (s[0] === '>') {
s = s.substr(1);
}
ret.threshold = parseFloat(s);
if (isNaN(ret.threshold)) {
return null;
}
return ret;
};
$scope.addMetric = function () {
$scope.panel.metrics.push(_.defaults({}, _metric_defaults));
$scope.editedMetricIndex = $scope.panel.metrics.length - 1;
};
$scope.deleteMetric = function (index) {
$scope.panel.metrics = _.without($scope.panel.metrics, $scope.panel.metrics[index]);
$scope.editedMetricIndex = -1;
};
});
module.directive('alertValue', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
if (/(<>)?\d+(.\d+)?/.test(viewValue)) {
// it is valid
ctrl.$setValidity('alertValue', true);
return scope.parseAlert(viewValue);
} else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('alertValue', false);
return undefined;
}
});
ctrl.$formatters.unshift(function (modelValue) {
return scope.formatAlert(modelValue);
});
}
};
});
module.directive('marvelNodesHealthChart', function () {