UNPKG

guv

Version:

Grid Utilization Virgilante

142 lines (107 loc) 4.31 kB
debug = require('debug')('guv:config') gaussian = require 'gaussian' url = require 'url' yaml = require 'js-yaml' fs = require 'fs' path = require 'path' calculateTarget = (config) -> # Calculate the point which the process completes # the desired percentage of jobs within debug 'calculate target', config tolerance = (100-config.percentile)/100 mean = config.processing variance = config.stddev*config.stddev d = gaussian mean, variance ppf = -d.ppf(tolerance) distance = mean+ppf # TODO: throw Error on impossible config # Shift the point up till hits at the specified deadline # XXX: Is it a safe assumption that variance is same for all return config.deadline-distance jobsInDeadline = (config) -> return config.target / config.process_time # Syntactical part parse = (str) -> o = yaml.safeLoad str o = {} if not o return o configFormat = () -> varFormat = [ 'short', 'name', 'description', 'unit', 'default' ] varList = [ # system-unique process parameters [ 'p', 'processing', 'Mean job processing time', 'seconds', 10.0 ] [ null, 'stddev', 'Standard deviation (1σ) of job processing time: 68% completed within -+ this.', 'seconds', '50% of mean processing time' ] [ 'd', 'deadline', 'Time practically all jobs should be completed within.', 'seconds', 60.0 ] [ null, 'boot', 'Mean boot time. From adding worker to processing jobs', 'seconds', 30.0 ] # worker limits [ 'max', 'maximum', 'Maximum amount of workers', 'N workers', 5 ] [ 'min', 'minimum', 'Minimum amount of workers', 'N workers', 1 ] # names [ 'w', 'worker', 'Worker name (dyno role)', 'string', 'role name' ] [ 'q', 'queue', 'Queue name', 'string', 'role name' ] [ null, 'app', 'Application name (ie on Heroku)', 'string', 'GUV_APP envvar' ] [ null, 'broker', 'Broker (ie RabbitMQ) URL', 'url', 'CLOUDAMQP_URL or GUV_BROKER envvar' ] # http://statuspage.io integration [ null, 'statuspage', 'Page id (for statuspage.io)', 'string', 'STATUSPAGE_ID envvar' ] [ null, 'metric', 'Metric id (for statuspage.io)', 'string', null ] # derived/advanced process parameters [ null, 'percentile', ' ', '%', 99 ] [ null, 'target', ' ', 'seconds', 'Calculated based on process time and variance, to meet percentile and deadline.' ] ] format = shortoptions: {} options: {} for v in varList o = {} varFormat.forEach (field, i) -> o[field] = v[i] o.type = 'string' o.type = 'number' if o.unit in [ 'N workers', 'seconds', '%' ] format.options[o.name] = o format.shortoptions[o.short] = o if o.short return format addDefaults = (format, role, c) -> for name, option of format.options continue if typeof option.default == 'string' c[name] = option.default if not c[name]? # TODO: make these functions with a toString, declared in varList? c.statuspage = process.env['STATUSPAGE_ID'] if not c.statuspage c.broker = process.env['GUV_BROKER'] if not c.broker c.broker = process.env['CLOUDAMQP_URL'] if not c.broker if role != '*' c.worker = role if not c.worker c.queue = role if not c.queue c.stddev = c.processing*0.5 if not c.stddev c.target = calculateTarget c if not c.target return c normalize = (role, vars, globals) -> format = configFormat() retvars = {} # Make all globals available on each role # Note: some things don't make sense be different per-role, but simpler this way for k, v of globals retvars[k] = v for name, val of vars # Lookup canonical long name from short name = format.shortoptions[name].name if format.shortoptions[name]? # Defined var f = format.options[name] retvars[name] = val # Inject defaults retvars = addDefaults format, role, retvars return retvars parseConfig = (str) -> parsed = parse str config = {} # Extract globals first, as they will be merged into individual roles globalRole = '*' parsed[globalRole] = {} if not parsed[globalRole] config[globalRole] = normalize globalRole, parsed[globalRole], {} for role, vars of parsed continue if role == globalRole config[role] = normalize role, vars, config[globalRole] return config exports.parse = parseConfig exports.parseOnly = parse exports.defaults = addDefaults