Squashed commit of the following:

commit 49e6c0040cc82c1e2684933a8e167ef14854aff8
Author: Julian Lam <julian@designcreateplay.com>
Date:   Thu Feb 25 16:12:15 2016 -0500

    added recording and charts for topic and post counts globally and by cid

commit e02ff70757f778aa016fbc42ef10a5da2d07a9d9
Author: Julian Lam <julian@designcreateplay.com>
Date:   Thu Feb 25 15:35:49 2016 -0500

    added labels to charts

commit e75d83bf3886e5183bcf5fcd848d71c513761e01
Author: Julian Lam <julian@designcreateplay.com>
Date:   Thu Feb 25 13:30:47 2016 -0500

    added per category graphs to ACP management page

commit e3f543200950925cc9e8bf33cccb592f949a100e
Author: Julian Lam <julian@designcreateplay.com>
Date:   Thu Feb 25 12:36:11 2016 -0500

    updated analytics to move helper methods to analytics lib and sending per category analytics to ACP page

commit 01891d8f7c408925fcdad18dcaa941e5ebbeb9b2
Author: Julian Lam <julian@designcreateplay.com>
Date:   Wed Feb 24 16:48:55 2016 -0500

    saving per-category analytics, and updated the writeData method to use async for "clarity"
This commit is contained in:
Julian Lam
2016-02-25 16:12:50 -05:00
parent a320ec3efb
commit 088940d4c7
9 changed files with 310 additions and 111 deletions

View File

@@ -1,23 +1,37 @@
'use strict';
var cronJob = require('cron').CronJob;
var async = require('async');
var db = require('./database');
(function(Analytics) {
var counters = {};
var pageViews = 0;
var uniqueIPCount = 0;
var uniquevisitors = 0;
var isCategory = /^(?:\/api)?\/category\/(\d+)/;
new cronJob('*/10 * * * *', function() {
Analytics.writeData();
}, null, true);
Analytics.pageView = function(ip) {
Analytics.increment = function(keys) {
keys = Array.isArray(keys) ? keys : [keys];
keys.forEach(function(key) {
counters[key] = counters[key] || 0;
++counters[key];
});
};
Analytics.pageView = function(payload) {
++pageViews;
if (ip) {
db.sortedSetScore('ip:recent', ip, function(err, score) {
if (payload.ip) {
db.sortedSetScore('ip:recent', payload.ip, function(err, score) {
if (err) {
return;
}
@@ -28,40 +42,116 @@ var db = require('./database');
today.setHours(today.getHours(), 0, 0, 0);
if (!score || score < today.getTime()) {
++uniquevisitors;
db.sortedSetAdd('ip:recent', Date.now(), ip);
db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
}
});
}
if (payload.path) {
var categoryMatch = payload.path.match(isCategory),
cid = categoryMatch ? parseInt(categoryMatch[1], 10) : null;
if (cid) {
Analytics.increment(['pageviews:byCid:' + cid]);
}
}
};
Analytics.writeData = function() {
var today = new Date();
var month = new Date();
var dbQueue = [];
var today;
if (pageViews > 0 || uniquevisitors > 0) {
today = new Date();
today.setHours(today.getHours(), 0, 0, 0);
}
today.setHours(today.getHours(), 0, 0, 0);
month.setMonth(month.getMonth(), 1);
month.setHours(0, 0, 0, 0);
if (pageViews > 0) {
db.sortedSetIncrBy('analytics:pageviews', pageViews, today.getTime());
var month = new Date();
month.setMonth(month.getMonth(), 1);
month.setHours(0, 0, 0, 0);
db.sortedSetIncrBy('analytics:pageviews:month', pageViews, month.getTime());
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews', pageViews, today.getTime()));
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews:month', pageViews, month.getTime()));
pageViews = 0;
}
if (uniquevisitors > 0) {
db.sortedSetIncrBy('analytics:uniquevisitors', uniquevisitors, today.getTime());
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:uniquevisitors', uniquevisitors, today.getTime()));
uniquevisitors = 0;
}
if (uniqueIPCount > 0) {
db.incrObjectFieldBy('global', 'uniqueIPCount', uniqueIPCount);
dbQueue.push(async.apply(db.incrObjectFieldBy, 'global', 'uniqueIPCount', uniqueIPCount));
uniqueIPCount = 0;
}
if (Object.keys(counters).length > 0) {
for(var key in counters) {
console.log('flushing', key, 'with a value of', counters[key]);
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:' + key, counters[key], today.getTime()));
delete counters[key];
}
}
async.parallel(dbQueue, function(err) {
if (err) {
winston.error('[analytics] Encountered error while writing analytics to data store: ' + err.message);
}
});
};
Analytics.getHourlyStatsForSet = function(set, hour, numHours, callback) {
var terms = {},
hoursArr = [];
hour = new Date(hour);
hour.setHours(hour.getHours(), 0, 0, 0);
for (var i = 0, ii = numHours; i < ii; i++) {
hoursArr.push(hour.getTime());
hour.setHours(hour.getHours() - 1, 0, 0, 0);
}
db.sortedSetScores(set, hoursArr, function(err, counts) {
if (err) {
return callback(err);
}
hoursArr.forEach(function(term, index) {
terms[term] = parseInt(counts[index], 10) || 0;
});
var termsArr = [];
hoursArr.reverse();
hoursArr.forEach(function(hour) {
termsArr.push(terms[hour]);
});
callback(null, termsArr);
});
};
Analytics.getDailyStatsForSet = function(set, day, numDays, callback) {
var daysArr = [];
day = new Date(day);
day.setDate(day.getDate()+1); // set the date to tomorrow, because getHourlyStatsForSet steps *backwards* 24 hours to sum up the values
day.setHours(0, 0, 0, 0);
async.whilst(function() {
return numDays--;
}, function(next) {
Analytics.getHourlyStatsForSet(set, day.getTime()-(1000*60*60*24*numDays), 24, function(err, day) {
if (err) {
return next(err);
}
daysArr.push(day.reduce(function(cur, next) {
return cur+next;
}));
next();
});
}, function(err) {
callback(err, daysArr);
});
};
Analytics.getUnwrittenPageviews = function() {
@@ -86,4 +176,13 @@ var db = require('./database');
});
};
Analytics.getCategoryAnalytics = function(cid, callback) {
async.parallel({
'pageviews:hourly': async.apply(Analytics.getHourlyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 24),
'pageviews:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 30),
'topics:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:topics:byCid:' + cid, Date.now(), 7),
'posts:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:posts:byCid:' + cid, Date.now(), 7),
}, callback);
};
}(exports));