Created
April 21, 2017 13:21
-
-
Save nherment/8f7b6a5533616e53f595a7995df62bf4 to your computer and use it in GitHub Desktop.
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
const readLine = require("readline"); | |
const rl = readLine.createInterface({output: process.stdout, input:process.stdin}); | |
const dgram = require('dgram'); | |
const server = dgram.createSocket('udp4'); | |
// This regex extracts everything after the first / in the requested URL that isn't | |
// part of a HTTP(S):// prefix. | |
const siteRegex = /"GET.*?[^\/]\/([^\/]\S+)/; | |
// Amount of hits per page and bottom-level section, respectively | |
const hits = new Map(); | |
const sectionHits = new Map(); | |
// Used for testing | |
module.exports = { | |
clearRecentHits:clearRecentHits, | |
addRecentHits:addRecentHits, | |
removeOldestRecentHits:removeOldestRecentHits, | |
shouldAlert:shouldAlert, | |
} | |
var alertThreshold = 200; | |
if(process.argv.length > 3) | |
{ | |
alertThreshold = process.argv[3]; | |
} | |
// This is used for the alerting logic. | |
// Each element represents 10 seconds of history. | |
// pastHits[0] is the currently ongoing timespan. | |
// As an example pastHits[3] is the amount of hits in a | |
// 10-second interval starting 20-30 seconds ago. | |
const pastHits = new Array(12); | |
const timeBetweenUIRefreshes = 100; | |
clearRecentHits(); | |
function clearRecentHits() { | |
pastHits.fill(0); | |
} | |
function addRecentHits(amount) { | |
pastHits[0] += amount; | |
} | |
function removeOldestRecentHits() { | |
pastHits.unshift(0); | |
pastHits.pop(); | |
} | |
function recentHitsCount() { | |
return pastHits.reduce( | |
function(acc, val) { return acc + val; }, | |
0); | |
} | |
function shouldAlert() { | |
return recentHitsCount() >= alertThreshold; | |
} | |
server.on('error', (err) => { | |
rl.write("Error:" + err); | |
server.close(); | |
}); | |
server.on('message', (msg, info) => { | |
const matches = msg.toString().match(siteRegex); | |
if(matches.length<2) | |
{ | |
writeTransientLine("Error parsing " + msg); | |
return; | |
} | |
const match = matches[1]; | |
addRecentHits(1); | |
outputState.totalHits++; | |
// Update hit count for the full url | |
if(hits.has(match)) | |
{ | |
hits.set(match, hits.get(match) + 1); | |
} | |
else | |
{ | |
hits.set(match, 1); | |
} | |
// Update hit counts for eventual section and subsection | |
const parts = match.split('/'); | |
if(parts.length > 1) | |
{ | |
// We have a root section | |
const rootSection = parts[0]; | |
var subSections; | |
if(sectionHits.has(rootSection)) | |
{ | |
sectionHits.get(rootSection).hits += 1; | |
subSections = sectionHits.get(rootSection).subSections; | |
} | |
else | |
{ | |
subSections = new Map(); | |
sectionHits.set(rootSection, {hits:1,subSections:subSections}); | |
} | |
if(parts.length > 2) | |
{ | |
// We have a subsection | |
const subsection = parts[1]; | |
if(subSections.has(subsection)) | |
{ | |
subSections.set(subsection, subSections.get(subsection) + 1); | |
} | |
else | |
{ | |
subSections.set(subsection, 1); | |
} | |
} | |
} | |
}); | |
var outputState = { | |
numLinesToOverwrite:0, // How many lines we wrote in the changing part of the UI last tick | |
alerting:false, | |
totalHits:0, | |
startTime:new Date() | |
}; | |
setInterval(function() { | |
// Clear out last ticks transient bit of the UI. | |
readLine.cursorTo(process.stdout, 0); | |
readLine.moveCursor(process.stdout, 0, -outputState.numLinesToOverwrite); | |
outputState.numLinesToOverwrite=0; | |
readLine.clearScreenDown(process.stdout); | |
// Raise or clear alerts | |
if(shouldAlert()) { | |
if (!outputState.alerting) { | |
const count = recentHitsCount(); | |
const time = (new Date).toLocaleString(); | |
rl.write("High traffic generated an alert - hits = " + count + ", triggered at " + time + "\n"); | |
outputState.alerting = true; | |
} | |
} else if (outputState.alerting) { | |
const time = (new Date).toLocaleString(); | |
rl.write("Alert recovered at " + time + "\n"); | |
outputState.alerting = false; | |
} | |
// Show the header line | |
const uptime = ((new Date()) - outputState.startTime)/60000; | |
writeTransientLine("Total hits:" + outputState.totalHits + " Recent:" + recentHitsCount() + " " + uptime.toFixed(1) + " min uptime"); | |
writeTransientLine(""); | |
// Show the 3 most hit pages | |
const topPages = Array.from(hits.entries()); | |
topPages.sort((a,b) => {return b[1]-a[1];}); | |
for (var entry of topPages.slice(0,3)) { | |
writeTransientLine(entry[0] + ":" + entry[1]); | |
} | |
writeTransientLine(""); | |
// Show the 3 most hit sections, and their most hit subsections. | |
const topSections = Array.from(sectionHits.entries()); | |
topSections.sort((a,b) => {return b[1].hits-a[1].hits;}); | |
for (var entry of topSections.slice(0,3)) { | |
const section = entry[0]; | |
writeTransientLine(section + ":" + entry[1].hits); | |
const topSubSections = Array.from(entry[1].subSections.entries()); | |
topSubSections.sort((a,b) => { return b[1]-a[1];}); | |
for (var sub of topSubSections.slice(0,3)) { | |
writeTransientLine("\t" + sub[0] + ":" + sub[1]); | |
} | |
} | |
removeOldestRecentHits(); | |
}, | |
timeBetweenUIRefreshes | |
); | |
function writeTransientLine(s) { | |
rl.write(s + '\n'); | |
outputState.numLinesToOverwrite++; | |
} | |
if(process.argv.length > 2) { | |
server.bind(process.argv[2]); | |
} else { | |
server.bind(8888); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment