This commit is contained in:
Hannes Verschore 2015-09-17 12:09:19 -07:00
Родитель 6d05f561e1 9d4b150375
Коммит a3d9498a8b
13 изменённых файлов: 333 добавлений и 60 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -24,4 +24,5 @@ benchmarks/asmjs-apps/zlib/minigzip64
benchmarks/asmjs-apps/zlib/minigzipsh
driver/awfy.config
slave/awfy.config
**/*-results/

Просмотреть файл

@ -2,14 +2,16 @@ Components
==========
Slave:
1. Builder: A python driver (build.py) that can create shell builds of spidermonkey/jsc/v8.
2. Downloader: A python driver (download.py) that can download browser builds of Firefox.
3. Executor: (execute.py) is a python script that executes one or multiple benchmarks on one or more builds.
Site:
1. Database: MySQL database that stores statistics.
2. Collector: Hidden PHP script on the webserver, where stats get sent.
3. Processor: Python aggregator that builds JSON data from the DB.
2. Collector: Hidden PHP script on the webserver, where stats get sent. (UPDATE.php in below diagram)
3. Processor: Python aggregator that builds JSON data from the DB. (update.py in below diagram)
4. Website: Static HTML as the frontpage, that queries JSON via XHR.
5. Command center: Sends commands to the slaves on what to execute. (In construction.)
@ -17,26 +19,38 @@ Components (2) and (4) must be on the same webserver, otherwise timestamps might
Keep in mind, most of this documentation is for posterity. AWFY was never intended to be a drag-and-drop all-in-one released product, so the procedures and scripts may be pretty rough.
System Diagram
==============
<!--
Image source at https://docs.google.com/drawings/d/1TlzFOMz4oxKYSD_hHqe-fL2wpAA5WwhEq62KJp0TytI/edit?usp=sharing
To edit it, 'make a copy' (from file menu) and edit that.
-->
![Alt text](/docs/awfy_system_diagram.png "System Diagram")
Benchmark locally
=================
1. Fetch the repo
2. Create a (shell) or retrieve a (browser) build to benchmark
* Creating a build:
```
cd slave
python build.py -s mozilla
```
- Creating a build:
* Pull a build:
cd slave
python build.py -s mozilla
- Pull a build:
cd slave
python download.py http://archive.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds/mozilla-inbound-linux/latest/
```
cd slave
python download.py http://archive.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds/mozilla-inbound-linux/latest/
```
3. Benchmark
python execute.p -b remote.octane -b remote.kraken
```
python execute.p -b remote.octane -b remote.kraken
```
Installation
============

Двоичные данные
docs/awfy_system_diagram.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 46 KiB

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

После

Ширина:  |  Высота:  |  Размер: 91 KiB

Просмотреть файл

@ -12,7 +12,6 @@ updateURL = http://www.arewefastyet.com/???
list = mozilla
[benchmarks]
dir = /home/h4writer/Build/arewefastyet/benchmarks
browserList = benchmark.remote.octane,
benchmark.remote.massive,
benchmark.remote.jetstream,

Просмотреть файл

@ -115,6 +115,85 @@ class Assorted(SunSpiderBased):
def __init__(self):
super(Assorted, self).__init__('misc', '0.5', 'misc', 3)
class AsmJSBased(Benchmark):
def __init__(self, suite, version, folder):
super(AsmJSBased, self).__init__(suite, version, folder)
"""
def _run(self, submit, native, modes):
# Run the C++ mode.
full_args = [utils.config.PythonName, 'harness.py', '--native']
full_args += ['--cc="' + native.cc + '"']
full_args += ['--cxx="' + native.cxx + '"']
full_args += ['--'] + native.args
output = utils.RunTimedCheckOutput(full_args)
tests = self.parse(output)
submit.AddTests(tests, self.suite, self.version, native.mode)
# Run normal benchmarks.
super(AsmJS, self)._run(submit, native, modes)
"""
def benchmark(self, shell, env, args):
full_args = [utils.config.PythonName, 'harness.py', shell, '--'] + args
print(' '.join(full_args))
output = utils.RunTimedCheckOutput(full_args, env=env)
return self.parse(output)
def parse(self, output):
total = 0.0
tests = []
for line in output.splitlines():
m = re.search("(.+) - (\d+(\.\d+)?)", line)
if not m:
continue
name = m.group(1)
score = m.group(2)
total += float(score)
tests.append({ 'name': name, 'time': score })
tests.append({ 'name': '__total__', 'time': total })
return tests
class AsmJSMicro(AsmJSBased):
def __init__(self):
super(AsmJSMicro, self).__init__('asmjs-ubench', '0.4.3', 'asmjs-ubench')
class AsmJSApps(AsmJSBased):
def __init__(self):
super(AsmJSApps, self).__init__('asmjs-apps', '0.2', 'asmjs-apps')
class Dart(Benchmark):
def __init__(self):
super(Dart, self).__init__('dart', '0.1', 'dart')
def benchmark(self, shell, env, args):
full_args = [shell]
if args:
full_args.extend(args)
full_args.append('run.js')
print(os.getcwd())
output = utils.RunTimedCheckOutput(full_args, env=env)
tests = []
lines = output.splitlines()
total = 0.0
for x in lines:
m = re.search("(.+)\(RunTime\): (\d+\.\d+)", x)
if not m:
continue
name = m.group(1)
score = float(m.group(2))/1000
total += score
tests.append({ 'name': name, 'time': score})
print(str(score) + ' - ' + name)
tests.append({ 'name': '__total__', 'time': total })
return tests
def getBenchmark(name):
if name == "octane":
return Octane()
@ -124,6 +203,12 @@ def getBenchmark(name):
return Kraken()
if name == "assorted":
return Assorted()
if name == "asmjsapps":
return AsmJSApps()
if name == "asmjsmicro":
return AsmJSMicro()
if name == "dart":
return Dart()
raise Exception("Unknown benchmark")
def run(submit, native, modes):

Просмотреть файл

@ -1,3 +1,4 @@
import sys
import json
import urllib2
import urllib
@ -30,8 +31,9 @@ class Environment(object):
def get(self):
env = self.env_.copy()
env["CC"] += " " + " ".join(self.ccoption)
env["CXX"] += " " + " ".join(self.ccoption)
if len(self.ccoption) > 0:
env["CC"] += " " + " ".join(self.ccoption)
env["CXX"] += " " + " ".join(self.ccoption)
return env
class Builder(object):
@ -41,11 +43,11 @@ class Builder(object):
self.config = config
self.folder = folder
if platform.system() == "Darwin":
self.installClang()
self.env.add("CC", os.path.abspath("clang-3.3/bin/clang"))
self.env.add("CXX", os.path.abspath("clang-3.3/bin/clang++"))
self.env.add("LINK", os.path.abspath("clang-3.3/bin/clang++"))
#if platform.system() == "Darwin":
# self.installClang()
# self.env.add("CC", os.path.abspath("clang-3.3/bin/clang"))
# self.env.add("CXX", os.path.abspath("clang-3.3/bin/clang++"))
# self.env.add("LINK", os.path.abspath("clang-3.3/bin/clang++"))
def installClang(self):
# The standard clang version on mac is outdated.
@ -96,8 +98,8 @@ class Builder(object):
info["revision"] = puller.identify()
info["shell"] = True
info["binary"] = os.path.abspath(self.binary())
fp = open(self.folder + "info.json", "w")
fp = open(os.path.join(self.folder, "info.json"), "w")
json.dump(info, fp)
fp.close()
@ -109,7 +111,7 @@ class MozillaBuilder(Builder):
self.env.add("AR",'ar')
self.env.add("CROSS_COMPILE", '1')
self.env.addCCOption("-m32")
def retrieveInfo(self):
info = {}
info["engine_type"] = "firefox"
@ -133,7 +135,7 @@ class MozillaBuilder(Builder):
# Step 2. configure
if not os.path.exists(os.path.join(self.folder, 'js', 'src', 'Opt')):
os.mkdir(os.path.join(self.folder, 'js', 'src', 'Opt'))
os.mkdir(os.path.join(self.folder, 'js', 'src', 'Opt'))
with utils.FolderChanger(os.path.join(self.folder, 'js', 'src', 'Opt')):
args = ['--enable-optimize', '--disable-debug']
if platform.architecture()[0] == "64bit" and self.config == "32bit":
@ -143,7 +145,7 @@ class MozillaBuilder(Builder):
args.append("--target=i686-pc-linux-gnu")
else:
assert False
Run(['../configure'] + args, self.env.get())
return True
@ -162,26 +164,24 @@ class WebkitBuilder(Builder):
info["env"] = {'DYLD_FRAMEWORK_PATH': objdir}
return info
def make(self):
with utils.chdir(os.path.join(self.folder)):
def patch(self):
patch = os.path.join(os.path.dirname(os.path.realpath(__file__)), "jsc.patch")
with utils.FolderChanger(self.folder):
# Hack 1: Remove reporting errors for warnings that currently are present.
Run(["sed","-i.bac","s/GCC_TREAT_WARNINGS_AS_ERRORS = YES;/GCC_TREAT_WARNINGS_AS_ERRORS=NO;/","Source/JavaScriptCore/Configurations/Base.xcconfig"])
Run(["sed","-i.bac","s/GCC_TREAT_WARNINGS_AS_ERRORS = YES;/GCC_TREAT_WARNINGS_AS_ERRORS=NO;/","Source/bmalloc/Configurations/Base.xcconfig"])
Run(["sed","-i.bac","s/GCC_TREAT_WARNINGS_AS_ERRORS = YES;/GCC_TREAT_WARNINGS_AS_ERRORS=NO;/","Source/WTF/Configurations/Base.xcconfig"])
Run(["sed","-i.bac","s/std::numeric_limits<unsigned char>::max()/255/","Source/bmalloc/bmalloc/Line.h"])
Run(["sed","-i.bac","s/std::numeric_limits<unsigned char>::max()/255/","Source/bmalloc/bmalloc/Page.h"])
Run(["patch","Source/JavaScriptCore/jsc.cpp","../../driver/jsc.patch"])
Run(["patch","Source/JavaScriptCore/jsc.cpp", patch])
with utils.FolderChanger(os.path.join('Tools', 'Scripts')):
# Hack 2: This check fails currently. Disable checking to still have a build.
os.rename("check-for-weak-vtables-and-externals", "check-for-weak-vtables-and-externals2");
# Hack 2: This check fails currently. Disable checking to still have a build.
os.remove("Tools/Scripts/check-for-weak-vtables-and-externals")
args = ['/usr/bin/perl', 'build-jsc']
if self.config == '32bit':
args += ['--32-bit']
Run(args, self.env.get())
os.rename("check-for-weak-vtables-and-externals2", "check-for-weak-vtables-and-externals");
def clean(self):
with utils.FolderChanger(self.folder):
Run(["svn","revert","Tools/Scripts/check-for-weak-vtables-and-externals"])
Run(["svn","revert","Source/JavaScriptCore/Configurations/Base.xcconfig"])
Run(["svn","revert","Source/bmalloc/Configurations/Base.xcconfig"])
@ -190,6 +190,17 @@ class WebkitBuilder(Builder):
Run(["svn","revert","Source/bmalloc/bmalloc/Page.h"])
Run(["svn","revert","Source/JavaScriptCore/jsc.cpp"])
def make(self):
try:
self.patch()
with utils.FolderChanger(os.path.join(self.folder, 'Tools', 'Scripts')):
args = ['/usr/bin/perl', 'build-jsc']
if self.config == '32bit':
args += ['--32-bit']
Run(args, self.env.get())
finally:
self.clean()
def objdir(self):
return os.path.join(self.folder, 'WebKitBuild', 'Release')
@ -209,7 +220,7 @@ class V8Builder(Builder):
return info
def make(self):
args = ['make', '-j6', '-C', self.folder]
args = ['make', '-j6', '-C', os.path.join(self.folder, 'v8')]
if self.config == '32bit':
args += ['ia32.release']
elif self.config == '64bit':
@ -221,9 +232,9 @@ class V8Builder(Builder):
def objdir(self):
if self.config == '64bit':
return os.path.join('out', 'x64.release')
return os.path.join(self.folder, 'v8', 'out', 'x64.release')
elif self.config == '32bit':
return os.path.join('out', 'ia32.release')
return os.path.join(self.folder, 'v8', 'out', 'ia32.release')
else:
assert False
@ -232,11 +243,11 @@ class V8Builder(Builder):
def getBuilder(config, path):
# fingerprint the known builders
if os.path.exists(os.path.join(path, "js/src/")):
if os.path.exists(os.path.join(path, "js", "src")):
return MozillaBuilder(config, path)
if os.path.exists(os.path.join(path, "Source/JavaScriptCore")):
if os.path.exists(os.path.join(path, "Source", "JavaScriptCore")):
return WebkitBuilder(config, path)
if os.path.exists(os.path.join(path, "LICENSE.v8")):
if os.path.exists(os.path.join(path, "v8", "LICENSE.v8")):
return V8Builder(config, path)
raise Exception("Unknown builder")
@ -263,14 +274,14 @@ if __name__ == "__main__":
(options, args) = parser.parse_args()
if options.repo is None:
print "Please provide the source repository to pull"
print "Please provide the source repository to pull"
exit()
if not options.output.endswith("/"):
options.output += "/"
if options.config not in ["default", "32bit", "64bit"]:
print "Please provide a valid config"
print "Please provide a valid config"
exit()
if options.config == "default":
@ -283,7 +294,7 @@ if __name__ == "__main__":
puller = puller.getPuller(options.repo, options.output)
puller.update(options.revision)
builder = getBuilder(options.config, puller.path())
builder = getBuilder(options.config, options.output)
if options.force:
builder.unlinkObjdir()
builder.build(puller)

Просмотреть файл

@ -39,15 +39,25 @@ class UnboxedObjects(Default):
def __init__(self, engine, shell):
super(UnboxedObjects, self).__init__(engine, shell)
if engine == "firefox":
if shell:
self.args_.append("--unboxed-arrays")
self.env_["JS_OPTION_USE_UNBOXED_ARRAYS"] = '1'
else:
self.omit_ = True
class TestbedRegalloc(Default):
def __init__(self, engine, shell):
super(TestbedRegalloc, self).__init__(engine, shell)
if engine == "firefox":
self.args_.append("--ion-regalloc=testbed")
else:
self.omit_ = True
class TurboFan(Default):
def __init__(self, engine, shell):
super(TurboFan, self).__init__(engine, shell)
if engine == "chrome"and shell:
self.args.append("--turbo");
self.args_.append("--turbo");
else:
self.omit_ = True
@ -55,7 +65,7 @@ class NoAsmjs(Default):
def __init__(self, engine, shell):
super(NoAsmjs, self).__init__(engine, shell)
if engine == "firefox" and shell:
self.args.append("--no-asmjs");
self.args_.append("--no-asmjs");
else:
self.omit_ = True
@ -64,6 +74,8 @@ def getConfig(name, info):
return Default(info["engine_type"], info["shell"])
if name == "unboxedobjects":
return UnboxedObjects(info["engine_type"], info["shell"])
if name == "testbedregalloc":
return TestbedRegalloc(info["engine_type"], info["shell"])
if name == "turbofan":
return TurboFan(info["engine_type"], info["shell"])
if name == "noasmjs":

Просмотреть файл

@ -3,6 +3,7 @@ import configs
import executors
import engineInfo
import submitter
import json
import sys
sys.path.insert(1, '../driver')
@ -17,9 +18,15 @@ parser.add_option("-b", "--benchmark", action="append", dest="benchmarks",
parser.add_option("-s", "--submitter", dest="submitter", type="string", default="print",
help="Submitter class ('remote' or 'print')")
parser.add_option("--submitter_mode", action="append", dest="mode_rules",
parser.add_option("--submitter-mode", action="append", dest="mode_rules",
help="When using the remote submitter, give rules to the mode name that needs to get submitted. Format: engine,config_name:mode_name. E.g. firefox,default:jmim")
parser.add_option("--submitter-machine", dest="machine", type="int",
help="When using the remote submitter, give the machine number to submit to.")
parser.add_option("--submitter-session", dest="session", type="string",
help="When using the remote submitter, it is possible to run execute.py multiple times and still report to the same report.")
parser.add_option("-e", "--engine", action="append", dest="engines",
help="Path to the engines that need to get benchmarked")
@ -41,7 +48,9 @@ if options.benchmarks is None:
if options.mode_rules is None:
options.mode_rules = [
"firefox,default:jmim",
"firefox,noasmjs:noasmjs",
"firefox,unboxedobjects:unboxedobjects",
"firefox,testbedregalloc:testbed",
"chrome,default:v8",
"chrome,turbofan:v8-turbofan",
"webkit,default:jsc",
@ -52,9 +61,18 @@ if options.mode_rules is None:
utils.config.init("awfy.config")
submitter = submitter.getSubmitter(options.submitter)
submitter.start()
submitter.setModeRules(options.mode_rules)
if options.session:
assert not options.machine
fp = open(options.session, "r")
session = json.load(fp)
submitter.setSession(session)
else:
if options.machine:
submitter.setMachine(options.machine)
submitter.start()
# Submit the revisions for every build.
for engine_path in options.engines:
@ -82,4 +100,5 @@ for benchmark in benchmarks:
mode = submitter.mode(info["engine_type"], config_name)
submitter.addTests(results, benchmark.suite, benchmark.version, mode)
submitter.finish()
if not options.session:
submitter.finish()

56
slave/jsc.patch Normal file
Просмотреть файл

@ -0,0 +1,56 @@
Index: jsc.cpp
===================================================================
--- jsc.cpp (revision 177145)
+++ jsc.cpp (working copy)
@@ -34,6 +34,7 @@
#include "Interpreter.h"
#include "JSArray.h"
#include "JSArrayBuffer.h"
+#include "JSArrayBufferConstructor.h"
#include "JSCInlines.h"
#include "JSFunction.h"
#include "JSLock.h"
@@ -461,6 +462,7 @@
static EncodedJSValue JSC_HOST_CALL functionRun(ExecState*);
static EncodedJSValue JSC_HOST_CALL functionLoad(ExecState*);
static EncodedJSValue JSC_HOST_CALL functionReadFile(ExecState*);
+static EncodedJSValue JSC_HOST_CALL functionReadBinaryFile(ExecState*);
static EncodedJSValue JSC_HOST_CALL functionCheckSyntax(ExecState*);
static EncodedJSValue JSC_HOST_CALL functionReadline(ExecState*);
static EncodedJSValue JSC_HOST_CALL functionPreciseTime(ExecState*);
@@ -598,6 +600,7 @@
addFunction(vm, "run", functionRun, 1);
addFunction(vm, "load", functionLoad, 1);
addFunction(vm, "readFile", functionReadFile, 1);
+ addFunction(vm, "readBinaryFile", functionReadBinaryFile, 1);
addFunction(vm, "checkSyntax", functionCheckSyntax, 1);
addFunction(vm, "jscStack", functionJSCStack, 1);
addFunction(vm, "readline", functionReadline, 0);
@@ -929,6 +932,26 @@
return JSValue::encode(jsString(exec, stringFromUTF(script.data())));
}
+EncodedJSValue JSC_HOST_CALL functionReadBinaryFile(ExecState* exec)
+{
+ String fileName = exec->argument(0).toString(exec)->value(exec);
+ Vector<char> script;
+ if (!fillBufferWithContentsOfFile(fileName, script))
+ return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
+
+ JSArrayBufferConstructor* constructor =
+ jsCast<JSArrayBufferConstructor*>(exec->callee());
+
+ RefPtr<ArrayBuffer> buffer = ArrayBuffer::create(script.data(), script.size());
+ if (!buffer)
+ return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Out of memory"))));
+
+ JSArrayBuffer* result = JSArrayBuffer::create(
+ exec->vm(), constructor->globalObject()->arrayBufferStructure(), buffer);
+
+ return JSValue::encode(result);
+}
+
EncodedJSValue JSC_HOST_CALL functionCheckSyntax(ExecState* exec)
{
String fileName = exec->argument(0).toString(exec)->value(exec);

Просмотреть файл

@ -15,7 +15,10 @@ class Puller(object):
if self.sameRepo():
return
shutil.rmtree(self.folder)
try:
shutil.rmtree(self.folder)
except:
pass
self.clone()
assert self.sameRepo()
@ -59,7 +62,17 @@ class SVN(Puller):
Run(['svn', 'co', self.repo, self.folder])
def sameRepo(self):
return False
with FolderChanger(self.path()):
try:
output = Run(['svn', 'info'])
except:
return False
print self.repo
print output
if "URL: "+self.repo in output:
return True
exit()
return False
def update(self, rev = None):
with FolderChanger(self.path()):
@ -72,7 +85,7 @@ class SVN(Puller):
raise Exception('unknown revision: ' + output)
return
def identify():
def identify(self):
with FolderChanger(self.path()):
output = Run(['svn', 'info'])
m = re.search("Revision: ([0-9]+)", output)
@ -121,7 +134,7 @@ class V8GIT(GIT):
#TODO: not needed?
#with FolderChanger(self.path()):
# Run(['git', 'checkout', 'master'])
def path(self):
return os.path.join(self.folder, "v8")
@ -131,8 +144,9 @@ class V8GIT(GIT):
def update(self, rev = None):
assert rev == None
Run(['git', 'pull', 'origin', 'master'])
with FolderChanger(self.folder):
Run(['git', 'pull', 'origin', 'master'])
env = os.environ.copy()
with FolderChanger(self.folder):
@ -149,7 +163,7 @@ def getPuller(repo, path):
if "svn." in repo:
return SVN(repo, path)
if repo.endswith(".git"):
return GIT(repo, path)
return GIT(repo, path)
if repo == "v8":
return V8GIT(repo, path)

Просмотреть файл

@ -12,17 +12,44 @@ import utils
class Submitter(object):
def __init__(self):
self.urls = utils.config.get('main', 'updateURL').split(",")
self.runIds = []
for i in range(len(self.urls)):
self.urls[i] = self.urls[i].strip()
self.runIds.append(0)
def setModeRules(self, rules):
self.rules = {}
for rule in rules:
rule = rule.split(":")
self.rules[rule[0]] = rule[1]
def setSession(self, session):
self.runIds = session
def mode(self, engine_type, config):
name = engine_type + "," + config
if name in self.rules:
return self.rules[engine_type + "," + config]
else:
return name
def assertMachine(self):
if not hasattr(self, "machine"):
print "please provide the machine number for submitting (--submitter-machine)"
exit()
def setMachine(self, machine):
self.machine = machine
class RemoteSubmitter(Submitter):
def mode(self, engine_type, config):
return self.rules[engine_type + "," + config]
class RemoteSubmitter(Submitter):
def start(self, timestamp=None):
self.assertMachine()
for i in range(len(self.urls)):
try:
url = self.urls[i]
@ -157,3 +184,33 @@ def getSubmitter(name):
return PrintSubmitter()
else:
raise Exception('unknown submitter!')
if __name__ == "__main__":
from optparse import OptionParser
parser = OptionParser(usage="usage: %prog [options]")
parser.add_option("-f", "--finish", action="store_true", dest="finish", default=False)
parser.add_option("-s", "--session", dest="session", type="string")
parser.add_option("-c", "--create", action="store_true", dest="create", default=False)
parser.add_option("-m", "--machine", dest="machine", type="int",
help="Give the machine number to submit to.")
(options, args) = parser.parse_args()
utils.config.init("awfy.config")
if options.create:
submitter = RemoteSubmitter()
submitter.setMachine(options.machine)
submitter.start()
print json.dumps(submitter.runIds)
elif options.finish:
fp = open(options.session, "r")
session = json.load(fp)
submitter = RemoteSubmitter()
submitter.setSession(session)
submitter.finish()
import os
os.remove(options.session)

Просмотреть файл

@ -33,7 +33,7 @@ class ConfigState:
self.inited = True
self.RepoPath = self.get('main', 'repos')
self.BenchmarkPath = self.get('benchmarks', 'dir')
self.BenchmarkPath = self.getDefault('benchmarks', 'dir', os.path.join(os.getcwd(), "..", "benchmarks"))
self.DriverPath = self.getDefault('main', 'driver', os.getcwd())
self.Timeout = self.getDefault('main', 'timeout', str(15*60))
self.Timeout = eval(self.Timeout, {}, {}) # silly hack to allow 30*60 in the config file.
@ -154,6 +154,7 @@ def RunTimedCheckOutput(args, env = os.environ.copy(), timeout = None, **popenar
if timeout is None:
timeout = config.Timeout
print('Running: "'+ '" "'.join(args) + '" with timeout: ' + str(timeout)+'s')
print("with: " + str(env))
p = subprocess.Popen(args, env = env, stdout=subprocess.PIPE, **popenargs)
with Handler(signal.SIGALRM, timeout_handler):
try: