sqd
Version:
executing unix commands with multi processes
208 lines (180 loc) • 6.96 kB
text/coffeescript
fs = require "fs"
tmp = require "tmp"
cp = require "child_process"
worker = require "./worker.js"
# main operation
main = (options)->
{ input, output, command, separator, nProcess, startTime, onClose, debug, stop, mem, rhwm, whwm, reduce } = options
if mem
count = 0
interval = setInterval( ->
memory = process.memoryUsage()
console.error "[memory]", ++count, memory.rss, memory.heapTotal, memory.heapUsed
, 1000)
else
interval = null
separator = "line" if not separator
# if .js file is passed, execute it
separatorAsJS = "#{__dirname}/#{separator}_separator.js"
if fs.existsSync separatorAsJS
separationOpearator = require separatorAsJS
separationOpearator input, nProcess, (err, separationRule)->
if err
throw err
else
process.nextTick -> run separationRule
# if command is passed, execute it and parse the result
else
sepcommand = [separator, input, nProcess].join(" ")
cp.exec sepCommand, (e, stdout, stderr)->
separationRule = JSON.parse(stdout)
run separationRule
# running commands as child processes
tmpfiles = []
finishProcesses = 0
run = (rule)->
if debug
console.error "after separator: %dms", new Date().getTime() - startTime
console.error "[separation rules]"
console.error JSON.stringify rule
positions = rule.positions
throw new Error "invalid separator: no positions" unless positions.length
actualNProcess = positions.length
debug and console.error "separator separated the file into #{actualNProcess} while p is #{nProcess}"
# if reduce, spawning a child process as a reducer
reducer = null
finalWstream = null
if reduce
reducer = cp.spawn
commandArgs = command.split(" ")
commandName = commandArgs.shift()
env = {}
env[k] = v for k, v of process.env
for k,v of options when typeof v isnt "function"
if typeof v is "boolean"
env["sqd_" + k] = if v then "1" else "0"
else
env["sqd_" + k] = v
env.sqd_reduce = "1"
reducer = cp.spawn(commandName, commandArgs, env: env)
finalWstream = if output then fs.createWriteStream output, highWaterMark: 1e5 else process.stdout
reducer.stdout.pipe finalWstream
debug and console.error "spawning a child process as a reducer"
tmp.setGracefulCleanup()
for k in [0...actualNProcess]
do (n = k)->
tmp.tmpName (e, path)->
if n isnt 0
tmpfiles.push path
debug and console.error "using tmp file: #{path}"
worker(
input : input
tmpfile : if n is 0 then (if reducer then reducer.stdin else if output then output else process.stdout) else path # write to output file when n is 0
command : command
start : positions[n]
end : if n+1 isnt actualNProcess then positions[n+1]-1 else null
n : n
debug : debug
hStart : rule.header?[0]
hEnd : rule.header?[1]
rhwm : rhwm
whwm : whwm
stop : stop
callback: ()->
finishProcesses++
debug and console.error "%d process(es) finished: %dms", finishProcesses, new Date().getTime() - startTime
if finishProcesses is actualNProcess
if tmpfiles.length
cat = cp.spawn "cat", tmpfiles
wstream = if reducer then reducer.stdin else if output then fs.createWriteStream output, flags: "a", highWaterMark: 1e5 else process.stdout
showStderr cat.stderr, "cat"
wstream.on "error", (e)-> error "outputStream", stop
cat.stdout.on "error", (e)-> error "cat.stdout", stop
cat.stdin.on "error", (e)-> error "cat.stdin", stop
cat.on "error", (e)-> error "cat", stop
cat.stdout.pipe wstream
onExit = ->
unlinkCounter = 1
cb = ->
if ++unlinkCounter is actualNProcess
clearInterval interval if interval
onClose()
fs.unlink tmpfile, cb for tmpfile in tmpfiles
if debug
cat.on "exit", (code, signal)->
console.error "cat onExit code:#{code}, #{signal}"
cat.on "close", (code, signal)->
console.error "cat onClose code:#{code}, #{signal}"
cat.on "disconnect", (code, signal)->
console.error "cat Disconnect"
else
onClose() if typeof onClose is "function"
if finalWstream
finalWstream.on "close", onExit
else if wstream is process.stdout
cat.on "close", onExit
else
wstream.on "close", onExit
)
error = (streamName, exit)->
return (e)->
console.error "[ERROR] #{streamName} :"
console.error e.stack
process.exit(1) if exit
showStderr = (stderr, name, exit)->
return if stderr is process.stderr
stderr.setEncoding "utf-8"
stderr.on "data", (data)->
console.error "STDERR in #{name}:"
console.error data
process.exit(1) if exit
showUsage = ->
console.error """
[USAGE]
\tsqd -c command [--debug] [--exit] [-p #process] [-s separator_command] [--reduce] <input file> [output file]
\tcommand:\t\t a unix command to be multiply executed from stream of the given input file
\tseparator_command:\t command which gives a set of information of separation of the given input file.
\t#process:\t\t the number of processes for parallel execution
"""
exports.run = ->
# argument definition
try
ap = require("argparser")
.files(0)
.arglen(1,2)
.vals("c","command", "s", "sep")
.nonvals("w", "debug", "d", "e", "exit", "mem", "reduce")
.nums("rhwm", "whwm")
.defaults(p: 4)
.parse()
catch e
console.error "[ERROR]: #{e.message}"
showUsage()
process.exit(1)
# creating a separation rule
separator = ap.opt("sep", "s")
# subcommand
command = ap.opt("command", "c")
if not command
console.error '[ERROR]: -c "<command>" is required'
showUsage()
process.exit(1)
startTime = new Date().getTime()
debug = ap.opt("debug", "d")
main(
input : ap.arg(0)
output : ap.arg(1)
nProcess : ap.opt("p")
command : command
separator : ap.opt("sep", "s")
startTime : startTime
debug : debug
stop : ap.opt("e", "exit")
mem : ap.opt("mem")
whwm : ap.opt("whwm")
rhwm : ap.opt("rhwm")
reduce : ap.opt("reduce")
onClose : ->
debug and console.error "time: %dms", new Date().getTime() - startTime
)
exports.run() if require.main is module