Skip to content

Instantly share code, notes, and snippets.

@mchung
Last active December 12, 2015 09:39
Show Gist options
  • Save mchung/4753188 to your computer and use it in GitHub Desktop.
Save mchung/4753188 to your computer and use it in GitHub Desktop.
A funny little hack I put together over the weekend. Read this for more information: http://ping.marcchung.com
/**
* Each record captured by the form is represented as a PingAlert.
*/
function PingAlert(rowId, record) {
this.rowNum = rowId + 1;
// See Spreadsheet for information on index.
this.url = record[1];
this.email = record[2];
// Extra columns
this.active = record[3]; // Live
this.accumDowntime = parseInt(record[4], 10); // Accumulated downtime
this.nextOutgoingAlert = parseInt(record[5], 10); // Next downtime alert dispatched
this.alertsDispatched = parseInt(record[6], 10); // Running count of alerts dispatched
}
/**
* Does the actual UrlFetch/Ping. Will depend on cached copy for subsequent calls.
*/
PingAlert.prototype.isAvailable = function() {
if (this.response === undefined) {
var options = {
"muteHttpExceptions" : true
};
this.response = UrlFetchApp.fetch(this.url, options);
}
return this.response.getResponseCode() == 200;
}
PingAlert.prototype.firstAlert = function() {
return (this.accumDowntime === 0 && this.nextOutgoingAlert === 0 && this.alertsDispatched === 0);
}
/**
* Main engine. Update invariants, send out emails.
*/
PingAlert.prototype.dispatch = function() {
if (this.isAvailable()) {
// reset to initial state
if (!this.firstAlert()) {
this.sendUptimeAlert();
this.accumDowntime = 0;
this.nextOutgoingAlert = 0;
this.alertsDispatched = 0;
}
} else {
if (this.firstAlert()) {
this.sendDowntimeAlert();
this.accumDowntime = 0;
this.nextOutgoingAlert = 1;
} else {
this.accumDowntime++;
if (this.accumDowntime === this.nextOutgoingAlert) {
this.sendDowntimeAlert();
this.updateNextOutgoingAlert();
}
}
}
}
/**
* Save state till next time main() is triggered. Is there some 2-way binding API?
*/
PingAlert.prototype.save = function(sheet) {
var updated = [[this.accumDowntime, this.nextOutgoingAlert, this.alertsDispatched]];
sheet.getRange(this.rowNum, 5, 1, 3).setValues(updated);
}
/**
* Fibonacci-like back off sequence to rate limit outgoing downtime emails.
*/
PingAlert.prototype.updateNextOutgoingAlert = function() {
var phi = Math.pow(5, 0.5) * 0.5 + 0.5;
var nextfib = Math.pow(phi, this.alertsDispatched+1) / Math.pow(5, 0.5);
this.nextOutgoingAlert = Math.round(nextfib);
}
/**
* Generates and delivers downtime email.
*/
PingAlert.prototype.sendDowntimeAlert = function() {
Logger.log(this.url + " is down. Alerting " + this.email);
var subject = "[Ping-a-lert] Uh-oh, " + this.url + " is unavailable";
var output = ContentService.createTextOutput();
output.append("Ahoy, " + this.email + "!");
output.append("\n\n");
output.append(this.url);
if (this.firstAlert()) {
output.append(" is down :(");
} else {
output.append(" is still down. ");
output.append("\n\n");
output.append("Total downtime: ");
output.append(this.accumDowntime)
if (this.accumDowntime == 1) {
output.append(" minute.");
} else {
output.append(" minutes.");
}
}
output.append("\n\n");
output.append("Response headers:\n");
var headers = this.response.getAllHeaders();
for (var key in headers) {
if(headers.hasOwnProperty(key)) {
output.append(key + ": " + headers[key]);
output.append("\n");
}
}
output.append("\n\n");
output.append("Response body:\n");
output.append(this.response.getContentText());
output.append("\n\n");
output.append("Thanks,\n\n--\nPing-a-lert by Marc Chung -- http://github.com/mchung");
MailApp.sendEmail(this.email, subject, output.getContent());
this.alertsDispatched++;
}
/**
* ... and we're back
*/
PingAlert.prototype.sendUptimeAlert = function() {
Logger.log(this.url + " is back up. Alerting " + this.email);
var subject = "[Ping-a-lert] Hooray, " + this.url + " is available";
var output = ContentService.createTextOutput();
output.append("Ahoy, " + this.email + "!");
output.append("\n\n");
output.append(this.url + " is back up again :)");
output.append("\n\n");
output.append("Total downtime: ");
output.append(this.accumDowntime)
if (this.accumDowntime == 1) {
output.append(" minute.");
} else {
output.append(" minutes.");
}
output.append("\n\n");
output.append("Response headers:\n");
var headers = this.response.getAllHeaders();
for (var key in headers) {
if(headers.hasOwnProperty(key)) {
output.append(key + ": " + headers[key]);
output.append("\n");
}
}
output.append("\n\n");
output.append("Thanks,\n\n--\nPing-a-lert by Marc Chung -- http://github.com/mchung");
MailApp.sendEmail(this.email, subject, output.getContent());
this.alertsDispatched++;
}
/**
* Ping-a-lert.
*/
function main() {
Logger.log("Emails remaining: " + MailApp.getRemainingDailyQuota());
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var sites = new Array();
for (var i = 1; i <= numRows-1; i++) {
var site = new PingAlert(i, values[i]);
if (site.active) {
site.dispatch();
site.save(sheet);
}
}
}
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Ping all active sites",
functionName : "main"
}];
sheet.addMenu("Script Center Menu", entries);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment