glutenfree
Version:
A profiler/loganalyzer for nginx/Cetrea Aw.
218 lines (180 loc) • 7.6 kB
text/coffeescript
Q = require("q")
http = require("http")
url = require("url")
fs = require("fs")
xml2js = require("xml2js")
_ = require("underscore")
winston = require("winston")
util = require("util")
class ArgumentGenerator
# @server is server
# @cache is a type.prop, values cache, e.g. BedPlace.Oid: [BedPlace.1, BedPlace.2]
# @premapped is a map of readymade conversions
# @mappers is a list of mappers, if none are provide auto-discovery is set in motion
constructor: (@server, @lookupConfiguration, @cache = {}, @premapped = {}, @mappers, @targeting) ->
@version = "0.2.1"
# find available mappers
@parser = new xml2js.Parser({ ignoreAttrs: true })
# if empty mappers list is supplied
winston.info "loading mappers"
if not @mappers?
@mappers = {}
for file in fs.readdirSync("ArgumentMappers") when file.match(/.*\.js/)
do (file) =>
m = require("./ArgumentMappers/#{file}").mapper
@mappers[m.identifier] = m
winston.info "installed mapper for '#{m.identifier}'"
winston.info "loading targeting"
if not @targeting?
@targeting = {}
for file in fs.readdirSync("ProfilerTargeting") when file.match(/.*\.js/)
do (file) =>
t = require("./ProfilerTargeting/#{file}").targeting
@targeting[t.name] = t
winston.info "installed targeting '#{t.name}'"
# map from given arguments to ones matching the given server
map: (method, action, component, componentVersion, endpoint, fun, args) ->
cached = @premapped["#{method}.#{component}.#{componentVersion}.#{endpoint}.#{fun}.#{args}"]
if cached? then return Q.fcall(->cached)
# retrieve mapper for endpoint
mapperId = [component, componentVersion, endpoint].filter((c) -> c?).join("/")
mapper = @mappers[mapperId]
# if no mapper for endpoint is found simple return url
if not mapper?
winston.warn "No mapper for #{mapperId} found. Original args returned."
return Q.fcall(->args)
# get schema for function
schema = mapper.schema(method, endpoint, action, fun, decodeURI(args))
if not schema?
winston.warn "No schema returned for '#{method} #{action} (#{component}/#{componentVersion}) #{endpoint}/#{fun}/#{args}'. Original args returned."
return Q.fcall(->args)
# walk through schema and find arguments from 1) cache, 2) server
promises = _.map(
_.flatten(schema),
(arg) =>
switch arg.origin
when "mapi"
cached = @cache["#{arg.type}.#{arg.hql}.#{arg.depth}"]
value = if cached? then cached
if not value?
deferred = Q.defer()
#deferred.promise.then (values) -> arg.newvalue = values
#@cache["#{arg.type}.#{arg.hql}.#{arg.depth}"] = deferred.promise
options =
host: @server
port: 8080
path: "/#{arg.type}?depth=#{arg.depth}&limit=200&hql=active=true" + (if arg.hql? then " AND "+arg.hql else "")
auth: "spider:spider"
headers: {
"User-Agent": "GLUTENFREE (AG-#{@version})"
}
# go through server
http.get(options, (res) =>
chunks = ""
res.on("data", (chunk) ->
chunks += chunk
)
res.on("end", () =>
console.log "get", options
# xml2js the shit out of this chunked mofo
@parser.parseString(
chunks,
(err, data) =>
values = _.flatten(_.values(data.Collection))
# console.log "values", values
# save result in @cache
@cache["#{arg.type}.#{arg.hql}.#{arg.depth}"] = values
# save result in arg.newval
arg.newvalue = values
# done, resolve
deferred.resolve(arg)
)
)
).on("error", (e) =>
winston.error e
deferred.reject("error when getting #{arg.type}.#{arg.hql}.#{arg.depth}")
)
# return
return deferred.promise
else # argument was cached
arg.newvalue = value
arg
when "configuration", "conf"
# load and match from configuration
if @lookupConfiguration?
newvalue = []
for currentvalue in arg.currentvalue
do (currentvalue) =>
v = @lookupConfiguration[arg.type][currentvalue]
if not v? # if no explicit mapping then pick random value
v = _.first(_.shuffle(_.values(@lookupConfiguration[arg.type])))
newvalue.push(v)
arg.newvalue = newvalue
else
arg.newvalue = arg.currentvalue
# return arg
arg
when "fixed" # origin is not mapi
arg.newvalue = arg.currentvalue
arg
)
# wait for all promises to be fullfulled then let mapper generate url
Q.allSettled(promises).then(
=>
# run through all resolved and pluck property
for schematic in _.flatten(schema)
do (schematic) ->
if schematic.origin is "mapi"
candidates = if schematic.filter? then _.filter(schematic.newvalue, (t) -> schematic.filter(t, schema)) else schematic.newvalue
schematic.newvalue = _.first(_.shuffle(_.flatten(_.pluck(candidates,schematic.property))),schematic.currentvalue.length)
url = mapper.applySchema(method, action, endpoint, fun, schema)
@premapped["#{method}.#{component}.#{componentVersion}.#{endpoint}.#{fun}.#{args}"] = url
console.log "url (pre-encode)", url
encodeURI(url)
)
exports.AG = ArgumentGenerator
# if run as main module
if require.main is module
argv = require('optimist')
.usage("Usage: $0 -i [inputfile] -o [outputfile] -s [server] -t [targeting] -c [configuration]")
.alias("s", "server")
.demand(["i","o","s"])
.alias("c", "configiration")
.argv
statsFile = if argv.i[0] is "/" then argv.i else "./#{argv.i}"
stats = require(statsFile)
lookupConf =
if argv.c?
if argv.c[0] is "/" then require(argv.c) else require("./#{argv.c}")
else {}
winston.info "creating ag"
ag = new ArgumentGenerator(argv.s, lookupConf)
winston.info "looping stats"
# deep loop
promises = []
for id, info of stats.uniques
do (id, info) ->
info.newArgs = []
promises.push(
ag.map(info.method, info.action, info.component, info.componentVersion, info.endpoint, info.fun, info.args)
.then(((newarg) -> info.newArgs.push newarg))
)
Q.all(promises)
.then(
->
# targeting
targeting = ag.targeting[argv.t]
if not targeting?
fs.writeFile(
argv.o,
JSON.stringify(stats),
(err) -> if not err? then winston.info "raw output written to #{argv.o}" else winston.warn "error writing to #{argv.o}"
)
else
targets = targeting.generate(stats)
fs.writeFile(
argv.o,
JSON.stringify(targets),
(err) -> if not err? then winston.info "targeting (#{targeting.name}) output written to #{argv.o}" else winston.warn "error writing to #{argv.o}"
)
)