From f942ec952e562d0c393808a48687233fc2b73c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 5 Sep 2015 18:31:42 +0200 Subject: [PATCH] feat(elasticsearch): worked on percentiles metric aggregator in editor and in elasticsearch response processing --- .../datasource/elasticsearch/bucketAgg.js | 14 ++++--- .../datasource/elasticsearch/datasource.js | 39 ++++++++++++----- .../datasource/elasticsearch/metricAgg.js | 32 ++++++++++---- .../elasticsearch/partials/bucketAgg.html | 4 +- .../elasticsearch/partials/metricAgg.html | 22 ++++++++-- .../datasource/elasticsearch/queryBuilder.js | 11 ++++- .../datasource/elasticsearch/queryDef.js | 1 + .../specs/elasticsearch-querybuilder-specs.js | 25 +++++++++++ public/test/specs/elasticsearch-specs.js | 42 +++++++++++++++++++ 9 files changed, 158 insertions(+), 32 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/bucketAgg.js b/public/app/plugins/datasource/elasticsearch/bucketAgg.js index 03b58569325..6a618cceea7 100644 --- a/public/app/plugins/datasource/elasticsearch/bucketAgg.js +++ b/public/app/plugins/datasource/elasticsearch/bucketAgg.js @@ -33,24 +33,26 @@ function (angular, _, queryDef) { }; $scope.validateModel = function() { + $scope.index = _.indexOf(bucketAggs, $scope.agg); + $scope.isFirst = $scope.index === 0; $scope.isLast = $scope.index === bucketAggs.length - 1; - $scope.aggOptionsString = ""; + $scope.settingsLinkText = ""; if ($scope.agg.type === "terms") { $scope.agg.order = $scope.agg.order || "desc"; $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') { - $scope.aggOptionsString = ""; + $scope.settingsLinkText = ""; } 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') { - $scope.aggOptionsString += ' (' + $scope.agg.order + ')'; + $scope.settingsLinkText += ' (' + $scope.agg.order + ')'; } } diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 7622fa243c0..38b28b7fc02 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -175,10 +175,18 @@ function (angular, _, config, kbn, moment, ElasticQueryBuilder) { // 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, childBucket, aggDef, esAgg; + var seriesName, value, metric, i, y, z, bucket, childBucket, aggDef, esAgg; var buckets; 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]; esAgg = aggs[aggDef.id]; @@ -191,16 +199,27 @@ function (angular, _, config, kbn, moment, ElasticQueryBuilder) { metric = target.metrics[y]; 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 { diff --git a/public/app/plugins/datasource/elasticsearch/metricAgg.js b/public/app/plugins/datasource/elasticsearch/metricAgg.js index f6670879741..fa5fa837c93 100644 --- a/public/app/plugins/datasource/elasticsearch/metricAgg.js +++ b/public/app/plugins/datasource/elasticsearch/metricAgg.js @@ -8,27 +8,43 @@ function (angular, _, queryDef) { 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; $scope.metricAggTypes = queryDef.metricAggTypes; $scope.init = function() { $scope.agg = metricAggs[$scope.index]; + $scope.validateModel(); + } + + $rootScope.onAppEvent('elastic-query-updated', function() { + $scope.index = _.indexOf(metricAggs, $scope.agg); + + $scope.isFirst = $scope.index === 0; + $scope.isSingle = metricAggs.length === 1; + $scope.validateModel(); + }); + + $scope.validateModel = function() { if (!$scope.agg.field) { $scope.agg.field = 'select field'; } - } - $scope.$watchCollection("target.metrics", function() { - $scope.isFirst = $scope.index === 0; - $scope.isLast = $scope.index === metricAggs.length - 1; - $scope.isSingle = metricAggs.length === 1; - }); + 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.showOptions = !$scope.showOptions; - } + }; + + $scope.onTypeChange = function() { + $scope.agg.settings = {}; + $scope.onChange(); + }; $scope.addMetricAgg = function() { var addIndex = metricAggs.length; diff --git a/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html b/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html index e89230f0c97..6186ebba114 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html @@ -8,8 +8,8 @@ -
  • - {{aggOptionsString}} +
  • + {{settingsLinkText}}
  • diff --git a/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html b/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html index 109e7c63cec..6fcb49d49b0 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html @@ -4,13 +4,13 @@ Metric
  • - +
  • -
  • - {{aggOptionsString}} +
  • + {{settingsLinkText}}
  • @@ -25,4 +25,18 @@
    - +
    +
    +
    +
      +
    • + Percentiles +
    • +
    • + +
    • +
    +
    +
    +
    +
    diff --git a/public/app/plugins/datasource/elasticsearch/queryBuilder.js b/public/app/plugins/datasource/elasticsearch/queryBuilder.js index 2eef154e83b..0090e5613f8 100644 --- a/public/app/plugins/datasource/elasticsearch/queryBuilder.js +++ b/public/app/plugins/datasource/elasticsearch/queryBuilder.js @@ -48,7 +48,7 @@ function (angular) { esAgg["date_histogram"] = { "interval": target.interval || "$interval", "field": aggDef.field, - "min_doc_count": 0, + "min_doc_count": 1, "extended_bounds": { "min": "$timeFrom", "max": "$timeTo" } }; break; @@ -92,8 +92,15 @@ function (angular) { continue; } + var metricAgg = {field: metric.field}; + for (var prop in metric.settings) { + if (metric.settings.hasOwnProperty(prop)) { + metricAgg[prop] = metric.settings[prop]; + } + } + var aggField = {}; - aggField[metric.type] = {field: metric.field}; + aggField[metric.type] = metricAgg; nestedAggs.aggs[metric.id] = aggField; } diff --git a/public/app/plugins/datasource/elasticsearch/queryDef.js b/public/app/plugins/datasource/elasticsearch/queryDef.js index 46172eb0ad7..57b3a3e2e24 100644 --- a/public/app/plugins/datasource/elasticsearch/queryDef.js +++ b/public/app/plugins/datasource/elasticsearch/queryDef.js @@ -12,6 +12,7 @@ function (_) { {text: "Max of", value: 'max' }, {text: "Min of", value: 'min' }, {text: "Standard Deviations", value: 'std_dev' }, + {text: "Percentiles", value: 'percentiles' }, ], bucketAggTypes: [ diff --git a/public/test/specs/elasticsearch-querybuilder-specs.js b/public/test/specs/elasticsearch-querybuilder-specs.js index 6622a2462a6..b28f5eea41f 100644 --- a/public/test/specs/elasticsearch-querybuilder-specs.js +++ b/public/test/specs/elasticsearch-querybuilder-specs.js @@ -65,6 +65,31 @@ define([ 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]); + }); + }); }); diff --git a/public/test/specs/elasticsearch-specs.js b/public/test/specs/elasticsearch-specs.js index f4de2cf3e8c..5c3c9049721 100644 --- a/public/test/specs/elasticsearch-specs.js +++ b/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); + }); + }); + + }); }); });