Browse Source

feat(elasticsearch): worked on percentiles metric aggregator in editor and in elasticsearch response processing

Torkel Ödegaard 10 years ago
parent
commit
f942ec952e

+ 8 - 6
public/app/plugins/datasource/elasticsearch/bucketAgg.js

@@ -33,24 +33,26 @@ function (angular, _, queryDef) {
     };
     };
 
 
     $scope.validateModel = function() {
     $scope.validateModel = function() {
+      $scope.index = _.indexOf(bucketAggs, $scope.agg);
+
       $scope.isFirst = $scope.index === 0;
       $scope.isFirst = $scope.index === 0;
       $scope.isLast = $scope.index === bucketAggs.length - 1;
       $scope.isLast = $scope.index === bucketAggs.length - 1;
-      $scope.aggOptionsString = "";
+      $scope.settingsLinkText = "";
 
 
       if ($scope.agg.type === "terms") {
       if ($scope.agg.type === "terms") {
         $scope.agg.order = $scope.agg.order || "desc";
         $scope.agg.order = $scope.agg.order || "desc";
         $scope.agg.size = $scope.agg.size || "0";
         $scope.agg.size = $scope.agg.size || "0";
-        $scope.agg.orderBy = $scope.agg.orderBy || "_count";
+        $scope.agg.orderBy = $scope.agg.orderBy || "_term";
 
 
         if ($scope.agg.size === '0') {
         if ($scope.agg.size === '0') {
-          $scope.aggOptionsString = "";
+          $scope.settingsLinkText = "";
         } else {
         } else {
-          $scope.aggOptionsString = queryDef.describeOrder($scope.agg.order) + ' ' + $scope.agg.size + ', '
+          $scope.settingsLinkText = queryDef.describeOrder($scope.agg.order) + ' ' + $scope.agg.size + ', '
         }
         }
-        $scope.aggOptionsString += 'Order by: ' + queryDef.describeOrderBy($scope.agg.orderBy, $scope.target);
+        $scope.settingsLinkText += 'Order by: ' + queryDef.describeOrderBy($scope.agg.orderBy, $scope.target);
 
 
         if ($scope.agg.size === '0') {
         if ($scope.agg.size === '0') {
-          $scope.aggOptionsString += ' (' + $scope.agg.order + ')';
+          $scope.settingsLinkText += ' (' + $scope.agg.order + ')';
         }
         }
       }
       }
 
 

+ 29 - 10
public/app/plugins/datasource/elasticsearch/datasource.js

@@ -175,10 +175,18 @@ function (angular, _, config, kbn, moment, ElasticQueryBuilder) {
     // This is quite complex
     // This is quite complex
     // neeed to recurise down the nested buckets to build series
     // neeed to recurise down the nested buckets to build series
     ElasticDatasource.prototype._processBuckets = function(aggs, target, series, level, parentName) {
     ElasticDatasource.prototype._processBuckets = function(aggs, target, series, level, parentName) {
-      var seriesName, value, metric, i, y, bucket, childBucket, aggDef, esAgg;
+      var seriesName, value, metric, i, y, z, bucket, childBucket, aggDef, esAgg;
       var buckets;
       var buckets;
       var dataFound = 0;
       var dataFound = 0;
 
 
+      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];
       aggDef = target.bucketAggs[level];
       esAgg = aggs[aggDef.id];
       esAgg = aggs[aggDef.id];
 
 
@@ -191,16 +199,27 @@ function (angular, _, config, kbn, moment, ElasticQueryBuilder) {
             metric = target.metrics[y];
             metric = target.metrics[y];
             seriesName = parentName;
             seriesName = parentName;
 
 
-            if (metric.type === 'count') {
-              seriesName += ' count';
-              value = bucket.doc_count;
-            } else {
-              seriesName += ' ' + metric.field + ' ' + metric.type;
-              value = bucket[metric.id].value;
+            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;
+              }
+              default: {
+                seriesName += ' ' + metric.field + ' ' + metric.type;
+                value = bucket[metric.id].value;
+                addMetricPoint(seriesName, value, bucket.key);
+                break;
+              }
             }
             }
-
-            var serie = series[seriesName] = series[seriesName] || {target: seriesName, datapoints: []};
-            serie.datapoints.push([value, bucket.key]);
           }
           }
         }
         }
         else {
         else {

+ 23 - 7
public/app/plugins/datasource/elasticsearch/metricAgg.js

@@ -8,27 +8,43 @@ function (angular, _, queryDef) {
 
 
   var module = angular.module('grafana.directives');
   var module = angular.module('grafana.directives');
 
 
-  module.controller('ElasticMetricAggCtrl', function($scope, uiSegmentSrv, $q) {
+  module.controller('ElasticMetricAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) {
     var metricAggs = $scope.target.metrics;
     var metricAggs = $scope.target.metrics;
 
 
     $scope.metricAggTypes = queryDef.metricAggTypes;
     $scope.metricAggTypes = queryDef.metricAggTypes;
 
 
     $scope.init = function() {
     $scope.init = function() {
       $scope.agg = metricAggs[$scope.index];
       $scope.agg = metricAggs[$scope.index];
-      if (!$scope.agg.field) {
-        $scope.agg.field = 'select field';
-      }
+      $scope.validateModel();
     }
     }
 
 
-    $scope.$watchCollection("target.metrics", function() {
+    $rootScope.onAppEvent('elastic-query-updated', function() {
+      $scope.index = _.indexOf(metricAggs, $scope.agg);
+
       $scope.isFirst = $scope.index === 0;
       $scope.isFirst = $scope.index === 0;
-      $scope.isLast = $scope.index === metricAggs.length - 1;
       $scope.isSingle = metricAggs.length === 1;
       $scope.isSingle = metricAggs.length === 1;
+      $scope.validateModel();
     });
     });
 
 
+    $scope.validateModel = function() {
+      if (!$scope.agg.field) {
+        $scope.agg.field = 'select field';
+      }
+
+      if ($scope.agg.type === 'percentiles') {
+        $scope.agg.settings.percents = $scope.agg.settings.percents || [25,50,75,95,99];
+        $scope.settingsLinkText = 'values: ' + $scope.agg.settings.percents.join(',');
+      }
+    }
+
     $scope.toggleOptions = function() {
     $scope.toggleOptions = function() {
       $scope.showOptions = !$scope.showOptions;
       $scope.showOptions = !$scope.showOptions;
-    }
+    };
+
+    $scope.onTypeChange = function() {
+      $scope.agg.settings = {};
+      $scope.onChange();
+    };
 
 
     $scope.addMetricAgg = function() {
     $scope.addMetricAgg = function() {
       var addIndex = metricAggs.length;
       var addIndex = metricAggs.length;

+ 2 - 2
public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html

@@ -8,8 +8,8 @@
 			<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onChangeInternal()"></metric-segment-model>
 			<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onChangeInternal()"></metric-segment-model>
 			<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChangeInternal()"></metric-segment>
 			<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChangeInternal()"></metric-segment>
 		</li>
 		</li>
-		<li class="tight-form-item tight-form-align" ng-if="aggOptionsString">
-			<a ng-click="toggleOptions()">{{aggOptionsString}}</a>
+		<li class="tight-form-item tight-form-align" ng-if="settingsLinkText">
+			<a ng-click="toggleOptions()">{{settingsLinkText}}</a>
 		</li>
 		</li>
 	</ul>
 	</ul>
 
 

+ 18 - 4
public/app/plugins/datasource/elasticsearch/partials/metricAgg.html

@@ -4,13 +4,13 @@
 			Metric
 			Metric
 		</li>
 		</li>
 		<li>
 		<li>
-			<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onChange()"></metric-segment-model>
+			<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()"></metric-segment-model>
 		</li>
 		</li>
 		<li ng-if="agg.type !== 'count'">
 		<li ng-if="agg.type !== 'count'">
 			<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChange()"></metric-segment>
 			<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChange()"></metric-segment>
 		</li>
 		</li>
-		<li class="tight-form-item tight-form-align" ng-if="aggOptionsString">
-			<a ng-click="toggleOptions()">{{aggOptionsString}}</a>
+		<li class="tight-form-item tight-form-align" ng-if="settingsLinkText">
+			<a ng-click="toggleOptions()">{{settingsLinkText}}</a>
 		</li>
 		</li>
 	</ul>
 	</ul>
 
 
@@ -25,4 +25,18 @@
 	<div class="clearfix"></div>
 	<div class="clearfix"></div>
 </div>
 </div>
 
 
-
+<div class="tight-form" ng-if="showOptions">
+	<div style="margin: 20px 0 20px 148px;display: inline-block">
+		<div class="tight-form last" ng-if="agg.type === 'percentiles'">
+			<ul class="tight-form-list">
+				<li class="tight-form-item">
+					Percentiles
+				</li>
+				<li>
+					<input type="text" class="input-xlarge tight-form-input last" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
+				</li>
+			</ul>
+			<div class="clearfix"></div>
+		</div>
+	</div>
+</div>

+ 9 - 2
public/app/plugins/datasource/elasticsearch/queryBuilder.js

@@ -48,7 +48,7 @@ function (angular) {
           esAgg["date_histogram"] = {
           esAgg["date_histogram"] = {
             "interval": target.interval || "$interval",
             "interval": target.interval || "$interval",
             "field": aggDef.field,
             "field": aggDef.field,
-            "min_doc_count": 0,
+            "min_doc_count": 1,
             "extended_bounds": { "min": "$timeFrom", "max": "$timeTo" }
             "extended_bounds": { "min": "$timeFrom", "max": "$timeTo" }
           };
           };
           break;
           break;
@@ -92,8 +92,15 @@ function (angular) {
         continue;
         continue;
       }
       }
 
 
+      var metricAgg = {field: metric.field};
+      for (var prop in metric.settings) {
+        if (metric.settings.hasOwnProperty(prop)) {
+          metricAgg[prop] = metric.settings[prop];
+        }
+      }
+
       var aggField = {};
       var aggField = {};
-      aggField[metric.type] = {field: metric.field};
+      aggField[metric.type] = metricAgg;
       nestedAggs.aggs[metric.id] = aggField;
       nestedAggs.aggs[metric.id] = aggField;
     }
     }
 
 

+ 1 - 0
public/app/plugins/datasource/elasticsearch/queryDef.js

@@ -12,6 +12,7 @@ function (_) {
       {text: "Max of",  value: 'max' },
       {text: "Max of",  value: 'max' },
       {text: "Min of",  value: 'min' },
       {text: "Min of",  value: 'min' },
       {text: "Standard Deviations",  value: 'std_dev' },
       {text: "Standard Deviations",  value: 'std_dev' },
+      {text: "Percentiles",  value: 'percentiles' },
     ],
     ],
 
 
     bucketAggTypes: [
     bucketAggTypes: [

+ 25 - 0
public/test/specs/elasticsearch-querybuilder-specs.js

@@ -65,6 +65,31 @@ define([
       expect(secondLevel.aggs["5"].avg.field).to.be("@value");
       expect(secondLevel.aggs["5"].avg.field).to.be("@value");
     });
     });
 
 
+    it('with metric percentiles', function() {
+      var builder = new ElasticQueryBuilder();
+
+      var query = builder.build({
+        metrics: [
+          {
+            id: '1',
+            type: 'percentiles',
+            field: '@load_time',
+            settings: {
+              percents: [1,2,3,4]
+            }
+          }
+        ],
+        bucketAggs: [
+          {type: 'date_histogram', field: '@timestamp', id: '3'}
+        ],
+      }, 100, 1000);
+
+      var firstLevel = query.aggs["3"];
+
+      expect(firstLevel.aggs["1"].percentiles.field).to.be("@load_time");
+      expect(firstLevel.aggs["1"].percentiles.percents).to.eql([1,2,3,4]);
+    });
+
   });
   });
 
 
 });
 });

+ 42 - 0
public/test/specs/elasticsearch-specs.js

@@ -145,6 +145,48 @@ define([
         });
         });
       });
       });
 
 
+      describe('with percentiles ', function() {
+        var result;
+
+        beforeEach(function() {
+          result = ctx.ds._processTimeSeries([{
+            refId: 'A',
+            metrics: [{type: 'percentiles', settings: {percents: [75, 90]},  id: '1'}],
+            bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '3'}],
+          }], {
+            responses: [{
+              aggregations: {
+                "3": {
+                  buckets: [
+                    {
+                      "1": {values: {"75": 3.3, "90": 5.5}},
+                      doc_count: 10,
+                      key: 1000
+                    },
+                    {
+                      "1": {values: {"75": 2.3, "90": 4.5}},
+                      doc_count: 15,
+                      key: 2000
+                    }
+                  ]
+                }
+              }
+            }]
+          });
+        });
+
+        it('should return 2 series', function() {
+          expect(result.data.length).to.be(2);
+          expect(result.data[0].datapoints.length).to.be(2);
+          expect(result.data[0].target).to.be('A 75');
+          expect(result.data[1].target).to.be('A 90');
+          expect(result.data[0].datapoints[0][0]).to.be(3.3);
+          expect(result.data[0].datapoints[0][1]).to.be(1000);
+          expect(result.data[1].datapoints[1][0]).to.be(4.5);
+        });
+      });
+
+
     });
     });
   });
   });
 });
 });