Last active
December 12, 2015 09:39
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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