Commit 572a80d1 by Torkel Ödegaard

feat(elasticsearch): metric response handling and processsing now supports alias…

feat(elasticsearch): metric response handling and processsing now supports alias patterns, {{term field name}} and {{metric}} now works, #1034
parent 2aa695fb
......@@ -11,8 +11,8 @@ function (_) {
// This is quite complex
// neeed to recurise down the nested buckets to build series
ElasticResponse.prototype.processBuckets = function(aggs, target, series, level) {
var value, metric, i, y, bucket, aggDef, esAgg, nestedSeries;
ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, level, props) {
var value, metric, i, y, bucket, aggDef, esAgg, newSeries;
aggDef = target.bucketAggs[level];
esAgg = aggs[aggDef.id];
......@@ -20,9 +20,9 @@ function (_) {
if (level < target.bucketAggs.length - 1) {
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
nestedSeries = {prop: {key: bucket.key, field: aggDef.field}, series: []};
series.push(nestedSeries);
this.processBuckets(bucket, target, nestedSeries.series, level+1);
props = _.clone(props);
props[aggDef.field] = bucket.key;
this.processBuckets(bucket, target, seriesList, level+1, props);
}
return;
}
......@@ -32,54 +32,86 @@ function (_) {
switch(metric.type) {
case 'count': {
var countSeries = { datapoints: [], metric: 'count'};
newSeries = { datapoints: [], metric: 'count', props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
value = bucket.doc_count;
countSeries.datapoints.push([value, bucket.key]);
newSeries.datapoints.push([value, bucket.key]);
}
series.push(countSeries);
seriesList.push(newSeries);
break;
}
case 'percentiles': {
// for (i = 0; i < esAgg.buckets.length; i++) {
// bucket = esAgg.buckets[i];
// var values = bucket[metric.id].values;
// for (var prop in values) {
// addMetricPoint(seriesName + ' ' + prop, values[prop], bucket.key);
// }
// }
if (esAgg.buckets.length === 0) {
break;
}
var firstBucket = esAgg.buckets[0];
var percentiles = firstBucket[metric.id].values;
for (var percentileName in percentiles) {
newSeries = {datapoints: [], metric: 'p' + percentileName, props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
var values = bucket[metric.id].values;
newSeries.datapoints.push([values[percentileName], bucket.key]);
}
seriesList.push(newSeries);
}
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);
// }
// }
for (var statName in metric.meta) {
if (!metric.meta[statName]) {
continue;
}
newSeries = {datapoints: [], metric: statName, props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
var stats = bucket[metric.id];
// add stats that are in nested obj to top level obj
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
newSeries.datapoints.push([stats[statName], bucket.key]);
}
seriesList.push(newSeries);
}
break;
}
default: {
var newSeries = { datapoints: [], metric: metric.type + ' ' + metric.field };
newSeries = { datapoints: [], metric: metric.type + ' ' + metric.field, props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
value = bucket[metric.id].value;
newSeries.datapoints.push([value, bucket.key]);
}
series.push(newSeries);
seriesList.push(newSeries);
break;
}
}
}
};
ElasticResponse.prototype._getSeriesName = function(props, metric, alias) {
if (alias) {
return alias;
ElasticResponse.prototype._getSeriesName = function(props, metric, target, metricTypeCount) {
if (target.alias) {
var regex = /\{\{([\s\S]+?)\}\}/g;
return target.alias.replace(regex, function(match, g1, g2) {
var group = g1 || g2;
if (props[group]) { return props[group]; }
if (group === 'metric') { return metric; }
return match;
});
}
var propKeys = _.keys(props);
......@@ -92,31 +124,15 @@ function (_) {
name += props[propName] + ' ';
}
if (propKeys.length === 1) {
if (metricTypeCount === 1) {
return name.trim();
}
return name.trim() + ' ' + metric;
};
ElasticResponse.prototype._collectSeriesFromTree = function(seriesTree, props, seriesList, alias) {
console.log('props: ', props);
for (var i = 0; i < seriesTree.length; i++) {
var series = seriesTree[i];
if (series.datapoints) {
series.target = this._getSeriesName(props, series.metric, alias);
seriesList.push(series);
} else {
props = _.clone(props);
props[series.prop.field] = series.prop.key;
this._collectSeriesFromTree(series.series, props, seriesList);
}
}
};
ElasticResponse.prototype.getTimeSeries = function() {
var series = [];
var seriesList = [];
for (var i = 0; i < this.response.responses.length; i++) {
var response = this.response.responses[i];
......@@ -126,13 +142,20 @@ function (_) {
var aggregations = response.aggregations;
var target = this.targets[i];
var seriesTree = [];
var tmpSeriesList = [];
this.processBuckets(aggregations, target, tmpSeriesList, 0, {});
this.processBuckets(aggregations, target, seriesTree, 0, '');
this._collectSeriesFromTree(seriesTree, {}, series, '');
var metricTypeCount = _.uniq(_.pluck(tmpSeriesList, 'metric')).length;
for (var y = 0; y < tmpSeriesList.length; y++) {
var series= tmpSeriesList[y];
series.target = this._getSeriesName(series.props, series.metric, target, metricTypeCount);
seriesList.push(series);
}
}
return { data: series };
return { data: seriesList };
};
return ElasticResponse;
......
......@@ -27,7 +27,6 @@ function (angular, _, queryDef) {
$scope.validateModel = function() {
$scope.isFirst = $scope.index === 0;
$scope.isSingle = metricAggs.length === 1;
$scope.settingsLinkText = '';
if (!$scope.agg.field) {
......
......@@ -47,8 +47,13 @@
<li>
<input type="text" class="tight-form-input" style="width: 345px;" ng-model="target.query" spellcheck='false' placeholder="Lucence query" ng-blur="get_data()">
</li>
<li class="tight-form-item query-keyword">
Alias
</li>
<li>
<input type="text" class="tight-form-input" style="width: 245px;" ng-model="target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="get_data()">
</li>
</ul>
<div class="clearfix"></div>
<div style="padding: 10px" ng-if="target.rawQuery">
......@@ -57,7 +62,6 @@
</div>
<div ng-hide="target.rawQuery">
<div ng-repeat="agg in target.metrics">
<elastic-metric-agg
target="target" index="$index"
......
......@@ -105,25 +105,5 @@ function (_) {
return list;
};
p.createNameForSeries = function(seriesName, groupByColValue) {
var regex = /\$(\w+)/g;
var segments = seriesName.split('.');
return this.alias.replace(regex, function(match, group) {
if (group === 's') {
return seriesName;
}
else if (group === 'g') {
return groupByColValue;
}
var index = parseInt(group);
if (_.isNumber(index) && index < segments.length) {
return segments[index];
}
return match;
});
};
return InfluxSeries;
});
......@@ -94,7 +94,7 @@ define([
});
describe('single group by query', function() {
describe('single group by query one metric', function() {
var result;
beforeEach(function() {
......@@ -145,7 +145,60 @@ define([
});
});
describe.skip('with percentiles ', function() {
describe('single group by query two metrics', function() {
var result;
beforeEach(function() {
targets = [{
refId: 'A',
metrics: [{type: 'count', id: '1'}, {type: 'avg', field: '@value', id: '4'}],
bucketAggs: [{type: 'terms', field: 'host', id: '2'}, {type: 'date_histogram', field: '@timestamp', id: '3'}],
}];
response = {
responses: [{
aggregations: {
"2": {
buckets: [
{
"3": {
buckets: [
{ "4": {value: 10}, doc_count: 1, key: 1000},
{ "4": {value: 12}, doc_count: 3, key: 2000}
]
},
doc_count: 4,
key: 'server1',
},
{
"3": {
buckets: [
{ "4": {value: 20}, doc_count: 1, key: 1000},
{ "4": {value: 32}, doc_count: 3, key: 2000}
]
},
doc_count: 10,
key: 'server2',
},
]
}
}
}]
};
result = new ElasticResponse(targets, response).getTimeSeries();
});
it('should return 2 series', function() {
expect(result.data.length).to.be(4);
expect(result.data[0].datapoints.length).to.be(2);
expect(result.data[0].target).to.be('server1 count');
expect(result.data[1].target).to.be('server1 avg @value');
expect(result.data[2].target).to.be('server2 count');
expect(result.data[3].target).to.be('server2 avg @value');
});
});
describe('with percentiles ', function() {
var result;
beforeEach(function() {
......@@ -181,22 +234,22 @@ define([
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('75');
expect(result.data[1].target).to.be('90');
expect(result.data[0].target).to.be('p75');
expect(result.data[1].target).to.be('p90');
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);
});
});
describe.skip('with extended_stats ', function() {
describe('with extended_stats', function() {
var result;
beforeEach(function() {
targets = [{
refId: 'A',
metrics: [{type: 'extended_stats', meta: {max: true, std_deviation_bounds_upper: true}, id: '1'}],
bucketAggs: [{type: 'date_histogram', id: '3'}],
bucketAggs: [{type: 'terms', field: 'host', id: '3'}, {type: 'date_histogram', id: '4'}],
}];
response = {
responses: [{
......@@ -204,15 +257,25 @@ define([
"3": {
buckets: [
{
"1": {max: 10.2, min: 5.5, std_deviation_bounds: {upper: 3, lower: -2}},
doc_count: 10,
key: 1000
key: 'server1',
"4": {
buckets: [{
"1": {max: 10.2, min: 5.5, std_deviation_bounds: {upper: 3, lower: -2}},
doc_count: 10,
key: 1000
}]
}
},
{
"1": {max: 7.2, min: 3.5, std_deviation_bounds: {upper: 4, lower: -1}},
doc_count: 15,
key: 2000
}
key: 'server2',
"4": {
buckets: [{
"1": {max: 10.2, min: 5.5, std_deviation_bounds: {upper: 3, lower: -2}},
doc_count: 10,
key: 1000
}]
}
},
]
}
}
......@@ -222,28 +285,25 @@ define([
result = new ElasticResponse(targets, response).getTimeSeries();
});
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('max');
expect(result.data[1].target).to.be('std_deviation_bounds_upper');
it('should return 4 series', function() {
expect(result.data.length).to.be(4);
expect(result.data[0].datapoints.length).to.be(1);
expect(result.data[0].target).to.be('server1 max');
expect(result.data[1].target).to.be('server1 std_deviation_bounds_upper');
expect(result.data[0].datapoints[0][0]).to.be(10.2);
expect(result.data[0].datapoints[1][0]).to.be(7.2);
expect(result.data[1].datapoints[0][0]).to.be(3);
expect(result.data[1].datapoints[1][0]).to.be(4);
});
});
describe.skip('single group by with alias pattern', function() {
describe('single group by with alias pattern', function() {
var result;
beforeEach(function() {
targets = [{
refId: 'A',
metrics: [{type: 'count', id: '1'}],
alias: '[[_@host]] $_metric and!',
alias: '{{@host}} {{metric}} and!',
bucketAggs: [
{type: 'terms', field: '@host', id: '2'},
{type: 'date_histogram', field: '@timestamp', id: '3'}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment