mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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 = {};
|
||||
|
||||
103
public/app/plugins/datasource/elasticsearch/elasticResponse.js
Normal file
103
public/app/plugins/datasource/elasticsearch/elasticResponse.js
Normal 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;
|
||||
});
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user