UNPKG

glutenfree

Version:

A profiler/loganalyzer for nginx/Cetrea Aw.

277 lines (227 loc) 7.68 kB
#!/usr/bin/env coffee dgram = require("dgram") optimist = require("optimist") winston = require("winston") http = require("http") uuid = require("node-uuid") util = require("util") _ = require("underscore") fs = require("fs") cluster = require("cluster") argv = optimist .usage(""" Usage: $0 -i [inputfile] -s [server] -p [port] -u [user] -w [password] -n [workers] -t [time] -h The Profiler (Pr) can operate in two modes; master and slave. The master-mode is entered by providing an input file (the statistics structure outputted from a LogAnalyzer then ArgumentGenerator w glutenfree targeting). The slave-mode is entered into by default (when no parameters are given). """) .alias("i", "input") .describe("i","Input file") .describe("s","The hostname of the server (Aw) to connect to.") .alias("s", "server") .describe("p","The port of the server (Aw) to connect to.") .alias("p", "port") .describe("u","Username (used in basic-auth)") .alias("u", "user") .describe("w","Password (used in basic-auth)") .alias("w", "password") .alias("h","help") .describe("t","Time to wait between requests (in ms)") .alias("t", "time") .describe("h","Display usage.") .alias("n","workers") .describe("n","Number of workers per slave. Default is number of CPUs on slave machine. Set value to force higher concurrency.") .argv if argv.h? optimist.showHelp() process.exit(0) mode = if argv.i? then "master" else "slave" slaves = {} if mode is "master" winston.info "Pr in MASTER mode" if not (argv.s? and argv.p? and argv.u? and argv.w?) optimist.showHelp() winston.error "Missing server, port, user or password. Please provide all four." process.exit(0) file = if argv.i[0] is "/" then argv.i else "./#{argv.i}" input = require(file) # setup control interface port = Math.floor((Math.random()*1000)+3000); ios = require('socket.io').listen(port, {log: false}) winston.info "control interface running on port #{port}" # io.set('log level', 0) ios.sockets.on( "connection", (socket) -> socket.on( "enslave", (slave) -> # add slave if it isnt alreadt registered if not slaves[socket.id]? winston.info "#{slave.id} enslaved" # remember slave slaves[socket.id] = _.extend(slave, { socket: socket }) # tell slave to start profiling socket.emit( "profile", { targeting: input server: argv.s port: argv.p user: argv.u password: argv.w workerCount: argv.n pause: argv.t } ) ) socket.on( "report" (hits) -> slave = slaves[socket.id] winston.debug "sample", hits[0] # build csv csv = _.reduce( hits (memo, hit) -> memo += _.map( _.values(hit) (value) -> if typeof(value) is "string" and value.indexOf(",") > 0 then "\"#{value}\"" else value ) .join(",") + "\n" "" ) # appending to file fs.appendFile( "#{slave.id}.csv" csv (err) -> if err? then winston.error "Could not write results to file.", err else winston.info "Results from #{slave.id} appended to file." ) ) socket.on( "disconnect", -> slave = slaves[socket.id] delete slaves[socket.id] winston.info "slave #{slave.id} freed" ) ) # advertise me in loop setInterval( () -> message = new Buffer("GLUTENFREE|Pr(MASTER)|ONLINE|#{port}") client = dgram.createSocket("udp4") client.send( message, 0, message.length, 5354, "224.0.0.251", (err, bytes) -> client.close() ) 5000 ) else # mode is SLAVE ioc = require("socket.io-client") winston.info "Pr in SLAVE mode" me = id: uuid.v4() state: "AVAILABLE" #224.0.0.251:5353 lsocket = dgram.createSocket("udp4") iosock = {} reports = [] # reporting will contain the interval for reporting reporting = {} lsocket.on( "message", (msg, rinfo) -> if me.state is "AVAILABLE" [protocol,type,state,interfaceport] = "#{msg}".split("|") # reporting function # reports back to master over iosocket (websocket) report = (iosock, interval) -> if reports? and reports.length > 0 payload = _.flatten(_.pluck(reports, "hits")) #winston.debug "#{reports.length} reports sent to MASTER" iosock.emit("report", payload) # reset reports reports = [] if me.state is "SLAVED" and iosock setTimeout( -> report(iosock, interval) interval ) iosock = ioc.connect("http://#{rinfo.address}:#{interfaceport}") iosock.emit("enslave", me) iosock.on( "connect", -> me.state = "SLAVED" winston.info "enslaved" ) iosock.on( "profile", (order) -> # extract targeting information targeting = order.targeting # setup cluster, ProfilerWorker.coffee contains worker-code cluster.setupMaster({ exec: "#{__dirname}/ProfilerWorker" }) # determine number of workers and fork workerCount = order.workerCount || require("os").cpus().length for w in [1..workerCount] do (w) -> cluster.fork() cluster.on("exit", (worker) -> if me.state is "SLAVED" winston.warn "worker (#{worker?.process?.pid}) died - forking new" cluster.fork() ) # send target lookup to worker cluster.on("online", (worker) -> # listen for reports from worker worker.on("message", (msg) -> switch msg.subject when "report" rprt = msg.report #winston.info "recv report from #{worker.id} containing #{rprt.hits.length} hits" reports.push(rprt) ) # wait a tick before ordering new worker around setTimeout( -> winston.info "sending targeting to", worker.id worker.send({ subject: "targeting", clusterId: me.id, server: order.server, port: order.port, user: order.user, password: order.password, targeting: targeting, pause: order.pause }) 1000 ) ) # setup timed reporting (every 10th second) report(iosock, 10000) ) # go back to AVAILABLE when disconnected iosock.on( "disconnect", -> me.state = "AVAILABLE" winston.info "freed, now asking workers to die" cluster.disconnect() ) ) lsocket.on( "listening", () -> address = lsocket.address() multicastadr = "224.0.0.251" winston.info "Pr SLAVE listening for MASTERs at #{address.address}:#{address.port} (multicast at #{multicastadr})" lsocket.addMembership(multicastadr) ) # discovery lsocket is bound lsocket.bind(5354)