Skip to content

Instantly share code, notes, and snippets.

@nherment
Created April 21, 2017 13:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nherment/8f7b6a5533616e53f595a7995df62bf4 to your computer and use it in GitHub Desktop.
Save nherment/8f7b6a5533616e53f595a7995df62bf4 to your computer and use it in GitHub Desktop.
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