springer-uptime
Version:
Remote monitoring for HTTP applications
614 lines (580 loc) • 20.4 kB
JavaScript
var async = require('async');
var moment = require('moment');
var Ping = require('../models/ping');
var IntervalBuilder = require('./intervalBuilder');
var QosAggregator = function() {
};
QosAggregator.prototype.getCallback = function(callback){
if ('undefined' == typeof callback) {
// Mogoose Model.update() implementation requires a callback
return function(err) { if (err) console.dir(err); };
}
return callback;
};
QosAggregator.prototype.updateHourlyQos = function(now, callback) {
var start = moment(now).clone().startOf('hour').toDate();
var end = moment(now).clone().endOf('hour').toDate();
async.parallel([
async.apply(this.updateHourlyCheckQos.bind(this), start, end),
async.apply(this.updateHourlyTagQos.bind(this), start, end)
], callback);
};
/**
* Aggregate Qos data of Pings from all checks around a timestamp into CheckHourlyStat documents
*
* The method finds the boundaries of the hour
* by resetting/completing the hour of the timestamp.
*
* @param {Int|Date} timestamp or Date object of a moment inside the chosen hour
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateHourlyCheckQos = function(start, end, callback) {
var CheckHourlyStat = require('../models/checkHourlyStat');
this.aggregatePingsIntoStatsGroupedByCheck(start, end, function(err, stats) {
async.forEach(stats, function(stat, next) {
CheckHourlyStat.update(
{ check: stat.check, timestamp: stat.timestamp },
{ $set: stat },
{ upsert: true },
next
);
}, callback);
});
};
QosAggregator.prototype.updateHourlyTagQos = function(start, end, callback) {
var TagHourlyStat = require('../models/tagHourlyStat');
this.aggregatePingsIntoStatsGroupedByTag(start, end, function(err, stats) {
if (err) return callback(err);
async.forEach(stats, function(stat, next) {
TagHourlyStat.update(
{ name: stat.name, timestamp: start },
{ $set: stat },
{ upsert: true },
next
);
}, callback);
});
};
/**
* Aggregate Qos data of Pings from the last 24h into the qos property of checks and tags
*
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateLast24HoursQos = function(callback) {
var start = new Date(Date.now() - (24 * 60 * 60 * 1000));
var end = new Date();
async.parallel([
async.apply(this.updateLast24HoursCheckQos.bind(this), start, end),
async.apply(this.updateLast24HoursTagQos.bind(this), start, end),
], callback);
};
QosAggregator.prototype.updateLast24HoursCheckQos = function(start, end, callback) {
var Check = require('../models/check');
this.aggregatePingsIntoStatsGroupedByCheck(start, end, function(err, stats) {
if (err) return callback(err);
async.forEach(stats, function(stat, next) {
Check.findById(stat.check, function (err2, check) {
if (err2 || !check) return next(err2);
check.qos = stat;
check.markModified('qos');
check.save(next);
});
}, callback);
});
};
QosAggregator.prototype.updateLast24HoursTagQos = function(start, end, callback) {
var Tag = require('../models/tag');
this.aggregatePingsIntoStatsGroupedByTag(start, end, function(err, stats) {
if (err) return callback(err);
async.forEach(stats, function(stat, next) {
Tag.update(
{ name: stat.name },
{ $set: { lastUpdated: end, count: stat.count, availability: stat.availability, responsiveness: stat.responsiveness, responseTime: stat.responseTime, downtime: stat.downtime, outages: stat.outages } },
{ upsert: true },
next
);
}, callback);
});
};
/**
* Aggregate Qos data of Pings from all checks between two boundaries
*
* @param {Date} Date object of a the beginning of the search period
* @param {Date} Date object of a the end of the search period
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.aggregatePingsIntoStatsGroupedByCheck = function(start, end, callback) {
async.waterfall([
function (next) { next(null, start, end) }, // pass the parameters to the next function
this.aggregatePingsByCheck,
this.getCheckStats.bind(this)
], callback);
};
QosAggregator.prototype.aggregatePingsByCheck = function(start, end, callback) {
Ping.aggregate(
{ $match: {
timestamp: { $gte: start, $lte: end }
} },
{ $project: {
check: 1,
responsive: { $cond: [ { $and: ["$isResponsive"] }, 1, 0] },
time: 1,
tags: 1,
} },
{ $group: {
_id: "$check",
count: { $sum: 1 },
responsiveness: { $avg: "$responsive" },
responseTime: { $avg: "$time" },
start: { $first: start.valueOf() }, // dunno any other way to set a constant
end: { $first: end.valueOf() }
} },
callback
);
};
QosAggregator.prototype.getCheckStats = function(documents, callback) {
var self = this;
var aggregatedStats = [];
async.forEach(documents, function(document, next) {
self.createCheckStatFromPingAggregationDocument(document, function(err, stat) {
if (err) return next(err);
aggregatedStats.push(stat);
next();
});
}, function(err) {
callback(err, aggregatedStats);
});
};
QosAggregator.prototype.createCheckStatFromPingAggregationDocument = function(document, callback) {
var stat = {
check: document._id,
timestamp: document.start,
count: document.count,
responsiveness: document.responsiveness,
responseTime: document.responseTime
};
var intervalBuilder = new IntervalBuilder();
intervalBuilder.addTarget(document._id);
intervalBuilder.build(document.start, document.end, function(err, outages, downtime, testedTime) {
if (err) return callback(err);
if (outages.length) {
stat.outages = outages;
}
stat.downtime = downtime;
stat.availability = testedTime != 0 ? (testedTime - downtime) / testedTime : 0;
callback(null, stat);
});
};
/**
* Aggregate Qos data of Pings from all tags between two boundaries
*
* @param {Date} Date object of a the beginning of the search period
* @param {Date} Date object of a the end of the search period
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.aggregatePingsIntoStatsGroupedByTag = function(start, end, callback) {
async.waterfall([
function (next) { next(null, start, end) }, // pass the parameters to the next function
this.aggregatePingsByTag,
this.getTagStats.bind(this)
], callback);
};
QosAggregator.prototype.aggregatePingsByTag = function(start, end, callback) {
Ping.aggregate(
{ $match: {
timestamp: { $gte: start, $lte: end }
} },
{ $project: {
check: 1,
responsive: { $cond: [ { $and: ["$isResponsive"] }, 1, 0] },
time: 1,
tags: 1,
} },
{ $unwind: "$tags" },
{ $group: {
_id: "$tags",
count: { $sum: 1 },
responsiveness: { $avg: "$responsive" },
responseTime: { $avg: "$time" },
start: { $first: start.valueOf() }, // dunno any other way to set a constant
end: { $first: end.valueOf() }
} },
callback
);
};
QosAggregator.prototype.getTagStats = function(documents, callback) {
var self = this;
var aggregatedStats = [];
async.forEach(documents, function(document, next) {
self.createTagStatFromPingAggregationDocument(document, function(err, stat) {
if (err) return next(err);
aggregatedStats.push(stat);
next();
});
}, function(err) {
callback(err, aggregatedStats);
});
};
QosAggregator.prototype.createTagStatFromPingAggregationDocument = function(document, callback) {
var Check = require('../models/check');
var duration = document.end - document.start;
var stat = {
name: document._id,
timestamp: document.start,
count: document.count,
responsiveness: document.responsiveness,
responseTime: document.responseTime
};
var intervalBuilder = new IntervalBuilder();
Check.findForTag(document._id, function(err, checks) {
if (err) return callback(err);
checks.forEach(function(check) {
intervalBuilder.addTarget(check);
});
intervalBuilder.build(document.start, document.end, function(err2, outages, downtime, testedTime) {
if (err2) return callback(err2);
stat.outages = outages;
stat.downtime = downtime;
stat.availability = testedTime != 0 ? (testedTime - downtime) / testedTime : 0;
callback(null, stat);
});
});
};
/**
* Aggregate hourly Qos data from all checks and tags around a timestamp
* into daily stat documents.
*
* The method finds the boundaries of the day
* by resetting/completing the day of the timestamp.
*
* @param {Int|Date} timestamp or Date object of a moment inside the chosen day
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateDailyQos = function(now, callback) {
var start = moment(now).clone().startOf('day').toDate();
var end = moment(now).clone().endOf('day').toDate();
async.parallel([
async.apply(this.updateDailyCheckQos.bind(this), start, end),
async.apply(this.updateDailyTagQos.bind(this), start, end)
], callback);
};
QosAggregator.prototype.updateDailyCheckQos = function(start, end, callback) {
async.waterfall([
function (next) { next(null, start, end) }, // pass the parameters to the next function
this.aggregateCheckHourlyStatsByCheck,
this.createCheckDailyStats.bind(this)
], callback);
};
QosAggregator.prototype.aggregateCheckHourlyStatsByCheck = function(start, end, callback) {
var CheckHourlyStat = require('../models/checkHourlyStat');
CheckHourlyStat.aggregate(
{ $match: {
timestamp: { $gte: start, $lte: end }
} },
{ $sort: {
timestamp: 1
} },
{ $group: {
_id: "$check",
count: { $sum: "$count" },
responsiveness: { $avg: "$responsiveness" },
responseTime: { $avg: "$responseTime" },
start: { $first: start.valueOf() },
end: { $first: end.valueOf() }
} },
callback
);
};
QosAggregator.prototype.createCheckDailyStats = function(documents, callback) {
var CheckDailyStat = require('../models/checkDailyStat');
var day = 24 * 60 * 60 * 1000;
async.forEach(documents, function(document, next) {
var stat = {
count: document.count,
responsiveness: document.responsiveness,
responseTime: document.responseTime
};
var intervalBuilder = new IntervalBuilder();
intervalBuilder.addTarget(document._id);
intervalBuilder.build(document.start, document.end, function(err, outages, downtime, testedTime) {
if (err) return next(err);
stat.outages = outages;
stat.downtime = downtime;
stat.availability = testedTime != 0 ? (testedTime - downtime) / testedTime : 0;
CheckDailyStat.update(
{ check: document._id, timestamp: document.start },
{ $set: stat },
{ upsert: true },
next
);
});
}, callback);
};
QosAggregator.prototype.updateDailyTagQos = function(start, end, callback) {
async.waterfall([
function (next) { next(null, start, end) }, // pass the parameters to the next function
this.aggregateTagHourlyStatsByTag,
this.createTagDailyStats.bind(this)
], callback);
};
QosAggregator.prototype.aggregateTagHourlyStatsByTag = function(start, end, callback) {
var TagHourlyStat = require('../models/tagHourlyStat');
TagHourlyStat.aggregate(
{ $match: {
timestamp: { $gte: start, $lte: end }
} },
{ $sort: {
timestamp: 1
} },
{ $group: {
_id: "$name",
count: { $sum: "$count" },
responsiveness: { $avg: "$responsiveness" },
responseTime: { $avg: "$responseTime" },
start: { $first: start.valueOf() },
end: { $first: end.valueOf() }
} },
callback
);
};
QosAggregator.prototype.createTagDailyStats = function(documents, callback) {
var Check = require('../models/check');
var TagDailyStat = require('../models/tagDailyStat');
var day = 24 * 60 * 60 * 1000;
async.forEach(documents, function(document, next) {
var stat = {
count: document.count,
responsiveness: document.responsiveness,
responseTime: document.responseTime,
};
var intervalBuilder = new IntervalBuilder();
Check.findForTag(document._id, function(err, checks) {
if (err) return next(err);
checks.forEach(function(check) {
intervalBuilder.addTarget(check);
});
intervalBuilder.build(document.start, document.end, function(err2, outages, downtime, testedTime) {
if (err2) return next(err2);
stat.outages = outages;
stat.downtime = downtime;
stat.availability = testedTime != 0 ? (testedTime - downtime) / testedTime : 0;
TagDailyStat.update(
{ name: document._id, timestamp: document.start },
{ $set: stat },
{ upsert: true },
next
);
});
});
}, callback);
};
/**
* Aggregate daily Qos data from all checks and tags around a timestamp
* into monthly stat documents.
*
* The method finds the boundaries of the month
* by resetting/completing the month of the timestamp.
*
* @param {Int|Date} timestamp or Date object of a moment inside the chosen month
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateMonthlyQos = function(now, callback) {
var start = moment(now).clone().startOf('month').toDate();
var end = moment(now).clone().endOf('month').toDate();
var CheckDailyStat = require('../models/checkDailyStat');
var CheckMonthlyStat = require('../models/checkMonthlyStat');
var TagDailyStat = require('../models/tagDailyStat');
var TagMonthlyStat = require('../models/tagMonthlyStat');
async.series([
async.apply(this.updateMonthlyQosStats.bind(this), start, end, CheckDailyStat, CheckMonthlyStat, "$check"),
async.apply(this.updateMonthlyQosStats.bind(this), start, end, TagDailyStat, TagMonthlyStat, "$name"),
], callback);
};
QosAggregator.prototype.updateMonthlyQosStats = function(start, end, dailyStatModel, monthlyStatModel, groupIdentifier, callback) {
callback = this.getCallback(callback);
var key = groupIdentifier.substr(1);
var month = end - start;
var pipeline = [
{ $match: {
timestamp: { $gte: start, $lte: end }
} },
{ $sort: {
timestamp: 1
} },
{ $group: {
_id: groupIdentifier,
count: { $sum: "$count" },
responsiveness: { $avg: "$responsiveness" },
responseTime: { $avg: "$responseTime" },
downtime: { $sum: "$downtime" },
availabilities: { $push: "$availability" },
timestamps: { $push: "$timestamp" }
} }
];
dailyStatModel.aggregate(pipeline, function(err, stats) {
if (err) return callback(err);
var query;
var day = 24 * 60 * 60 * 1000;
async.forEach(stats, function(stat, cb) {
var outages = [];
stat.availabilities.forEach(function(availability, index) {
var date = new Date(stat.timestamps[index]).valueOf();
outages.push([date, date + day, availability]);
});
var toStatDocument = {
end: end,
count: stat.count,
availability: (month - stat.downtime) / month,
responsiveness: stat.responsiveness,
responseTime: stat.responseTime,
downtime: stat.downtime,
outages: outages
};
query = { timestamp: start };
query[key] = stat._id;
monthlyStatModel.update(
query,
{ $set: toStatDocument },
{ upsert: true },
cb
);
}, callback);
});
};
/**
* Aggregate daily Qos data from all checks and tags around a timestamp
* into yearly stat documents.
*
* The method finds the boundaries of the month
* by resetting/completing the month of the timestamp.
*
* @param {Int|Date} timestamp or Date object of a moment inside the chosen month
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateYearlyQos = function(now, callback) {
var start = moment(now).clone().startOf('year').toDate();
var end = moment(now).clone().endOf('year').toDate();
var CheckMonthlyStat = require('../models/checkMonthlyStat');
var CheckYearlyStat = require('../models/checkYearlyStat');
var TagMonthlyStat = require('../models/tagMonthlyStat');
var TagYearlyStat = require('../models/tagYearlyStat');
async.series([
async.apply(this.updateYearlyQosStats.bind(this), start, end, CheckMonthlyStat, CheckYearlyStat, "$check"),
async.apply(this.updateYearlyQosStats.bind(this), start, end, TagMonthlyStat, TagYearlyStat, "$name"),
], callback);
};
QosAggregator.prototype.updateYearlyQosStats = function(start, end, monthlyStatModel, yearlyStatModel, groupIdentifier, callback) {
callback = this.getCallback(callback);
var key = groupIdentifier.substr(1);
var month = end - start;
var pipeline = [
{ $match: {
timestamp: { $gte: start, $lte: end }
} },
{ $sort: {
timestamp: 1
} },
{ $group: {
_id: groupIdentifier,
count: { $sum: "$count" },
responsiveness: { $avg: "$responsiveness" },
responseTime: { $avg: "$responseTime" },
downtime: { $sum: "$downtime" },
availabilities: { $push: "$availability" },
timestamps: { $push: "$timestamp" },
ends: { $push: "$end" }
} }
];
monthlyStatModel.aggregate(pipeline, function(err, stats) {
if (err) return callback(err);
var query;
async.forEach(stats, function(stat, cb) {
var outages = [];
stat.availabilities.forEach(function(availability, index) {
var outageStart = new Date(stat.timestamps[index]).valueOf();
var outageEnd = new Date(stat.ends[index]).valueOf();
outages.push([outageStart, outageEnd, availability]);
});
var toStatDocument = {
end: end,
count: stat.count,
availability: (month - stat.downtime) / month,
responsiveness: stat.responsiveness,
responseTime: stat.responseTime,
downtime: stat.downtime,
outages: outages
};
query = { timestamp: start };
query[key] = stat._id;
yearlyStatModel.update(
query,
{ $set: toStatDocument },
{ upsert: true },
cb
);
}, callback);
});
};
/**
* Aggregate Qos data of Pings from all checks from the last hour
* into CheckHourlyStat and TagHourlyStat documents.
*
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateLastHourQos = function(callback) {
var now = new Date(Date.now() - 1000 * 60 * 6); // 6 minutes in the past, to accommodate script running every 5 minutes
this.updateHourlyQos(now, callback);
};
/**
* Aggregate Qos data of CheckHourlyStat from all checks from the last day
* into CheckDailyStat documents.
*
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateLastDayQos = function(callback) {
var now = new Date(Date.now() - 1000 * 60 * 66); // 1 hour and 6 minutes in the past, to accommodate script running every hour
this.updateDailyQos(now, callback);
};
/**
* Aggregate Qos data of CheckDailyStat from all checks from the last month
* into CheckMonthlyStat documents.
*
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateLastMonthQos = function(callback) {
var now = new Date(Date.now() - 1000 * 60 * 66); // 1 hour and 6 minutes in the past, to accommodate script running every hour
this.updateMonthlyQos(now, callback);
};
/**
* Aggregate Qos data of CheckMonthlyStat from all checks from the last month
* into CheckYearlyStat documents.
*
* @param {Function} callback(err) to be called upon completion
*
* @api public
*/
QosAggregator.prototype.updateLastYearQos = function(callback) {
var now = new Date(Date.now() - 1000 * 60 * 66); // 1 hour and 6 minutes in the past, to accommodate script running every hour
this.updateYearlyQos(now, callback);
};
module.exports = new QosAggregator();