Skip to content

Commit

Permalink
bf: ZENKO-1144 add sorted set support StatsModel
Browse files Browse the repository at this point in the history
Changes in this commit:
- Helper method _normalizeTimestampByHour normalizes date to
  nearest hour
- Helper method _setDateToPreviousHour sets date back 1 hour
- method getSortedSetHours returns list of 24 normalized
  hourly timestamps
- method getSortedSetCurrentHour returns normalized
  hourly timestamp based on epoch passed
- method addToSortedSet adds to a sorted set and applies
  expiry if adding to new sorted set
  • Loading branch information
philipyoo committed Sep 14, 2018
1 parent a3973ac commit 0b91835
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
10 changes: 10 additions & 0 deletions lib/metrics/RedisClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ class RedisClient {
return this._client.zrangebyscore(key, min, max, cb);
}

/**
* get TTL or expiration in seconds
* @param {string} key - name of key
* @param {function} cb - callback
* @return {undefined}
*/
ttl(key, cb) {
return this._client.ttl(key, cb);
}

clear(cb) {
return this._client.flushdb(cb);
}
Expand Down
80 changes: 80 additions & 0 deletions lib/metrics/StatsModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,86 @@ class StatsModel extends StatsClient {
return cb(null, statsRes);
});
}

/**
* normalize date timestamp to the nearest hour
* @param {Date} d - Date instance
* @return {number} timestamp - normalized to the nearest hour
*/
_normalizeTimestampByHour(d) {
return d.setMinutes(0, 0, 0);
}

/**
* get previous hour to date given
* @param {Date} d - Date instance
* @return {number} timestamp - one hour prior to date passed
*/
_getDatePreviousHour(d) {
return d.setHours(d.getHours() - 1);
}

/**
* get list of sorted set key timestamps
* @param {number} epoch - epoch time
* @return {array} array of sorted set key timestamps
*/
getSortedSetHours(epoch) {
const timestamps = [];
let date = this._normalizeTimestampByHour(new Date(epoch));
while (timestamps.length < 24) {
timestamps.push(date);
date = this._getDatePreviousHour(new Date(date));
}
return timestamps;
}

/**
* get the normalized hour timestamp for given epoch time
* @param {number} epoch - epoch time
* @return {string} normalized hour timestamp for given time
*/
getSortedSetCurrentHour(epoch) {
return this._normalizeTimestampByHour(new Date(epoch));
}

/**
* helper method to add element to a sorted set, applying TTL if new set
* @param {string} key - name of key
* @param {integer} score - score used to order set
* @param {string} value - value to store
* @param {callback} cb - callback
* @return {undefined}
*/
addToSortedSet(key, score, value, cb) {
this._redis.exists(key, (err, resCode) => {
if (err) {
return cb(err);
}
if (resCode === 0) {
// milliseconds in a day
const msInADay = 24 * 60 * 60 * 1000;
const nearestHour = this._normalizeTimestampByHour(new Date());
const ttl = msInADay - (Date.now() - nearestHour);
const cmds = [
['zadd', key, score, value],
['expire', key, ttl],
];
return this._redis.batch(cmds, (err, res) => {
if (err) {
return cb(err);
}
const cmdErr = res.find(r => r[0] !== null);
if (cmdErr) {
return cb(cmdErr);
}
const successResponse = res[0][1];
return cb(null, successResponse);
});
}
return this._redis.zadd(key, score, value, cb);
});
}
}

module.exports = StatsModel;
62 changes: 62 additions & 0 deletions tests/functional/metrics/StatsModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,66 @@ describe('StatsModel class', () => {
},
], done);
});

it('should normalize to the nearest hour using _normalizeTimestampByHour',
() => {
const date = new Date('2018-09-13T23:30:59.195Z');
const newDate = new Date(statsModel._normalizeTimestampByHour(date));

assert.strictEqual(date.getHours(), newDate.getHours());
assert.strictEqual(newDate.getMinutes(), 0);
assert.strictEqual(newDate.getSeconds(), 0);
assert.strictEqual(newDate.getMilliseconds(), 0);
});

it('should get previous hour using _getDatePreviousHour', () => {
const date = new Date('2018-09-13T23:30:59.195Z');
const newDate = statsModel._getDatePreviousHour(new Date(date));

const millisecondsInOneHour = 3600000;
assert.strictEqual(date - newDate, millisecondsInOneHour);
});

it('should get an array of hourly timestamps using getSortedSetHours',
() => {
const epoch = 1536882476501;
const millisecondsInOneHour = 3600000;

const expected = [];
let dateInMilliseconds = statsModel._normalizeTimestampByHour(
new Date(epoch));

for (let i = 0; i < 24; i++) {
expected.push(dateInMilliseconds);
dateInMilliseconds -= millisecondsInOneHour;
}
const res = statsModel.getSortedSetHours(epoch);

assert.deepStrictEqual(res, expected);
});

it('should apply TTL on a new sorted set using addToSortedSet', done => {
const key = 'a-test-key';
const score = 100;
const value = 'a-value';

statsModel.addToSortedSet(key, score, value, (err, res) => {
assert.ifError(err);
// check both a "zadd" and "expire" occurred
assert.equal(res.length, 2);

const responses = res.map(i => i[1]);
responses.forEach(r => {
// assert an action occurred for both
assert.equal(r, 1);
});

// check TTL applied
redisClient.ttl(key, (err, res) => {
assert.ifError(err);
assert(res > 0);
done();
});
});
});
});

0 comments on commit 0b91835

Please sign in to comment.