//time series constructor. Requires a redis client and a namespace
function TimeSeries(client, namespace){
this.namespace = namespace;
this.client = client;
//granularity names and their equivalents in seconds
this.units = {
second:1,
minute: 60,
hour: 60 * 60,
day: 24 * 60 * 60
};
//each granularity has a name, TTL(time to live) and a duration.
//the null ttl present on 1dat meands that this ttl never expires
this.granularities = {
'1sec': {name: '1sec', ttl: this.units.hour * 2, duration: this.units.second},
'1min': {name: '1min', ttl: this.units.day * 7, duration: this.units.minute},
'1hour': {name: '1hour', ttl: this.units.day * 60, duration: this.units.hour},
'1day': {name: '1day', ttl: null, duration: this.units.day}
};
}
//insert a particular price at a given timestamp
TimeSeries.prototype.insert = function(timestampInSeconds, price){
//iterate over all franularities
for (var granularityName in this.granularities){
var granularity = this.granularities[granularityName];
//get a key name in the format "napespace:granularity:timestamp"
//for ex.: "google:1sec:12"
var key = this._getKeyName(granularity, timestampInSeconds);
//execute the SET command
this.client.set(key, price);
//the EXPIRE command
//pass the key and the ttl
//this command deletes a redis key automatically after a given number
//of seconds
if(granularity.ttl !== null){
this.client.expire(key, granularity.ttl);
}
}
};
//returns a key based on granularitu and timestamp
TimeSeries.prototype._getKeyName = function(granularity, timestampInSeconds){
var roundedTimestamp = this._getRoundedTimestamp(timestampInSeconds, granularity.duration);
return [this.namespace, granularity.name, roundedTimestamp].join(':');
};
//returns a normalized timestamp by granularity duration.
//For example, all inserts that happen in the first minute of an hour are stored
//in a key like "namespace:1min:0". All inserts from the second minute are stored
//in the "namespace:1min:60", and so on
TimeSeries.prototype._getRoundedTimestamp = function(timestampInSeconds, precision){
return Math.floor(timestampInSeconds / precision) * precision;
};
//executes a callback by passing an array of data points
TimeSeries.prototype.fetch = function(granularityName, beginTimestamp, endTimestamp, onComplete){
var granularity = this.granularities[granularityName];
var begin = this._getRoundedTimestamp(beginTimestamp, granularity.duration);
var end = this._getRoundedTimestamp(endTimestamp, granularity.duration);
var keys = [];
//iterate over all the timestamps in the specified range and save their values
//in the "keys" variable
for(var timestamp = begin; timestamp <= end; timestamp += granularity.duration){
var key = this._getKeyName(granularity, timestamp);
keys.push(key);
}
//the MGET command
this.client.mget(keys, function(err, replies){
var results = [];
//iterate over all replies
for(var i = 0; i < replies.length; i++){
var timestamp = beginTimestamp + i * granularity.duration;
//convert value to an integer
var value = parseInt(replies[i], 10) || 0;
//save timestamp and value in the "results" variable
results.push({timestamp: timestamp, value:value});
}
//execute callback passing the variables "granularityName" and "results"
onComplete(granularityName, results);
});
};
//make a function available as a module in Node.js
exports.TimeSeries = TimeSeries;