Merge remote-tracking branch 'spencer/zero-filled-ts' into zero
This commit is contained in:
+145
-53
@@ -108,11 +108,17 @@ angular.module('kibana.histogram', [])
|
|||||||
$scope.panel.interval = interval || '10m';
|
$scope.panel.interval = interval || '10m';
|
||||||
return $scope.panel.interval;
|
return $scope.panel.interval;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
|
* Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
|
||||||
* need to be consulted (like timestamped logstash indicies)
|
* need to be consulted (like timestamped logstash indicies)
|
||||||
* @param number segment The segment count, (0 based)
|
*
|
||||||
* @param number query_id The id of the query, generated on the first run and passed back when
|
* The results of this function are stored on the scope's data property. This property will be an
|
||||||
|
* array of objects with the properties info, time_series, and hits. These objects are used in the
|
||||||
|
* render_panel function to create the historgram.
|
||||||
|
*
|
||||||
|
* @param {number} segment The segment count, (0 based)
|
||||||
|
* @param {number} query_id The id of the query, generated on the first run and passed back when
|
||||||
* this call is made recursively for more segments
|
* this call is made recursively for more segments
|
||||||
*/
|
*/
|
||||||
$scope.get_data = function(segment, query_id) {
|
$scope.get_data = function(segment, query_id) {
|
||||||
@@ -197,12 +203,12 @@ angular.module('kibana.histogram', [])
|
|||||||
// we need to initialize the data variable on the first run,
|
// we need to initialize the data variable on the first run,
|
||||||
// and when we are working on the first segment of the data.
|
// and when we are working on the first segment of the data.
|
||||||
if(_.isUndefined($scope.data[i]) || segment === 0) {
|
if(_.isUndefined($scope.data[i]) || segment === 0) {
|
||||||
time_series = new timeSeries.ZeroFilled(
|
time_series = new timeSeries.ZeroFilled({
|
||||||
_interval,
|
interval: _interval,
|
||||||
// range may be false
|
start_date: _range && _range.from,
|
||||||
_range && _range.from,
|
end_date: _range && _range.to,
|
||||||
_range && _range.to
|
fill_style: 'minimal'
|
||||||
);
|
});
|
||||||
hits = 0;
|
hits = 0;
|
||||||
} else {
|
} else {
|
||||||
time_series = $scope.data[i].time_series;
|
time_series = $scope.data[i].time_series;
|
||||||
@@ -216,9 +222,8 @@ angular.module('kibana.histogram', [])
|
|||||||
$scope.hits += entry.count; // Entire dataset level hits counter
|
$scope.hits += entry.count; // Entire dataset level hits counter
|
||||||
});
|
});
|
||||||
$scope.data[i] = {
|
$scope.data[i] = {
|
||||||
time_series: time_series,
|
|
||||||
info: querySrv.list[id],
|
info: querySrv.list[id],
|
||||||
data: time_series.getFlotPairs(),
|
time_series: time_series,
|
||||||
hits: hits
|
hits: hits
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -310,7 +315,7 @@ angular.module('kibana.histogram', [])
|
|||||||
|
|
||||||
// Populate from the query service
|
// Populate from the query service
|
||||||
try {
|
try {
|
||||||
_.each(scope.data,function(series) {
|
_.each(scope.data, function(series) {
|
||||||
series.label = series.info.alias;
|
series.label = series.info.alias;
|
||||||
series.color = series.info.color;
|
series.color = series.info.color;
|
||||||
});
|
});
|
||||||
@@ -383,6 +388,19 @@ angular.module('kibana.histogram', [])
|
|||||||
options.selection = { mode: "x", color: '#666' };
|
options.selection = { mode: "x", color: '#666' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when rendering stacked bars, we need to ensure each point that has data is zero-filled
|
||||||
|
// so that the stacking happens in the proper order
|
||||||
|
var required_times = [];
|
||||||
|
if (scope.panel.bars && stack) {
|
||||||
|
required_times = Array.prototype.concat.apply([], _.map(scope.data, function (series) {
|
||||||
|
return series.time_series.getOrderedTimes();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < scope.data.length; i++) {
|
||||||
|
scope.data[i].data = scope.data[i].time_series.getFlotPairs(required_times);
|
||||||
|
}
|
||||||
|
|
||||||
scope.plot = $.plot(elem, scope.data, options);
|
scope.plot = $.plot(elem, scope.data, options);
|
||||||
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
@@ -448,36 +466,53 @@ angular.module('kibana.histogram', [])
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.service('timeSeries', function () {
|
.service('timeSeries', function () {
|
||||||
|
// map compatable parseInt
|
||||||
|
function base10Int(val) {
|
||||||
|
return parseInt(val, 10);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Certain graphs require 0 entries to be specified for them to render
|
* Certain graphs require 0 entries to be specified for them to render
|
||||||
* properly (like the line graph). So with this we will caluclate all of
|
* properly (like the line graph). So with this we will caluclate all of
|
||||||
* the expected time measurements, and fill the missing ones in with 0
|
* the expected time measurements, and fill the missing ones in with 0
|
||||||
* @param date start The start time for the result set
|
* @param {object} opts An object specifying some/all of the options
|
||||||
* @param date end The end time for the result set
|
*
|
||||||
* @param integer interval The length between measurements, in es interval
|
* OPTIONS:
|
||||||
* notation (1m, 30s, 1h, 15d)
|
* @opt {string} interval The interval notion describing the expected spacing between
|
||||||
|
* each data point.
|
||||||
|
* @opt {date} start_date (optional) The start point for the time series, setting this and the
|
||||||
|
* end_date will ensure that the series streches to resemble the entire
|
||||||
|
* expected result
|
||||||
|
* @opt {date} end_date (optional) The end point for the time series, see start_date
|
||||||
|
* @opt {string} fill_style Either "minimal", or "all" describing the strategy used to zero-fill
|
||||||
|
* the series.
|
||||||
*/
|
*/
|
||||||
var undef;
|
this.ZeroFilled = function (opts) {
|
||||||
function base10Int(val) {
|
this.opts = _.defaults(opts, {
|
||||||
return parseInt(val, 10);
|
interval: '10m',
|
||||||
}
|
start_date: null,
|
||||||
this.ZeroFilled = function (interval, start, end) {
|
end_date: null,
|
||||||
|
fill_style: 'minimal'
|
||||||
|
});
|
||||||
|
|
||||||
// the expected differenece between readings.
|
// the expected differenece between readings.
|
||||||
this.interval_ms = base10Int(kbn.interval_to_seconds(interval)) * 1000;
|
this.interval_ms = base10Int(kbn.interval_to_seconds(opts.interval)) * 1000;
|
||||||
|
|
||||||
// will keep all values here, keyed by their time
|
// will keep all values here, keyed by their time
|
||||||
this._data = {};
|
this._data = {};
|
||||||
|
|
||||||
if (start) {
|
if (opts.start_date) {
|
||||||
this.addValue(start, null);
|
this.addValue(opts.start_date, null);
|
||||||
}
|
}
|
||||||
if (end) {
|
if (opts.end_date) {
|
||||||
this.addValue(end, null);
|
this.addValue(opts.end_date, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a row
|
* Add a row
|
||||||
* @param int time The time for the value, in
|
* @param {int} time The time for the value, in
|
||||||
* @param any value The value at this time
|
* @param {any} value The value at this time
|
||||||
*/
|
*/
|
||||||
this.ZeroFilled.prototype.addValue = function (time, value) {
|
this.ZeroFilled.prototype.addValue = function (time, value) {
|
||||||
if (time instanceof Date) {
|
if (time instanceof Date) {
|
||||||
@@ -486,44 +521,101 @@ angular.module('kibana.histogram', [])
|
|||||||
time = base10Int(time);
|
time = base10Int(time);
|
||||||
}
|
}
|
||||||
if (!isNaN(time)) {
|
if (!isNaN(time)) {
|
||||||
this._data[time] = (value === undef ? 0 : value);
|
this._data[time] = (_.isUndefined(value) ? 0 : value);
|
||||||
}
|
}
|
||||||
|
this._cached_times = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of the times that have been explicitly set in the series
|
||||||
|
* @param {array} include (optional) list of timestamps to include in the response
|
||||||
|
* @return {array} An array of integer times.
|
||||||
|
*/
|
||||||
|
this.ZeroFilled.prototype.getOrderedTimes = function (include) {
|
||||||
|
var times = _.map(_.keys(this._data), base10Int).sort();
|
||||||
|
if (_.isArray(include)) {
|
||||||
|
times = times.concat(include);
|
||||||
|
}
|
||||||
|
return times;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return the rows in the format:
|
* return the rows in the format:
|
||||||
* [ [time, value], [time, value], ... ]
|
* [ [time, value], [time, value], ... ]
|
||||||
* @return array
|
*
|
||||||
|
* Heavy lifting is done by _get(Min|All)FlotPairs()
|
||||||
|
* @param {array} required_times An array of timestamps that must be in the resulting pairs
|
||||||
|
* @return {array}
|
||||||
*/
|
*/
|
||||||
this.ZeroFilled.prototype.getFlotPairs = function () {
|
this.ZeroFilled.prototype.getFlotPairs = function (required_times) {
|
||||||
// var startTime = performance.now();
|
var times = this.getOrderedTimes(required_times),
|
||||||
var times = _.map(_.keys(this._data), base10Int).sort(),
|
strategy,
|
||||||
result = [];
|
pairs;
|
||||||
_.each(times, function (time, i, times) {
|
|
||||||
var next, expected_next, prev, expected_prev;
|
|
||||||
|
|
||||||
// check for previous measurement
|
if(this.opts.fill_style === 'all') {
|
||||||
if (i > 0) {
|
strategy = this._getAllFlotPairs;
|
||||||
prev = times[i - 1];
|
} else {
|
||||||
expected_prev = time - this.interval_ms;
|
strategy = this._getMinFlotPairs;
|
||||||
if (prev < expected_prev) {
|
}
|
||||||
result.push([expected_prev, 0]);
|
|
||||||
}
|
return _.reduce(
|
||||||
|
times, // what
|
||||||
|
strategy, // how
|
||||||
|
[], // where
|
||||||
|
this // context
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ** called as a reduce stragegy in getFlotPairs() **
|
||||||
|
* Fill zero's on either side of the current time, unless there is already a measurement there or
|
||||||
|
* we are looking at an edge.
|
||||||
|
* @return {array} An array of points to plot with flot
|
||||||
|
*/
|
||||||
|
this.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
|
||||||
|
var next, expected_next, prev, expected_prev;
|
||||||
|
|
||||||
|
// check for previous measurement
|
||||||
|
if (i > 0) {
|
||||||
|
prev = times[i - 1];
|
||||||
|
expected_prev = time - this.interval_ms;
|
||||||
|
if (prev < expected_prev) {
|
||||||
|
result.push([expected_prev, 0]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add the current time
|
// add the current time
|
||||||
result.push([ time, this._data[time] ]);
|
result.push([ time, this._data[time] || 0 ]);
|
||||||
|
|
||||||
// check for next measurement
|
// check for next measurement
|
||||||
if (times.length > i) {
|
if (times.length > i) {
|
||||||
next = times[i + 1];
|
next = times[i + 1];
|
||||||
expected_next = time + this.interval_ms;
|
expected_next = time + this.interval_ms;
|
||||||
if (next > expected_next) {
|
if (next > expected_next) {
|
||||||
result.push([expected_next, 0]);
|
result.push([expected_next, 0]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}, this);
|
|
||||||
// console.log(Math.round((performance.now() - startTime)*100)/100, 'ms to get', result.length, 'pairs');
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ** called as a reduce stragegy in getFlotPairs() **
|
||||||
|
* Fill zero's to the right of each time, until the next measurement is reached or we are at the
|
||||||
|
* last measurement
|
||||||
|
* @return {array} An array of points to plot with flot
|
||||||
|
*/
|
||||||
|
this.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
|
||||||
|
var next, expected_next;
|
||||||
|
|
||||||
|
result.push([ times[i], this._data[times[i]] || 0 ]);
|
||||||
|
next = times[i + 1];
|
||||||
|
expected_next = times[i] + this.interval_ms;
|
||||||
|
for(; times.length > i && next > expected_next; expected_next+= this.interval_ms) {
|
||||||
|
result.push([expected_next, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user