feat(elasticsearch): extended stats like std deviation now works, and sigma option as well, added unique count (cardinality as well, #1034

This commit is contained in:
Torkel Ödegaard
2015-09-07 13:13:19 +02:00
parent efc3def7f2
commit 3999a3caa2
13 changed files with 412 additions and 280 deletions

View File

@@ -5,10 +5,11 @@ define([
'kbn',
'./queryBuilder',
'./indexPattern',
'./elasticResponse',
'./queryCtrl',
'./directives'
],
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern) {
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
'use strict';
var module = angular.module('grafana.services');
@@ -174,8 +175,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern) {
payload = payload.replace(/\$maxDataPoints/g, options.maxDataPoints);
payload = templateSrv.replace(payload, options.scopedVars);
var processTimeSeries = _.bind(this._processTimeSeries, this, sentTargets);
return this._post('/_msearch?search_type=count', payload).then(processTimeSeries);
return this._post('/_msearch?search_type=count', payload).then(function(res) {
return new ElasticResponse(sentTargets, res).getTimeSeries();
});
};
ElasticDatasource.prototype.translateTime = function(date) {
@@ -186,94 +188,6 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern) {
return date.getTime();
};
// This is quite complex
// neeed to recurise down the nested buckets to build series
ElasticDatasource.prototype._processBuckets = function(aggs, target, series, level, parentName) {
var seriesName, value, metric, i, y, bucket, aggDef, esAgg;
function addMetricPoint(seriesName, value, time) {
var current = series[seriesName];
if (!current) {
current = series[seriesName] = {target: seriesName, datapoints: []};
}
current.datapoints.push([value, time]);
}
aggDef = target.bucketAggs[level];
esAgg = aggs[aggDef.id];
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
// if last agg collect series
if (level === target.bucketAggs.length - 1) {
for (y = 0; y < target.metrics.length; y++) {
metric = target.metrics[y];
seriesName = parentName;
switch(metric.type) {
case 'count': {
seriesName += ' count';
value = bucket.doc_count;
addMetricPoint(seriesName, value, bucket.key);
break;
}
case 'percentiles': {
var values = bucket[metric.id].values;
for (var prop in values) {
addMetricPoint(seriesName + ' ' + prop, values[prop], bucket.key);
}
break;
}
case 'extended_stats': {
var stats = bucket[metric.id];
for (var statIndex in metric.stats) {
var statName = metric.stats[statIndex];
addMetricPoint(seriesName + ' ' + statName, stats[statName], bucket.key);
}
break;
}
default: {
seriesName += ' ' + metric.field + ' ' + metric.type;
value = bucket[metric.id].value;
addMetricPoint(seriesName, value, bucket.key);
break;
}
}
}
}
else {
this._processBuckets(bucket, target, series, level+1, parentName + ' ' + bucket.key);
}
}
};
ElasticDatasource.prototype._processTimeSeries = function(targets, results) {
var series = [];
for (var i = 0; i < results.responses.length; i++) {
var response = results.responses[i];
if (response.error) {
throw { message: response.error };
}
var aggregations = response.aggregations;
var target = targets[i];
var querySeries = {};
this._processBuckets(aggregations, target, querySeries, 0, target.refId);
for (var prop in querySeries) {
if (querySeries.hasOwnProperty(prop)) {
series.push(querySeries[prop]);
}
}
}
return { data: series };
};
ElasticDatasource.prototype.metricFindQuery = function() {
return this._get('/_mapping').then(function(res) {
var fields = {};

View File

@@ -0,0 +1,103 @@
define([
],
function () {
'use strict';
function ElasticResponse(targets, response) {
this.targets = targets;
this.response = response;
}
// This is quite complex
// neeed to recurise down the nested buckets to build series
ElasticResponse.prototype.processBuckets = function(aggs, target, series, level, parentName) {
var seriesName, value, metric, i, y, bucket, aggDef, esAgg;
function addMetricPoint(seriesName, value, time) {
var current = series[seriesName];
if (!current) {
current = series[seriesName] = {target: seriesName, datapoints: []};
}
current.datapoints.push([value, time]);
}
aggDef = target.bucketAggs[level];
esAgg = aggs[aggDef.id];
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
// if last agg collect series
if (level === target.bucketAggs.length - 1) {
for (y = 0; y < target.metrics.length; y++) {
metric = target.metrics[y];
seriesName = parentName;
switch(metric.type) {
case 'count': {
seriesName += ' count';
value = bucket.doc_count;
addMetricPoint(seriesName, value, bucket.key);
break;
}
case 'percentiles': {
var values = bucket[metric.id].values;
for (var prop in values) {
addMetricPoint(seriesName + ' ' + prop, values[prop], bucket.key);
}
break;
}
case 'extended_stats': {
var stats = bucket[metric.id];
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
for (var statName in metric.meta) {
if (metric.meta[statName]) {
addMetricPoint(seriesName + ' ' + statName, stats[statName], bucket.key);
}
}
break;
}
default: {
seriesName += ' ' + metric.field + ' ' + metric.type;
value = bucket[metric.id].value;
addMetricPoint(seriesName, value, bucket.key);
break;
}
}
}
}
else {
this.processBuckets(bucket, target, series, level+1, parentName + ' ' + bucket.key);
}
}
};
ElasticResponse.prototype.getTimeSeries = function() {
var series = [];
for (var i = 0; i < this.response.responses.length; i++) {
var response = this.response.responses[i];
if (response.error) {
throw { message: response.error };
}
var aggregations = response.aggregations;
var target = this.targets[i];
var querySeries = {};
this.processBuckets(aggregations, target, querySeries, 0, target.refId);
for (var prop in querySeries) {
if (querySeries.hasOwnProperty(prop)) {
series.push(querySeries[prop]);
}
}
}
return { data: series };
};
return ElasticResponse;
});

View File

@@ -12,6 +12,7 @@ function (angular, _, queryDef) {
var metricAggs = $scope.target.metrics;
$scope.metricAggTypes = queryDef.metricAggTypes;
$scope.extendedStats = queryDef.extendedStats;
$scope.init = function() {
$scope.agg = metricAggs[$scope.index];
@@ -40,8 +41,14 @@ function (angular, _, queryDef) {
break;
}
case 'extended_stats': {
$scope.agg.stats = $scope.agg.stats || ['std_deviation'];
$scope.settingsLinkText = 'Stats: ' + $scope.agg.stats.join(',');
var stats = _.reduce($scope.agg.meta, function(memo, val, key) {
if (val) {
var def = _.findWhere($scope.extendedStats, {value: key});
memo.push(def.text);
}
return memo;
}, []);
$scope.settingsLinkText = 'Stats: ' + stats.join(', ');
}
}
};
@@ -52,6 +59,9 @@ function (angular, _, queryDef) {
$scope.onTypeChange = function() {
$scope.agg.settings = {};
$scope.agg.meta = {};
$scope.showOptions = false;
$scope.validateModel();
$scope.onChange();
};

View File

@@ -27,7 +27,7 @@
</div>
<div class="tight-form" ng-if="showOptions">
<div style="tight-form-inner-box" ng-if="agg.type === 'terms'">
<div class="tight-form-inner-box" ng-if="agg.type === 'terms'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 60px">

View File

@@ -26,7 +26,7 @@
</div>
<div class="tight-form" ng-if="showOptions">
<div style="margin: 20px 0 20px 148px;display: inline-block">
<div class="tight-form-inner-box">
<div class="tight-form last" ng-if="agg.type === 'percentiles'">
<ul class="tight-form-list">
<li class="tight-form-item">
@@ -38,5 +38,29 @@
</ul>
<div class="clearfix"></div>
</div>
<div ng-if="agg.type === 'extended_stats'">
<div class="tight-form" ng-repeat="stat in extendedStats">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
{{stat.text}}
</li>
<li class="tight-form-item last">
<editor-checkbox text="" model="agg.meta.{{stat.value}}" change="onChange()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="tight-form last" ng-if="agg.type === 'extended_stats'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Sigma
</li>
<li>
<input type="number" class="input-mini tight-form-input last" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@@ -105,7 +105,7 @@ function (angular) {
var metricAgg = {field: metric.field};
for (var prop in metric.settings) {
if (metric.settings.hasOwnProperty(prop)) {
if (metric.settings.hasOwnProperty(prop) && metric.settings[prop] !== null) {
metricAgg[prop] = metric.settings[prop];
}
}

View File

@@ -13,6 +13,7 @@ function (_) {
{text: "Min of", value: 'min' },
{text: "Extended Stats", value: 'extended_stats' },
{text: "Percentiles", value: 'percentiles' },
{text: "Unique Count", value: "cardinality" }
],
bucketAggTypes: [
@@ -41,6 +42,17 @@ function (_) {
{text: "20", value: '20' },
],
extendedStats: [
{text: 'Avg', value: 'avg'},
{text: 'Min', value: 'min'},
{text: 'Max', value: 'max'},
{text: 'Sum', value: 'sum'},
{text: 'Count', value: 'count'},
{text: 'Std Dev', value: 'std_deviation'},
{text: 'Std Dev Upper', value: 'std_deviation_bounds_upper'},
{text: 'Std Dev Lower', value: 'std_deviation_bounds_lower'},
],
getOrderByOptions: function(target) {
var self = this;
var metricRefs = [];