git reimport
This commit is contained in:
110
dcj/tool/README
Normal file
110
dcj/tool/README
Normal file
@@ -0,0 +1,110 @@
|
||||
INSTALLATION
|
||||
|
||||
Unpack the archive in a place convienient for you. If you downloaded 'minimal'
|
||||
version build parunner (see [RE]BUILDING parunner) and create config.json file
|
||||
(in the directory to which you unpacked the archive) (see CONFIGURATION).
|
||||
|
||||
USAGE
|
||||
See python dcj.py -h
|
||||
|
||||
[RE]BUILDING parunner
|
||||
* Make sure go is installed (installation instructions are on
|
||||
https://golang.org/doc/install).
|
||||
* Build parunner by going to src/parunner/ subdirectory and running
|
||||
|
||||
go build
|
||||
|
||||
* Copy executable (parunner file) to executable/ subdirectory:
|
||||
|
||||
cp parunner ../../executable/
|
||||
|
||||
CONFIGURATION
|
||||
Configuration is stored in config.json file, located in the directory to which
|
||||
you unpacked the archive. Some sample configs are provided in sample-configs/
|
||||
subdirectory.
|
||||
|
||||
The configuration file should contain a single object, with the following
|
||||
fields:
|
||||
* c-compiler - command that will be used to compile files written in C.
|
||||
* c-compiler-flags - list of flags with which files written in C will be
|
||||
compiled.
|
||||
* cpp-compiler - command that will be used to compile files written in C++.
|
||||
* cpp-compiler-flags - list of flags with which files written in C++ will be
|
||||
compiled.
|
||||
* java-compiler - command used to compile .java files.
|
||||
* java-compiler-classpath-arg - argument of java-compiler which specifies Java
|
||||
class path to be used by the compiler.
|
||||
* java-include-dirs - list of directories containing includes necessary for
|
||||
compilation .c files with implementation of Java libraries.
|
||||
* java-native-library-linker - command used to link implementation of Java
|
||||
library.
|
||||
* java-native-library-linker-options - list of options passed to
|
||||
java-native-library-linker.
|
||||
* java-native-library-name - name of file containing native implementation of
|
||||
message class for Java.
|
||||
* java-wrapper-file-content - script that will be used to run your solution.
|
||||
Use {0} as a placeholder for directory containing .class file of your
|
||||
solution and {1} as a placeholder for directory containing libraries used by
|
||||
your solution.
|
||||
* parunner-file - name of parunner executable.
|
||||
You may figure proper values by building and running a simple program using
|
||||
message library manually (see BUILDING AND RUNNING SOLUTIONS MANUALLY).
|
||||
|
||||
BUILDING AND RUNNING SOLUTIONS MANUALLY
|
||||
* If you are using Java or Python:
|
||||
* Install SWIG (http://swig.org).
|
||||
* Generate wrappers for language of your choice.
|
||||
* For Java:
|
||||
|
||||
swig -java src/swig/message.i
|
||||
|
||||
* For Python:
|
||||
|
||||
swig -python src/swig/message.i
|
||||
|
||||
* Build message library from
|
||||
|
||||
libraries/message_internal.c
|
||||
libraries/zeus_local.c
|
||||
|
||||
and files generated by SWIG (see http://www.swig.org/tutorial.html for
|
||||
reference).
|
||||
* Build solution.
|
||||
* Run the solution using pa runner:
|
||||
|
||||
parunner path-to-built-soluton -n=number-of-simulated-hosts
|
||||
|
||||
INSTALLING MinGW TO WORK WITH THE TOOL
|
||||
* Install MinGW, following instructions on:
|
||||
|
||||
http://www.mingw.org/wiki/Getting_Started
|
||||
|
||||
make sure the following packages will be installed:
|
||||
* mingw32-binutils
|
||||
* mingw32-gcc
|
||||
* mingw32-gcc-g++
|
||||
* mingw32-libz
|
||||
* msys-base
|
||||
* msys-console
|
||||
* msys-zlib
|
||||
* Install python from:
|
||||
|
||||
https://www.python.org/downloads/release/python-2710/
|
||||
|
||||
* Add the following line (replacing /c/python27 if you didn't use default
|
||||
installation directory for Python):
|
||||
|
||||
export PATH="$PATH:/c/python27"
|
||||
|
||||
to file (replacing your-user-name)(replace C:\MinGW if you did not use
|
||||
default MinGW instalation directory):
|
||||
|
||||
c:\MinGW\msys\1.0\home\your-user-name\.bashrc
|
||||
|
||||
* Open terminal by running:
|
||||
|
||||
C:\MinGW\msys\1.0\msys.bat
|
||||
|
||||
* Use the tool:
|
||||
|
||||
python path/to/dcj.py
|
27
dcj/tool/config.json
Normal file
27
dcj/tool/config.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"c-compiler": "gcc",
|
||||
"c-compiler-flags": [
|
||||
"-O2",
|
||||
"-lm"
|
||||
],
|
||||
"cpp-compiler": "g++",
|
||||
"cpp-compiler-flags": [
|
||||
"-O2",
|
||||
"-std=gnu++0x",
|
||||
"-lm"
|
||||
],
|
||||
"java-compiler": "javac",
|
||||
"java-compiler-classpath-arg": "-classpath",
|
||||
"java-include-dirs": [
|
||||
"/System//Library/Frameworks/JavaVM.framework/Versions/A/Headers"
|
||||
],
|
||||
"java-native-library-linker": "cc",
|
||||
"java-native-library-linker-options": [
|
||||
"-framework",
|
||||
"JavaVM",
|
||||
"-bundle"
|
||||
],
|
||||
"java-native-library-name": "libmessage.jnilib",
|
||||
"java-wrapper-file-content": "#!/bin/bash\ncd {0}\n/usr/bin/java -Djava.library.path={1} -classpath {1} Wrapper\n",
|
||||
"parunner-file": "parunner"
|
||||
}
|
73
dcj/tool/dcj.py
Normal file
73
dcj/tool/dcj.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""CLI for local testing of solutions in Distributed Code Jam."""
|
||||
import argparse
|
||||
import os
|
||||
from os import chmod
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from dcj import build
|
||||
from dcj import command_chooser
|
||||
from dcj import configuration
|
||||
from dcj import run
|
||||
from dcj import test
|
||||
|
||||
|
||||
def _print(x):
|
||||
print ' '.join(x)
|
||||
return 0 # Tell tool that command execution was succesfull.
|
||||
|
||||
|
||||
def _subprocess_call_with_error_catching(command):
|
||||
try:
|
||||
subprocess.call(command)
|
||||
return 0
|
||||
except OSError as e:
|
||||
if e.args == (2, 'No such file or directory'):
|
||||
raise ValueError('Command {0} not found.'.format(command[0]))
|
||||
else:
|
||||
raise ValueError(
|
||||
'Error when executing command {0!r}: {1!r}.'.format(command, e))
|
||||
|
||||
|
||||
def _create_script(script_path, content):
|
||||
with open(script_path, 'w') as f:
|
||||
f.write(content)
|
||||
chmod(script_path, stat.S_IRWXU | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='dcj')
|
||||
config = configuration.Configuration()
|
||||
# TODO(jbartosik): allow using different configs.
|
||||
config.Load(
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.json'))
|
||||
builder = build.Build(config)
|
||||
runner = run.Run(config)
|
||||
tester = test.Tester(builder, runner)
|
||||
chooser = command_chooser.CommandChooser({
|
||||
'build': builder,
|
||||
'run': runner,
|
||||
'test': tester,
|
||||
})
|
||||
chooser.AddToParser(parser)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Only print commands, don\'t execute them.',
|
||||
default=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dry_run:
|
||||
builder.SetCommandExecutor(_print)
|
||||
runner.SetCommandExecutor(_print)
|
||||
else:
|
||||
builder.SetCommandExecutor(_subprocess_call_with_error_catching)
|
||||
builder.SetScriptCreator(_create_script)
|
||||
runner.SetCommandExecutor(_subprocess_call_with_error_catching)
|
||||
|
||||
try:
|
||||
chooser.Run(args)
|
||||
except (NotImplementedError, RuntimeError, ValueError) as error:
|
||||
print error
|
||||
sys.exit(1)
|
6
dcj/tool/dcj.sh
Executable file
6
dcj/tool/dcj.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIRECTORY=$( cd "$( dirname $0 )" && pwd )
|
||||
PYTHON="/usr/bin/python2.7"
|
||||
|
||||
$PYTHON $DIRECTORY/dcj.py $@
|
1
dcj/tool/dcj/__init__.py
Normal file
1
dcj/tool/dcj/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""CLI for DCJ."""
|
BIN
dcj/tool/dcj/__init__.pyc
Normal file
BIN
dcj/tool/dcj/__init__.pyc
Normal file
Binary file not shown.
344
dcj/tool/dcj/build.py
Normal file
344
dcj/tool/dcj/build.py
Normal file
@@ -0,0 +1,344 @@
|
||||
"""Class for building DCJ test executables."""
|
||||
from os import path
|
||||
|
||||
|
||||
class Build(object):
|
||||
"""A class for building DCJ test executables."""
|
||||
|
||||
def __init__(self, config):
|
||||
self._command_executor = lambda x: None
|
||||
self._script_creator = lambda x, y: None
|
||||
self._config = config
|
||||
|
||||
def SetCommandExecutor(self, command_executor):
|
||||
self._command_executor = command_executor
|
||||
|
||||
def SetScriptCreator(self, script_creator):
|
||||
self._script_creator = script_creator
|
||||
|
||||
def AddToParser(self, parser):
|
||||
"""Adds flags to parser and returns it."""
|
||||
parser.add_argument('--source', required=True,
|
||||
help='source file of the solution.')
|
||||
parser.add_argument('--language',
|
||||
help='language of the solution. Valid choices are: '
|
||||
'{0}. If the flag is not provided language will be '
|
||||
'deduced from source file extensions.'
|
||||
.format(
|
||||
', '.join(self._SUPPORTED_LANGUAGE_TO_EXTENSION))
|
||||
)
|
||||
parser.add_argument('--library',
|
||||
help='source file of library generating input.')
|
||||
parser.add_argument('--executable',
|
||||
help='path of the executable to be built. By default '
|
||||
'it\'s the same as path of source with removed '
|
||||
'filename extension.')
|
||||
parser.add_argument('--extra_flags',
|
||||
help='comma-separated list of additional flags to pass '
|
||||
'to compiler. For '
|
||||
'example --extra_flags="-Wall,-Wextra".')
|
||||
return parser
|
||||
|
||||
def Run(self, args):
|
||||
# TODO(jabrtosik): When running python tell user / check if file meets
|
||||
# necessary conditions:
|
||||
# * Is executable.
|
||||
# * First line is #!/path/to/interpreter.
|
||||
self._ValidateArgs(args)
|
||||
source_extension = self._SourceExtension(args)
|
||||
commands_builder = self._BUILDER_FOR_EXTENSION[source_extension]
|
||||
for command in commands_builder(self, args):
|
||||
if self._command_executor(command) != 0:
|
||||
raise RuntimeError('Build failed.')
|
||||
|
||||
def Description(self):
|
||||
return 'Builds solution for local testing.'
|
||||
|
||||
def _ValidateArgs(self, args):
|
||||
"""Validate arguments.
|
||||
|
||||
Args:
|
||||
args: arguments to be validated.
|
||||
|
||||
Raises:
|
||||
ValueError: exception with string describing the problem detected.
|
||||
"""
|
||||
if 'language' in args and args['language']:
|
||||
if args['language'] not in self._SUPPORTED_LANGUAGE_TO_EXTENSION:
|
||||
raise ValueError('--language must be one of {0!r} but it was {1!r}.'
|
||||
.format(self._SUPPORTED_LANGUAGE_TO_EXTENSION.keys(),
|
||||
args['language']))
|
||||
else:
|
||||
# Skip file extension validations.
|
||||
return
|
||||
|
||||
source_extension = self._SourceExtension(args)
|
||||
|
||||
if source_extension not in self._BUILDER_FOR_EXTENSION:
|
||||
raise ValueError('Source extension must be one of {0!r} but it was {1!r}.'
|
||||
.format(self._BUILDER_FOR_EXTENSION.keys(),
|
||||
source_extension))
|
||||
|
||||
if self._HasLibrary(args):
|
||||
library_extension = self._LibraryExtension(args)
|
||||
|
||||
if source_extension == '.c':
|
||||
if library_extension != '.c' and library_extension != '.h':
|
||||
raise ValueError('C solutions should have a .h or .c library')
|
||||
elif source_extension == '.cc' or source_extension == '.cpp':
|
||||
if (library_extension != '.cc' and library_extension != '.cpp' and
|
||||
library_extension != '.c' and library_extension != '.h'):
|
||||
raise ValueError('C++ solutions should have a .cc/.cpp or .h library')
|
||||
elif source_extension == '.py':
|
||||
if library_extension != '.py':
|
||||
raise ValueError('Python solutions should have a .py library')
|
||||
elif source_extension == '.java':
|
||||
if library_extension != '.java':
|
||||
raise ValueError('Java solutions should have a .java library')
|
||||
|
||||
def _CBuildCommands(self, args):
|
||||
"""Prepare commands to build solution written in C.
|
||||
|
||||
Args:
|
||||
args: arguments of the build.
|
||||
|
||||
Returns:
|
||||
tuple in which each item is a tuple with command that will execute a step
|
||||
of building solution.
|
||||
"""
|
||||
compiler = self._config.GetStringConfigValue('c-compiler')
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
include_dir = path.join(dcj_root, 'includes')
|
||||
local_zeus_path = path.join(dcj_root, 'libraries', 'zeus_local.c')
|
||||
message_path = path.join(dcj_root, 'libraries', 'message_internal.c')
|
||||
# TODO(jbartosik): support compilers that don't have -I flag for include
|
||||
# dirs.
|
||||
compiler_args = (
|
||||
self._config.GetStringListConfigValue('c-compiler-flags') + [
|
||||
'-I' + include_dir,
|
||||
local_zeus_path, message_path,
|
||||
]
|
||||
)
|
||||
if self._HasLibrary(args):
|
||||
compiler_args += [args['library']]
|
||||
build_solution_command = (
|
||||
(compiler,) + tuple(compiler_args) + self.ExtraFlags(args) +
|
||||
(args['source'], '-o', self.ExecutablePath(args),)
|
||||
)
|
||||
|
||||
return (build_solution_command,)
|
||||
|
||||
def _CcBuildCommands(self, args):
|
||||
"""Prepare commands to build solution written in C++.
|
||||
|
||||
Args:
|
||||
args: arguments of the build.
|
||||
|
||||
Returns:
|
||||
tuple in which each item is a tuple with command that will execute a step
|
||||
of building solution.
|
||||
"""
|
||||
# TODO(jbartosik): support other compilers.
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
include_dir = path.join(dcj_root, 'includes')
|
||||
c_compiler_with_flags = tuple(
|
||||
[self._config.GetStringConfigValue('c-compiler')] +
|
||||
self._config.GetStringListConfigValue('c-compiler-flags') +
|
||||
['-I' + include_dir]
|
||||
)
|
||||
cpp_compiler_with_flags = tuple(
|
||||
[self._config.GetStringConfigValue('cpp-compiler')] +
|
||||
self._config.GetStringListConfigValue('cpp-compiler-flags') +
|
||||
['-I' + include_dir]
|
||||
)
|
||||
c_sources = (path.join(dcj_root, 'libraries', 'zeus_local.c'),
|
||||
path.join(dcj_root, 'libraries', 'message_internal.c'),
|
||||
)
|
||||
c_object_files_build_commands = tuple(
|
||||
c_compiler_with_flags +
|
||||
(source_file, '-c', '-o', path.splitext(source_file)[0] + '.o')
|
||||
for source_file in c_sources
|
||||
)
|
||||
object_files = tuple(
|
||||
path.splitext(source_file)[0] + '.o' for source_file in c_sources
|
||||
)
|
||||
|
||||
files = [args['source']]
|
||||
if self._HasLibrary(args):
|
||||
files += [args['library']]
|
||||
|
||||
build_solution_command = (
|
||||
cpp_compiler_with_flags + self.ExtraFlags(args) + object_files +
|
||||
tuple(files) + ('-o', self.ExecutablePath(args),)
|
||||
)
|
||||
|
||||
return c_object_files_build_commands + (build_solution_command,)
|
||||
|
||||
def _BuildPythonObjectFileCommand(self, c_source, output):
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
return (
|
||||
'gcc', '-c', '-fpic',
|
||||
# TODO(jbartosik): Don't rely on users having this exact version of
|
||||
# python.
|
||||
'-I/usr/include/python2.7',
|
||||
'-I' + path.join(dcj_root, 'includes'),
|
||||
path.join(dcj_root, 'libraries', c_source),
|
||||
'-o', path.join(dcj_root, 'libraries', output),
|
||||
)
|
||||
|
||||
def _BuildJavaObjectFileCommand(self, c_source, output):
|
||||
"""Return command building .c file to work with Java via SWIG."""
|
||||
compiler = self._config.GetStringConfigValue('c-compiler')
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
include_dir = path.join(dcj_root, 'includes')
|
||||
# TODO(jbartosik): support compilers that don't have -I flag for include
|
||||
# dirs.
|
||||
compiler_args = (
|
||||
'-c', '-fpic',
|
||||
'-I' + include_dir,
|
||||
)
|
||||
java_include_options = tuple(
|
||||
[
|
||||
'-I' + directory
|
||||
for directory in
|
||||
self._config.GetDirListConfigValue('java-include-dirs')
|
||||
]
|
||||
)
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
return (compiler,) + compiler_args + java_include_options + (
|
||||
path.join(dcj_root, 'libraries', c_source),
|
||||
'-o', path.join(dcj_root, 'libraries', output),
|
||||
)
|
||||
|
||||
def _PyBuildCommands(self, unused_args):
|
||||
"""Returns tuple with commands for building Python solutions."""
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
|
||||
# TODO(jbartosik): use another directory to store object files.
|
||||
build_object_file_commands = tuple(
|
||||
self._BuildPythonObjectFileCommand(item + '.c', item + '.o')
|
||||
for item in self._MESSAGE_SO_PYTHON_INGREDIENTS)
|
||||
|
||||
object_files = tuple(
|
||||
path.join(dcj_root, 'libraries', item + '.o')
|
||||
for item in self._MESSAGE_SO_PYTHON_INGREDIENTS
|
||||
)
|
||||
|
||||
link_object_files = (
|
||||
('ld', '-shared',) +
|
||||
object_files +
|
||||
('-o', path.join(dcj_root, 'libraries', '_message.so',))
|
||||
)
|
||||
return build_object_file_commands + (link_object_files,)
|
||||
|
||||
def _JavaBuildCommands(self, args):
|
||||
"""Prepare commands to build solution written in Java.
|
||||
|
||||
Args:
|
||||
args: arguments of the build.
|
||||
|
||||
Returns:
|
||||
tuple in which each item is a tuple with command that will execute a step
|
||||
of building solution.
|
||||
"""
|
||||
# Prepare a script that will run java solution. This step is needed because
|
||||
# parunner works only with single executable files.
|
||||
solution_class_dir = path.dirname(path.realpath(args['source']))
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
message_dir = path.join(dcj_root, 'libraries')
|
||||
library_dir = path.dirname(path.realpath(args['library']))
|
||||
classpath = ':'.join((message_dir, library_dir,))
|
||||
self._script_creator(
|
||||
self.ExecutablePath(args),
|
||||
self._config.GetStringConfigValue('java-wrapper-file-content').format(
|
||||
solution_class_dir,
|
||||
classpath,
|
||||
)
|
||||
)
|
||||
|
||||
# Build message_.o and libmessage.so
|
||||
# TODO(jbartosik): deduplicate with Python
|
||||
build_object_file_commands = tuple(
|
||||
self._BuildJavaObjectFileCommand(item + '.c', item + '.o')
|
||||
for item in self._MESSAGE_SO_JAVA_INGREDIENTS)
|
||||
|
||||
object_files = [
|
||||
path.join(dcj_root, 'libraries', item + '.o')
|
||||
for item in self._MESSAGE_SO_JAVA_INGREDIENTS]
|
||||
|
||||
link_object_files = tuple(
|
||||
[self._config.GetStringConfigValue('java-native-library-linker')] +
|
||||
self._config.GetStringListConfigValue(
|
||||
'java-native-library-linker-options') +
|
||||
object_files +
|
||||
['-o', path.join(
|
||||
dcj_root, 'libraries',
|
||||
self._config.GetStringConfigValue('java-native-library-name'),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Create a class file to be ran.
|
||||
build_class_file_command = (
|
||||
self._config.GetStringConfigValue('java-compiler'),
|
||||
path.join(dcj_root, 'libraries', 'Wrapper.java'),
|
||||
args['source'],
|
||||
path.join(dcj_root, 'libraries', 'message.java'),
|
||||
path.join(dcj_root, 'libraries', 'messageJNI.java'),
|
||||
self._config.GetStringConfigValue('java-compiler-classpath-arg'),
|
||||
classpath,
|
||||
)
|
||||
return (
|
||||
build_object_file_commands +
|
||||
(link_object_files, build_class_file_command,)
|
||||
)
|
||||
|
||||
def _SourceExtension(self, args):
|
||||
if 'language' in args and args['language']:
|
||||
return self._SUPPORTED_LANGUAGE_TO_EXTENSION[args['language']]
|
||||
return path.splitext(args['source'])[1]
|
||||
|
||||
def _LibraryExtension(self, args):
|
||||
return path.splitext(args['library'])[1]
|
||||
|
||||
def ExtraFlags(self, args):
|
||||
if 'extra_flags' in args and args['extra_flags']:
|
||||
return tuple(args['extra_flags'].split(','))
|
||||
return ()
|
||||
|
||||
def ExecutablePath(self, args):
|
||||
if args['executable']:
|
||||
return args['executable']
|
||||
if self._SourceExtension(args) == '.py':
|
||||
return args['source']
|
||||
return path.splitext(args['source'])[0]
|
||||
|
||||
def _HasLibrary(self, args):
|
||||
return 'library' in args and args['library'] is not None
|
||||
|
||||
_BUILDER_FOR_EXTENSION = {
|
||||
'.c': _CBuildCommands,
|
||||
'.cc': _CcBuildCommands,
|
||||
'.cpp': _CcBuildCommands,
|
||||
'.java': _JavaBuildCommands,
|
||||
'.py': _PyBuildCommands,
|
||||
}
|
||||
|
||||
_MESSAGE_SO_PYTHON_INGREDIENTS = (
|
||||
'message_internal',
|
||||
'message_wrap_python',
|
||||
'zeus_local',
|
||||
)
|
||||
|
||||
_MESSAGE_SO_JAVA_INGREDIENTS = (
|
||||
'message_internal',
|
||||
'message_wrap_java',
|
||||
'zeus_local',
|
||||
)
|
||||
|
||||
_SUPPORTED_LANGUAGE_TO_EXTENSION = {
|
||||
'C': '.c',
|
||||
'C++': '.cc',
|
||||
'Java': '.java',
|
||||
'Python': '.py',
|
||||
}
|
BIN
dcj/tool/dcj/build.pyc
Normal file
BIN
dcj/tool/dcj/build.pyc
Normal file
Binary file not shown.
41
dcj/tool/dcj/command_chooser.py
Normal file
41
dcj/tool/dcj/command_chooser.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Class for choosing cli commands."""
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
class CommandChooser(object):
|
||||
"""Chooses command to run based on commandline arguments."""
|
||||
|
||||
def __init__(self, commands_dict):
|
||||
"""Initialize CommandChooser.
|
||||
|
||||
Args:
|
||||
commands_dict: dict from command name to object responsible for executing
|
||||
the command. The object should provide two methods:
|
||||
* AddToParser(parser) returning parser to parse arguments passed to the
|
||||
command.
|
||||
* Decription() returning string that will describe the command when
|
||||
using --help flag.
|
||||
* Run(args) runing the commands with given args.
|
||||
"""
|
||||
self.commands_dict = commands_dict
|
||||
|
||||
def AddToParser(self, parser):
|
||||
"""Returns parser that should be used to parse arguments for Run().
|
||||
|
||||
Args:
|
||||
parser: parse to which commands will be added.
|
||||
"""
|
||||
subparsers = parser.add_subparsers(title='Command to perform')
|
||||
for (command, executor) in sorted(self.commands_dict.iteritems()):
|
||||
parser_command = subparsers.add_parser(
|
||||
command,
|
||||
help=executor.Description(),
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
conflict_handler='resolve',
|
||||
)
|
||||
executor.AddToParser(parser_command)
|
||||
parser_command.set_defaults(func=executor.Run)
|
||||
|
||||
def Run(self, args):
|
||||
args.func(vars(args))
|
BIN
dcj/tool/dcj/command_chooser.pyc
Normal file
BIN
dcj/tool/dcj/command_chooser.pyc
Normal file
Binary file not shown.
89
dcj/tool/dcj/configuration.py
Normal file
89
dcj/tool/dcj/configuration.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Class for getting configuration and nicely reporting problems (if any)."""
|
||||
|
||||
import json
|
||||
from os import path
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
"""Class for getting configuration and nicely reporting problems (if any)."""
|
||||
|
||||
def __init__(self):
|
||||
self._parsed_config = {}
|
||||
self._config_path = ''
|
||||
|
||||
def Load(self, config_path):
|
||||
"""Load configuration from specified file.
|
||||
|
||||
The file should be readable, it should be a properly formated JSON and it
|
||||
should contain a single Object.
|
||||
|
||||
Args:
|
||||
config_path: path to file containg configuration.
|
||||
|
||||
Raises:
|
||||
RuntimeError: containig details of the problem.
|
||||
"""
|
||||
try:
|
||||
config = open(config_path, 'r')
|
||||
except IOError as e:
|
||||
raise RuntimeError('Opening configuration file {0} failed with: {1!r}'
|
||||
.format(config_path, e))
|
||||
|
||||
try:
|
||||
parsed_config = json.load(config)
|
||||
except ValueError as e:
|
||||
raise RuntimeError(
|
||||
'Couldn\'t parse configuration file(as JSON): {0!r}'.format(e))
|
||||
|
||||
if not isinstance(parsed_config, dict):
|
||||
raise RuntimeError(
|
||||
'Config file {0} parsed successfully as JSON. Expected content was a '
|
||||
'single Object, found: {1!r}.'.format(config_path, parsed_config))
|
||||
self._parsed_config = parsed_config
|
||||
self._config_path = config_path
|
||||
|
||||
def _RaiseConfigurationFileError(self, key, extra_message):
|
||||
raise RuntimeError('Error in configuration file {0} in key {1}: {2}'
|
||||
.format(self._config_path, key, extra_message))
|
||||
|
||||
def _GetRawConfigValue(self, key):
|
||||
if unicode(key) not in self._parsed_config:
|
||||
self._RaiseConfigurationFileError(key, 'key not found')
|
||||
return self._parsed_config[unicode(key)]
|
||||
|
||||
def GetStringConfigValue(self, key):
|
||||
value = self._GetRawConfigValue(key)
|
||||
if not isinstance(value, (str, unicode,)):
|
||||
self._RaiseConfigurationFileError(
|
||||
key, 'expected value to be a string but it is {0!r}.'.format(value))
|
||||
return value
|
||||
|
||||
def GetStringListConfigValue(self, key):
|
||||
"""Returns value for the key if it exists and is a list of strings."""
|
||||
value = self._GetRawConfigValue(key)
|
||||
if not isinstance(value, (list)):
|
||||
self._RaiseConfigurationFileError(
|
||||
key, 'expected value to be a list but it is {0!r}.'.format(value))
|
||||
for item in value:
|
||||
if not isinstance(item, (str, unicode,)):
|
||||
self._RaiseConfigurationFileError(
|
||||
key, 'expected all items of the list to be strings but one of them '
|
||||
'is {0!r}.'.format(item))
|
||||
return value
|
||||
|
||||
def GetExistingFilePath(self, key):
|
||||
value = self.GetStringConfigValue(key)
|
||||
if not path.isfile(value):
|
||||
self._RaiseConfigurationFileError(
|
||||
key, 'expected value to point to an existing file, file {1!r} does '
|
||||
'not exist. '.format())
|
||||
return value
|
||||
|
||||
def GetDirListConfigValue(self, key):
|
||||
value = self.GetStringListConfigValue(key)
|
||||
for item in value:
|
||||
if not path.isdir(item):
|
||||
self._RaiseConfigurationFileError(
|
||||
key, 'expected value to point to an existing directory, directory '
|
||||
'{0!r} does not exist.'.format(item))
|
||||
return value
|
BIN
dcj/tool/dcj/configuration.pyc
Normal file
BIN
dcj/tool/dcj/configuration.pyc
Normal file
Binary file not shown.
73
dcj/tool/dcj/dcj.py
Normal file
73
dcj/tool/dcj/dcj.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""CLI for local testing of solutions in Distributed Code Jam."""
|
||||
import argparse
|
||||
import os
|
||||
from os import chmod
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from dcj import build
|
||||
from dcj import command_chooser
|
||||
from dcj import configuration
|
||||
from dcj import run
|
||||
from dcj import test
|
||||
|
||||
|
||||
def _print(x):
|
||||
print ' '.join(x)
|
||||
return 0 # Tell tool that command execution was succesfull.
|
||||
|
||||
|
||||
def _subprocess_call_with_error_catching(command):
|
||||
try:
|
||||
subprocess.call(command)
|
||||
return 0
|
||||
except OSError as e:
|
||||
if e.args == (2, 'No such file or directory'):
|
||||
raise ValueError('Command {0} not found.'.format(command[0]))
|
||||
else:
|
||||
raise ValueError(
|
||||
'Error when executing command {0!r}: {1!r}.'.format(command, e))
|
||||
|
||||
|
||||
def _create_script(script_path, content):
|
||||
with open(script_path, 'w') as f:
|
||||
f.write(content)
|
||||
chmod(script_path, stat.S_IRWXU | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='dcj')
|
||||
config = configuration.Configuration()
|
||||
# TODO(jbartosik): allow using different configs.
|
||||
config.Load(
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.json'))
|
||||
builder = build.Build(config)
|
||||
runner = run.Run(config)
|
||||
tester = test.Tester(builder, runner)
|
||||
chooser = command_chooser.CommandChooser({
|
||||
'build': builder,
|
||||
'run': runner,
|
||||
'test': tester,
|
||||
})
|
||||
chooser.AddToParser(parser)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Only print commands, don\'t execute them.',
|
||||
default=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dry_run:
|
||||
builder.SetCommandExecutor(_print)
|
||||
runner.SetCommandExecutor(_print)
|
||||
else:
|
||||
builder.SetCommandExecutor(_subprocess_call_with_error_catching)
|
||||
builder.SetScriptCreator(_create_script)
|
||||
runner.SetCommandExecutor(_subprocess_call_with_error_catching)
|
||||
|
||||
try:
|
||||
chooser.Run(args)
|
||||
except (NotImplementedError, RuntimeError, ValueError) as error:
|
||||
print error
|
||||
sys.exit(1)
|
87
dcj/tool/dcj/run.py
Normal file
87
dcj/tool/dcj/run.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Class for running DCJ test executables."""
|
||||
import os
|
||||
|
||||
from os import path
|
||||
|
||||
# First item in the array is default.
|
||||
_OUTPUT_MODES = ['tagged', 'all', 'files']
|
||||
|
||||
|
||||
class Run(object):
|
||||
"""A class for running DCJ test executables.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self._command_executor = lambda x: None
|
||||
self._config = config
|
||||
|
||||
def _AppendToEnvPath(self, path_variable, value_to_append):
|
||||
if path_variable not in os.environ:
|
||||
os.environ[path_variable] = ''
|
||||
os.environ[path_variable] = (
|
||||
':'.join((os.environ[path_variable], value_to_append)))
|
||||
|
||||
def SetCommandExecutor(self, command_executor):
|
||||
self._command_executor = command_executor
|
||||
|
||||
def AddToParser(self, parser):
|
||||
"""Adds flags to parser and returns it."""
|
||||
parser.add_argument('--executable', help='path of executable to run.')
|
||||
parser.add_argument('--nodes', required=True, type=int,
|
||||
help='number of nodes that will run the solution.')
|
||||
output = parser.add_argument(
|
||||
'--output',
|
||||
default=_OUTPUT_MODES[0]
|
||||
)
|
||||
# TOOD(jbartosik): Add line breaks.
|
||||
output.help = """Mode of output. Allowed values are:
|
||||
*tagged*::: This is default. In this mode each row of stdout from each
|
||||
instance of the solution will be prepended with:
|
||||
STDOUT ${ROW_NUMBER}:
|
||||
and sent to stdout of the command. Stderr will be treated similarily.
|
||||
*all*::: In this mode stdout and stderr from all machines will be sent to
|
||||
(respectively) stdout and stderr of the command.
|
||||
*files*::: In this mode each row of stdout from each instance of the
|
||||
solution will written to file
|
||||
${EXECUTABLE_FILE_NAME}.stdout.${MACHINE_NUMBER}
|
||||
Stderr will be treated similarily.
|
||||
"""
|
||||
return parser
|
||||
|
||||
def Run(self, args):
|
||||
"""Actually run the required executable."""
|
||||
self._ValidateArgs(args)
|
||||
parunner = path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), '..', 'executable',
|
||||
self._config.GetStringConfigValue('parunner-file'))
|
||||
dcj_root = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||
# TODO(onufry): This modifies the user's env directly, it would be better to
|
||||
# just pass a modified map of env vars over to the command executor.
|
||||
self._AppendToEnvPath('PYTHONPATH', path.join(dcj_root, 'libraries'))
|
||||
self._AppendToEnvPath('PYTHONPATH', path.join(dcj_root, 'modules'))
|
||||
self._AppendToEnvPath('PATH', '.')
|
||||
os.environ['LD_LIBRARY_PATH'] = path.join(dcj_root, 'libraries')
|
||||
if 0 != self._command_executor((parunner,
|
||||
'--n', str(args['nodes']),
|
||||
'--stdout', args['output'],
|
||||
'--stderr', args['output'],
|
||||
args['executable'])):
|
||||
raise RuntimeError('Run failed.')
|
||||
|
||||
def Description(self):
|
||||
return 'Runs previously built solution locally.'
|
||||
|
||||
def _ValidateArgs(self, args):
|
||||
"""Validate arguments.
|
||||
|
||||
Args:
|
||||
args: arguments to be validated.
|
||||
|
||||
Raises:
|
||||
ValueError: exception with string describing the problem detected.
|
||||
"""
|
||||
if args['nodes'] <= 0:
|
||||
raise ValueError('argument --nodes must be positive.')
|
||||
if args['output'] not in _OUTPUT_MODES:
|
||||
raise ValueError(
|
||||
'argument --output must be one of ' + ', '.join(_OUTPUT_MODES))
|
BIN
dcj/tool/dcj/run.pyc
Normal file
BIN
dcj/tool/dcj/run.pyc
Normal file
Binary file not shown.
27
dcj/tool/dcj/test.py
Normal file
27
dcj/tool/dcj/test.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Class for building & running DCJ test executables."""
|
||||
|
||||
|
||||
class Tester(object):
|
||||
"""A class for building & running DCJ test executables.
|
||||
"""
|
||||
|
||||
def __init__(self, builder, runner):
|
||||
self.builder = builder
|
||||
self.runner = runner
|
||||
|
||||
def AddToParser(self, parser):
|
||||
self.builder.AddToParser(parser)
|
||||
self.runner.AddToParser(parser)
|
||||
parser.add_argument('--executable',
|
||||
help='path of the executable to be built. By default '
|
||||
'it\'s the same as path of source with removed '
|
||||
'filename extension.')
|
||||
return parser
|
||||
|
||||
def Run(self, args):
|
||||
self.builder.Run(args)
|
||||
args['executable'] = self.builder.ExecutablePath(args)
|
||||
self.runner.Run(args)
|
||||
|
||||
def Description(self):
|
||||
return 'Builds and locally runs a solution.'
|
BIN
dcj/tool/dcj/test.pyc
Normal file
BIN
dcj/tool/dcj/test.pyc
Normal file
Binary file not shown.
BIN
dcj/tool/dcj_mac_os.tar.bz
Normal file
BIN
dcj/tool/dcj_mac_os.tar.bz
Normal file
Binary file not shown.
BIN
dcj/tool/executable/parunner
Executable file
BIN
dcj/tool/executable/parunner
Executable file
Binary file not shown.
68
dcj/tool/includes/message.h
Normal file
68
dcj/tool/includes/message.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// The contestant-available API for the Zeus distributed contest.
|
||||
#ifndef MESSAGE_H_ // NOLINT
|
||||
#define MESSAGE_H_ // NOLINT
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// The number of nodes on which the solution is running.
|
||||
int NumberOfNodes();
|
||||
|
||||
// The number (in the range [0 .. NumberOfNodes()-1]) of the node on which this
|
||||
// process is running.
|
||||
int MyNodeId();
|
||||
|
||||
// In all the methods below, if "target" or "source" is not in the valid range,
|
||||
// the behaviour is undefined.
|
||||
|
||||
// The library internally has a message buffer for each of the nodes in
|
||||
// [0 .. NumberOfNodes()-1]. It accumulates the message in such a buffer through
|
||||
// the "Put" methods.
|
||||
|
||||
// Append "value" to the message that is being prepared for the node with id
|
||||
// "target".
|
||||
void PutChar(int target, char value);
|
||||
void PutInt(int target, int value);
|
||||
void PutLL(int target, long long value); // NOLINT
|
||||
|
||||
// Sends the message that was accumulated in the appropriate buffer to the
|
||||
// "target" instance, and clear the buffer for this instance.
|
||||
//
|
||||
// This method is non-blocking - that is, it does not wait for the receiver to
|
||||
// call "Receive", it returns immediately after sending the message.
|
||||
void Send(int target);
|
||||
|
||||
// The library also has a receiving buffer for each instance. When you call
|
||||
// "Receive" and retrieve a message from an instance, the buffer tied to this
|
||||
// instance is overwritten. You can then retrieve individual parts of the
|
||||
// message through the Get* methods. You must retrieve the contents of the
|
||||
// message in the order in which they were appended.
|
||||
//
|
||||
// This method is blocking - if there is no message to receive, it will wait for
|
||||
// the message to arrive.
|
||||
//
|
||||
// You can call Receive(-1) to retrieve a message from any source, or with with
|
||||
// source in [0 .. NumberOfNodes()-1] to retrieve a message from a particular
|
||||
// source.
|
||||
//
|
||||
// It returns the number of the instance which sent the message (which is equal
|
||||
// to source, unless source is -1).
|
||||
int Receive(int source);
|
||||
|
||||
// Each of these methods returns and consumes one item from the buffer of the
|
||||
// appropriate instance. You must call these methods in the order in which the
|
||||
// elements were appended to the message (so, for instance, if the message was
|
||||
// created with PutChar, PutChar, PutLL, you must call GetChar, GetChar, GetLL
|
||||
// in this order).
|
||||
// If you call them in different order, or you call a Get* method after
|
||||
// consuming all the contents of the buffer, behaviour is undefined.
|
||||
char GetChar(int source);
|
||||
int GetInt(int source);
|
||||
long long GetLL(int source); // NOLINT
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MESSAGE_H_ // NOLINT
|
72
dcj/tool/includes/zeus.h
Normal file
72
dcj/tool/includes/zeus.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2014, Onufry Wojtaszczuk <onufryw@gmail.com>
|
||||
#ifndef RECRUITING_DISTRIBUTED_API_ZEUS_H_
|
||||
#define RECRUITING_DISTRIBUTED_API_ZEUS_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ZEUS(s) zeus_##s
|
||||
|
||||
// The basic contestant-available API for the Zeus distributed contest.
|
||||
|
||||
// The number of nodes on which the solution is running.
|
||||
int ZEUS(NumberOfNodes)();
|
||||
|
||||
// The number (in the range [0 .. NumberOfNodes()-1]) of the node on which this
|
||||
// process is running.
|
||||
typedef int ZEUS(NodeId);
|
||||
ZEUS(NodeId) ZEUS(MyNodeId());
|
||||
|
||||
// It is guaranteed that messages sent between two given nodes will arrive in
|
||||
// order.
|
||||
// No ordering guarantees are given between messages to different nodes (in
|
||||
// particular, if A sends a message to B, then to C, and C sends a message to B
|
||||
// after receiving the message from A, it is possible for B to receive C's
|
||||
// message first).
|
||||
|
||||
// Send |bytes| bytes of |message| to node |target|. This is asynchronous (that
|
||||
// is, it does not wait for the receiver to Receive the message).
|
||||
// If |message| is shorter than |bytes|, behaviour is undefined.
|
||||
// If |target| is not a valid node id (not in [0 .. NumberOfNodes()-1]), will
|
||||
// crash.
|
||||
void ZEUS(Send)(ZEUS(NodeId) target, const char *message, int bytes);
|
||||
|
||||
typedef struct {
|
||||
// Id of the sending node.
|
||||
ZEUS(NodeId) sender_id;
|
||||
// Length of the received message in bytes.
|
||||
int length;
|
||||
} ZEUS(MessageInfo);
|
||||
|
||||
// Receive a message from the |source| node id, and copy the contents of the
|
||||
// message to |buffer|, up to |buffer_size| bytes. No extra characters (in
|
||||
// particular, a trailing '\0') will be appended to the message.
|
||||
// A special case is |source| equal to -1, in which case a message from any
|
||||
// other node can be received.
|
||||
// This call is blocking - that is, the function will not return until a message
|
||||
// is received.
|
||||
// A std::pair will be returned.
|
||||
// First element of the pair will be the id of the sending node.
|
||||
// Second element of the pair will be the length of the received message
|
||||
// in bytes.
|
||||
//
|
||||
// If the received message is larger than |buffer_size|, will crash.
|
||||
// If the received message is smaller than |buffer_size|, the rest of the buffer
|
||||
// contents are not modified.
|
||||
// If |buffer| is smaller than |buffer_size|, behaviour is undefined.
|
||||
// If |source| is neither -1 nor a valid node ID, will crash.
|
||||
ZEUS(MessageInfo) ZEUS(Receive)(ZEUS(NodeId) source, char *buffer, int buffer_size);
|
||||
|
||||
// Returns the list of nodes from which we have unreceived messages (thus,
|
||||
// calling Receive() with one of the returned node ids as the argument will
|
||||
// not block). The order in which the node IDs are given is not specified. Each
|
||||
// ID will be given once.
|
||||
//std::vector<NodeId> Poll(); // NOTE: NOT IMPLEMENTED
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RECRUITING_DISTRIBUTED_API_ZEUS_H_
|
BIN
dcj/tool/libraries/Wrapper.class
Normal file
BIN
dcj/tool/libraries/Wrapper.class
Normal file
Binary file not shown.
6
dcj/tool/libraries/Wrapper.java
Normal file
6
dcj/tool/libraries/Wrapper.java
Normal file
@@ -0,0 +1,6 @@
|
||||
class Wrapper {
|
||||
public static void main(String[] args) {
|
||||
System.loadLibrary("message");
|
||||
Main.main(args);
|
||||
}
|
||||
}
|
BIN
dcj/tool/libraries/libmessage.jnilib
Executable file
BIN
dcj/tool/libraries/libmessage.jnilib
Executable file
Binary file not shown.
BIN
dcj/tool/libraries/message.class
Normal file
BIN
dcj/tool/libraries/message.class
Normal file
Binary file not shown.
51
dcj/tool/libraries/message.java
Normal file
51
dcj/tool/libraries/message.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
* This file was automatically generated by SWIG (http://www.swig.org).
|
||||
* Version 2.0.11
|
||||
*
|
||||
* Do not make changes to this file unless you know what you are doing--modify
|
||||
* the SWIG interface file instead.
|
||||
* ----------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
public class message {
|
||||
public static int NumberOfNodes() {
|
||||
return messageJNI.NumberOfNodes();
|
||||
}
|
||||
|
||||
public static int MyNodeId() {
|
||||
return messageJNI.MyNodeId();
|
||||
}
|
||||
|
||||
public static void PutChar(int target, char value) {
|
||||
messageJNI.PutChar(target, value);
|
||||
}
|
||||
|
||||
public static void PutInt(int target, int value) {
|
||||
messageJNI.PutInt(target, value);
|
||||
}
|
||||
|
||||
public static void PutLL(int target, long value) {
|
||||
messageJNI.PutLL(target, value);
|
||||
}
|
||||
|
||||
public static void Send(int target) {
|
||||
messageJNI.Send(target);
|
||||
}
|
||||
|
||||
public static int Receive(int source) {
|
||||
return messageJNI.Receive(source);
|
||||
}
|
||||
|
||||
public static char GetChar(int source) {
|
||||
return messageJNI.GetChar(source);
|
||||
}
|
||||
|
||||
public static int GetInt(int source) {
|
||||
return messageJNI.GetInt(source);
|
||||
}
|
||||
|
||||
public static long GetLL(int source) {
|
||||
return messageJNI.GetLL(source);
|
||||
}
|
||||
|
||||
}
|
BIN
dcj/tool/libraries/messageJNI.class
Normal file
BIN
dcj/tool/libraries/messageJNI.class
Normal file
Binary file not shown.
21
dcj/tool/libraries/messageJNI.java
Normal file
21
dcj/tool/libraries/messageJNI.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
* This file was automatically generated by SWIG (http://www.swig.org).
|
||||
* Version 2.0.11
|
||||
*
|
||||
* Do not make changes to this file unless you know what you are doing--modify
|
||||
* the SWIG interface file instead.
|
||||
* ----------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
public class messageJNI {
|
||||
public final static native int NumberOfNodes();
|
||||
public final static native int MyNodeId();
|
||||
public final static native void PutChar(int jarg1, char jarg2);
|
||||
public final static native void PutInt(int jarg1, int jarg2);
|
||||
public final static native void PutLL(int jarg1, long jarg2);
|
||||
public final static native void Send(int jarg1);
|
||||
public final static native int Receive(int jarg1);
|
||||
public final static native char GetChar(int jarg1);
|
||||
public final static native int GetInt(int jarg1);
|
||||
public final static native long GetLL(int jarg1);
|
||||
}
|
158
dcj/tool/libraries/message_internal.c
Normal file
158
dcj/tool/libraries/message_internal.c
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "message.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "zeus.h"
|
||||
|
||||
#define ZEUS_WRAP(s) zeus_##s
|
||||
|
||||
#define DEBUG 1
|
||||
#define MAX_MESSAGE_SIZE (8 * (1 << 20))
|
||||
#define MAX_MACHINES 100
|
||||
|
||||
static void Die(const char* s) {
|
||||
fputs(s, stderr);
|
||||
exit(20);
|
||||
}
|
||||
|
||||
int NumberOfNodes() { return ZEUS_WRAP(NumberOfNodes)(); }
|
||||
|
||||
int MyNodeId() { return ZEUS_WRAP(MyNodeId)(); }
|
||||
|
||||
static void CheckNodeId(int node) {
|
||||
if (!DEBUG) return;
|
||||
if (node < 0 || node >= NumberOfNodes()) Die("Incorrect machine number");
|
||||
}
|
||||
|
||||
typedef struct Buffer {
|
||||
char* buffer;
|
||||
int size;
|
||||
int pos; // for input buffers next byte to be read. for output buffers next
|
||||
// byte to be written.
|
||||
} Buffer;
|
||||
|
||||
static int Empty(Buffer* buffer) { return buffer->pos >= buffer->size; }
|
||||
|
||||
static unsigned char GetRawByte(Buffer* buf) {
|
||||
if (Empty(buf)) {
|
||||
Die("Read past the end of the message");
|
||||
}
|
||||
char r = buf->buffer[buf->pos++];
|
||||
if (Empty(buf)) {
|
||||
free(buf->buffer);
|
||||
buf->buffer = NULL;
|
||||
buf->pos = 0;
|
||||
buf->size = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void PutRawByte(Buffer* buffer, unsigned char byte) {
|
||||
if (buffer->pos >= buffer->size) {
|
||||
buffer->size = 2 * buffer->size;
|
||||
if (buffer->size < 128) buffer->size = 128;
|
||||
buffer->buffer = (char*)realloc(buffer->buffer, buffer->size);
|
||||
assert(buffer->buffer);
|
||||
}
|
||||
buffer->buffer[buffer->pos++] = byte;
|
||||
}
|
||||
|
||||
static Buffer incoming_buffers[MAX_MACHINES];
|
||||
static Buffer outgoing_buffers[MAX_MACHINES];
|
||||
|
||||
char recv_buffer[MAX_MESSAGE_SIZE];
|
||||
|
||||
int Receive(int source) {
|
||||
if (source != -1) CheckNodeId(source);
|
||||
if (DEBUG && source == -1) {
|
||||
int i;
|
||||
for (i = 0; i < ZEUS_WRAP(NumberOfNodes)(); i++) {
|
||||
if (!Empty(&incoming_buffers[i]))
|
||||
Die("Cannot call Receive(-1) if any message is unread.");
|
||||
}
|
||||
}
|
||||
ZEUS_WRAP(MessageInfo) mi =
|
||||
ZEUS_WRAP(Receive)(source, recv_buffer, sizeof(recv_buffer));
|
||||
Buffer* buf = &incoming_buffers[mi.sender_id];
|
||||
if (DEBUG && !Empty(buf))
|
||||
Die("Receive()'ed a message when the previous one wasn't consumed.");
|
||||
if (buf->buffer != NULL) {
|
||||
free(buf->buffer);
|
||||
buf->buffer = NULL;
|
||||
}
|
||||
buf->buffer = (char*)malloc(mi.length);
|
||||
assert(buf->buffer);
|
||||
memcpy(buf->buffer, recv_buffer, mi.length);
|
||||
buf->pos = 0;
|
||||
buf->size = mi.length;
|
||||
return mi.sender_id;
|
||||
}
|
||||
|
||||
#define kChar 14
|
||||
#define kInt 15
|
||||
#define kLL 16
|
||||
|
||||
static void GetTag(int source, int expected) {
|
||||
if (!DEBUG) return;
|
||||
int tag = GetRawByte(&incoming_buffers[source]);
|
||||
if (tag != expected) Die("Type mismatch between read and requested types");
|
||||
}
|
||||
|
||||
int GetInt(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kInt);
|
||||
int result = 0, i;
|
||||
for (i = 0; i < 4; i++)
|
||||
result |= (int)(GetRawByte(&incoming_buffers[source])) << (8 * i);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutInt(int target, int value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG) PutRawByte(&outgoing_buffers[target], kInt);
|
||||
int i;
|
||||
for (i = 0; i < 4; i++)
|
||||
PutRawByte(&outgoing_buffers[target], (0xff & (value >> (8 * i))));
|
||||
}
|
||||
|
||||
long long GetLL(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kLL);
|
||||
long long result = 0;
|
||||
int i;
|
||||
for (i = 0; i < 8; i++)
|
||||
result |= (long long)(GetRawByte(&incoming_buffers[source])) << (8 * i);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutLL(int target, long long value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG) PutRawByte(&outgoing_buffers[target], kLL);
|
||||
int i;
|
||||
for (i = 0; i < 8; i++)
|
||||
PutRawByte(&outgoing_buffers[target], (0xff & (value >> (8 * i))));
|
||||
}
|
||||
|
||||
char GetChar(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kChar);
|
||||
return GetRawByte(&incoming_buffers[source]);
|
||||
}
|
||||
|
||||
void PutChar(int target, char value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG) PutRawByte(&outgoing_buffers[target], kChar);
|
||||
PutRawByte(&outgoing_buffers[target], value);
|
||||
}
|
||||
|
||||
void Send(int target) {
|
||||
CheckNodeId(target);
|
||||
Buffer* buffer = &outgoing_buffers[target];
|
||||
if (buffer->pos > (int)sizeof(recv_buffer)) Die("Message too long");
|
||||
ZEUS_WRAP(Send)(target, buffer->buffer, buffer->pos);
|
||||
free(buffer->buffer);
|
||||
buffer->buffer = NULL;
|
||||
buffer->pos = buffer->size = 0;
|
||||
}
|
BIN
dcj/tool/libraries/message_internal.o
Normal file
BIN
dcj/tool/libraries/message_internal.o
Normal file
Binary file not shown.
335
dcj/tool/libraries/message_wrap_java.c
Normal file
335
dcj/tool/libraries/message_wrap_java.c
Normal file
@@ -0,0 +1,335 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
* This file was automatically generated by SWIG (http://www.swig.org).
|
||||
* Version 2.0.11
|
||||
*
|
||||
* This file is not intended to be easily readable and contains a number of
|
||||
* coding conventions designed to improve portability and efficiency. Do not make
|
||||
* changes to this file unless you know what you are doing--modify the SWIG
|
||||
* interface file instead.
|
||||
* ----------------------------------------------------------------------------- */
|
||||
|
||||
#define SWIGJAVA
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* This section contains generic SWIG labels for method/variable
|
||||
* declarations/attributes, and other compiler dependent labels.
|
||||
* ----------------------------------------------------------------------------- */
|
||||
|
||||
/* template workaround for compilers that cannot correctly implement the C++ standard */
|
||||
#ifndef SWIGTEMPLATEDISAMBIGUATOR
|
||||
# if defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x560)
|
||||
# define SWIGTEMPLATEDISAMBIGUATOR template
|
||||
# elif defined(__HP_aCC)
|
||||
/* Needed even with `aCC -AA' when `aCC -V' reports HP ANSI C++ B3910B A.03.55 */
|
||||
/* If we find a maximum version that requires this, the test would be __HP_aCC <= 35500 for A.03.55 */
|
||||
# define SWIGTEMPLATEDISAMBIGUATOR template
|
||||
# else
|
||||
# define SWIGTEMPLATEDISAMBIGUATOR
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* inline attribute */
|
||||
#ifndef SWIGINLINE
|
||||
# if defined(__cplusplus) || (defined(__GNUC__) && !defined(__STRICT_ANSI__))
|
||||
# define SWIGINLINE inline
|
||||
# else
|
||||
# define SWIGINLINE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* attribute recognised by some compilers to avoid 'unused' warnings */
|
||||
#ifndef SWIGUNUSED
|
||||
# if defined(__GNUC__)
|
||||
# if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
|
||||
# define SWIGUNUSED __attribute__ ((__unused__))
|
||||
# else
|
||||
# define SWIGUNUSED
|
||||
# endif
|
||||
# elif defined(__ICC)
|
||||
# define SWIGUNUSED __attribute__ ((__unused__))
|
||||
# else
|
||||
# define SWIGUNUSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef SWIG_MSC_UNSUPPRESS_4505
|
||||
# if defined(_MSC_VER)
|
||||
# pragma warning(disable : 4505) /* unreferenced local function has been removed */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef SWIGUNUSEDPARM
|
||||
# ifdef __cplusplus
|
||||
# define SWIGUNUSEDPARM(p)
|
||||
# else
|
||||
# define SWIGUNUSEDPARM(p) p SWIGUNUSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* internal SWIG method */
|
||||
#ifndef SWIGINTERN
|
||||
# define SWIGINTERN static SWIGUNUSED
|
||||
#endif
|
||||
|
||||
/* internal inline SWIG method */
|
||||
#ifndef SWIGINTERNINLINE
|
||||
# define SWIGINTERNINLINE SWIGINTERN SWIGINLINE
|
||||
#endif
|
||||
|
||||
/* exporting methods */
|
||||
#if (__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
|
||||
# ifndef GCC_HASCLASSVISIBILITY
|
||||
# define GCC_HASCLASSVISIBILITY
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef SWIGEXPORT
|
||||
# if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
|
||||
# if defined(STATIC_LINKED)
|
||||
# define SWIGEXPORT
|
||||
# else
|
||||
# define SWIGEXPORT __declspec(dllexport)
|
||||
# endif
|
||||
# else
|
||||
# if defined(__GNUC__) && defined(GCC_HASCLASSVISIBILITY)
|
||||
# define SWIGEXPORT __attribute__ ((visibility("default")))
|
||||
# else
|
||||
# define SWIGEXPORT
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* calling conventions for Windows */
|
||||
#ifndef SWIGSTDCALL
|
||||
# if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
|
||||
# define SWIGSTDCALL __stdcall
|
||||
# else
|
||||
# define SWIGSTDCALL
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Deal with Microsoft's attempt at deprecating C standard runtime functions */
|
||||
#if !defined(SWIG_NO_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) && !defined(_CRT_SECURE_NO_DEPRECATE)
|
||||
# define _CRT_SECURE_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
/* Deal with Microsoft's attempt at deprecating methods in the standard C++ library */
|
||||
#if !defined(SWIG_NO_SCL_SECURE_NO_DEPRECATE) && defined(_MSC_VER) && !defined(_SCL_SECURE_NO_DEPRECATE)
|
||||
# define _SCL_SECURE_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* Fix for jlong on some versions of gcc on Windows */
|
||||
#if defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
||||
typedef long long __int64;
|
||||
#endif
|
||||
|
||||
/* Fix for jlong on 64-bit x86 Solaris */
|
||||
#if defined(__x86_64)
|
||||
# ifdef _LP64
|
||||
# undef _LP64
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* Support for throwing Java exceptions */
|
||||
typedef enum {
|
||||
SWIG_JavaOutOfMemoryError = 1,
|
||||
SWIG_JavaIOException,
|
||||
SWIG_JavaRuntimeException,
|
||||
SWIG_JavaIndexOutOfBoundsException,
|
||||
SWIG_JavaArithmeticException,
|
||||
SWIG_JavaIllegalArgumentException,
|
||||
SWIG_JavaNullPointerException,
|
||||
SWIG_JavaDirectorPureVirtual,
|
||||
SWIG_JavaUnknownError
|
||||
} SWIG_JavaExceptionCodes;
|
||||
|
||||
typedef struct {
|
||||
SWIG_JavaExceptionCodes code;
|
||||
const char *java_exception;
|
||||
} SWIG_JavaExceptions_t;
|
||||
|
||||
|
||||
static void SWIGUNUSED SWIG_JavaThrowException(JNIEnv *jenv, SWIG_JavaExceptionCodes code, const char *msg) {
|
||||
jclass excep;
|
||||
static const SWIG_JavaExceptions_t java_exceptions[] = {
|
||||
{ SWIG_JavaOutOfMemoryError, "java/lang/OutOfMemoryError" },
|
||||
{ SWIG_JavaIOException, "java/io/IOException" },
|
||||
{ SWIG_JavaRuntimeException, "java/lang/RuntimeException" },
|
||||
{ SWIG_JavaIndexOutOfBoundsException, "java/lang/IndexOutOfBoundsException" },
|
||||
{ SWIG_JavaArithmeticException, "java/lang/ArithmeticException" },
|
||||
{ SWIG_JavaIllegalArgumentException, "java/lang/IllegalArgumentException" },
|
||||
{ SWIG_JavaNullPointerException, "java/lang/NullPointerException" },
|
||||
{ SWIG_JavaDirectorPureVirtual, "java/lang/RuntimeException" },
|
||||
{ SWIG_JavaUnknownError, "java/lang/UnknownError" },
|
||||
{ (SWIG_JavaExceptionCodes)0, "java/lang/UnknownError" }
|
||||
};
|
||||
const SWIG_JavaExceptions_t *except_ptr = java_exceptions;
|
||||
|
||||
while (except_ptr->code != code && except_ptr->code)
|
||||
except_ptr++;
|
||||
|
||||
(*jenv)->ExceptionClear(jenv);
|
||||
excep = (*jenv)->FindClass(jenv, except_ptr->java_exception);
|
||||
if (excep)
|
||||
(*jenv)->ThrowNew(jenv, excep, msg);
|
||||
}
|
||||
|
||||
|
||||
/* Contract support */
|
||||
|
||||
#define SWIG_contract_assert(nullreturn, expr, msg) if (!(expr)) {SWIG_JavaThrowException(jenv, SWIG_JavaIllegalArgumentException, msg); return nullreturn; } else
|
||||
|
||||
|
||||
extern int NumberOfNodes();
|
||||
extern int MyNodeId();
|
||||
extern void PutChar(int target, char value);
|
||||
extern void PutInt(int target, int value);
|
||||
extern void PutLL(int target, long long value);
|
||||
extern void Send(int target);
|
||||
extern int Receive(int source);
|
||||
extern char GetChar(int source);
|
||||
extern int GetInt(int source);
|
||||
extern long long GetLL(int source);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
SWIGEXPORT jint JNICALL Java_messageJNI_NumberOfNodes(JNIEnv *jenv, jclass jcls) {
|
||||
jint jresult = 0 ;
|
||||
int result;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
result = (int)NumberOfNodes();
|
||||
jresult = (jint)result;
|
||||
return jresult;
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT jint JNICALL Java_messageJNI_MyNodeId(JNIEnv *jenv, jclass jcls) {
|
||||
jint jresult = 0 ;
|
||||
int result;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
result = (int)MyNodeId();
|
||||
jresult = (jint)result;
|
||||
return jresult;
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT void JNICALL Java_messageJNI_PutChar(JNIEnv *jenv, jclass jcls, jint jarg1, jchar jarg2) {
|
||||
int arg1 ;
|
||||
char arg2 ;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
arg2 = (char)jarg2;
|
||||
PutChar(arg1,arg2);
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT void JNICALL Java_messageJNI_PutInt(JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2) {
|
||||
int arg1 ;
|
||||
int arg2 ;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
arg2 = (int)jarg2;
|
||||
PutInt(arg1,arg2);
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT void JNICALL Java_messageJNI_PutLL(JNIEnv *jenv, jclass jcls, jint jarg1, jlong jarg2) {
|
||||
int arg1 ;
|
||||
long long arg2 ;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
arg2 = (long long)jarg2;
|
||||
PutLL(arg1,arg2);
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT void JNICALL Java_messageJNI_Send(JNIEnv *jenv, jclass jcls, jint jarg1) {
|
||||
int arg1 ;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
Send(arg1);
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT jint JNICALL Java_messageJNI_Receive(JNIEnv *jenv, jclass jcls, jint jarg1) {
|
||||
jint jresult = 0 ;
|
||||
int arg1 ;
|
||||
int result;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
result = (int)Receive(arg1);
|
||||
jresult = (jint)result;
|
||||
return jresult;
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT jchar JNICALL Java_messageJNI_GetChar(JNIEnv *jenv, jclass jcls, jint jarg1) {
|
||||
jchar jresult = 0 ;
|
||||
int arg1 ;
|
||||
char result;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
result = (char)GetChar(arg1);
|
||||
jresult = (jchar)result;
|
||||
return jresult;
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT jint JNICALL Java_messageJNI_GetInt(JNIEnv *jenv, jclass jcls, jint jarg1) {
|
||||
jint jresult = 0 ;
|
||||
int arg1 ;
|
||||
int result;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
result = (int)GetInt(arg1);
|
||||
jresult = (jint)result;
|
||||
return jresult;
|
||||
}
|
||||
|
||||
|
||||
SWIGEXPORT jlong JNICALL Java_messageJNI_GetLL(JNIEnv *jenv, jclass jcls, jint jarg1) {
|
||||
jlong jresult = 0 ;
|
||||
int arg1 ;
|
||||
long long result;
|
||||
|
||||
(void)jenv;
|
||||
(void)jcls;
|
||||
arg1 = (int)jarg1;
|
||||
result = (long long)GetLL(arg1);
|
||||
jresult = (jlong)result;
|
||||
return jresult;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
BIN
dcj/tool/libraries/message_wrap_java.o
Normal file
BIN
dcj/tool/libraries/message_wrap_java.o
Normal file
Binary file not shown.
4264
dcj/tool/libraries/message_wrap_python.c
Normal file
4264
dcj/tool/libraries/message_wrap_python.c
Normal file
File diff suppressed because it is too large
Load Diff
147
dcj/tool/libraries/zeus_local.c
Normal file
147
dcj/tool/libraries/zeus_local.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "zeus.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define MAX_MESSAGE_SIZE (8*1024*1024)
|
||||
#define MAGIC 1736434764
|
||||
#define SEND 3
|
||||
#define RECV 4
|
||||
|
||||
static int initialized;
|
||||
static FILE* cmdin;
|
||||
static FILE* cmdout;
|
||||
static int nof_nodes;
|
||||
static int node_id;
|
||||
|
||||
static unsigned char ReadByte() {
|
||||
unsigned char c;
|
||||
assert(fread(&c, 1, 1, cmdin) == 1);
|
||||
return c;
|
||||
}
|
||||
|
||||
static int ReadInt() {
|
||||
int v = 0;
|
||||
int i;
|
||||
for(i=0;i<4;i++)
|
||||
v |= (int)(ReadByte()) << (8 * i);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void WriteByte(unsigned char c) {
|
||||
assert(fwrite(&c, 1, 1, cmdout) == 1);
|
||||
}
|
||||
|
||||
static void WriteInt(int v) {
|
||||
int i;
|
||||
for(i=0;i<4;i++)
|
||||
WriteByte((v >> (8 * i)) & 0xff);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static int GetFd(int dir) {
|
||||
const char* names[2] = { "ZSHANDLE_IN", "ZSHANDLE_OUT" };
|
||||
char* handle_s = getenv(names[dir]);
|
||||
if (handle_s == NULL)
|
||||
return -1;
|
||||
int handle = atoi(handle_s);
|
||||
return _open_osfhandle(handle, dir == 0 ? _O_RDONLY : _O_APPEND);
|
||||
}
|
||||
#else
|
||||
static int GetFd(int dir) {
|
||||
return 3 + dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void Init() {
|
||||
if (initialized)
|
||||
return;
|
||||
cmdin = fdopen(GetFd(0), "r");
|
||||
assert(cmdin != NULL);
|
||||
cmdout = fdopen(GetFd(1), "w");
|
||||
assert(cmdout != NULL);
|
||||
if (ReadInt() != MAGIC)
|
||||
assert(0);
|
||||
nof_nodes = ReadInt();
|
||||
assert(1 <= nof_nodes);
|
||||
node_id = ReadInt();
|
||||
assert(0 <= node_id && node_id < nof_nodes);
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
int ZEUS(NumberOfNodes)() {
|
||||
Init();
|
||||
return nof_nodes;
|
||||
}
|
||||
|
||||
ZEUS(NodeId) ZEUS(MyNodeId)() {
|
||||
Init();
|
||||
return node_id;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static int CurrentTime() {
|
||||
HANDLE me = GetCurrentProcess();
|
||||
FILETIME lpCreationTime, lpExitTime, lpKernelTime, lpUserTime;
|
||||
GetProcessTimes(me, &lpCreationTime, &lpExitTime, &lpKernelTime, &lpUserTime);
|
||||
ULONGLONG cTime =
|
||||
lpUserTime.dwLowDateTime +
|
||||
lpKernelTime.dwLowDateTime +
|
||||
(((ULONGLONG) lpUserTime.dwHighDateTime) << 32) +
|
||||
(((ULONGLONG) lpKernelTime.dwHighDateTime) << 32);
|
||||
return (int)(cTime / 10000);
|
||||
}
|
||||
#else
|
||||
static int CurrentTime() {
|
||||
static int warned;
|
||||
int time = clock();
|
||||
if (time == -1) {
|
||||
if (!warned) {
|
||||
warned = 1;
|
||||
fprintf(stderr, "Warning: clock() returned -1; time measurements will be bogus.\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return time * 1000 / CLOCKS_PER_SEC;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ZEUS(Send)(ZEUS(NodeId) target, const char* message, int bytes) {
|
||||
Init();
|
||||
assert(target >= 0 && target < nof_nodes);
|
||||
assert(bytes <= MAX_MESSAGE_SIZE);
|
||||
int i;
|
||||
WriteByte(SEND);
|
||||
WriteInt(target);
|
||||
WriteInt(CurrentTime());
|
||||
WriteInt(bytes);
|
||||
for(i=0;i<bytes;i++)
|
||||
WriteByte(message[i]);
|
||||
fflush(cmdout);
|
||||
}
|
||||
|
||||
ZEUS(MessageInfo) ZEUS(Receive)(ZEUS(NodeId) source, char* buffer, int buffer_size) {
|
||||
Init();
|
||||
assert(source >= -1 && source < nof_nodes);
|
||||
ZEUS(MessageInfo) mi;
|
||||
int i;
|
||||
WriteByte(RECV);
|
||||
WriteInt(source);
|
||||
WriteInt(CurrentTime());
|
||||
fflush(cmdout);
|
||||
if (ReadInt() != MAGIC + 1)
|
||||
assert(0);
|
||||
mi.sender_id = ReadInt();
|
||||
mi.length = ReadInt();
|
||||
assert(mi.length <= buffer_size);
|
||||
for(i=0;i<mi.length;i++)
|
||||
buffer[i] = ReadByte();
|
||||
return mi;
|
||||
}
|
BIN
dcj/tool/libraries/zeus_local.o
Normal file
BIN
dcj/tool/libraries/zeus_local.o
Normal file
Binary file not shown.
114
dcj/tool/modules/message.py
Normal file
114
dcj/tool/modules/message.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# This file was automatically generated by SWIG (http://www.swig.org).
|
||||
# Version 2.0.11
|
||||
#
|
||||
# Do not make changes to this file unless you know what you are doing--modify
|
||||
# the SWIG interface file instead.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
from sys import version_info
|
||||
if version_info >= (2,6,0):
|
||||
def swig_import_helper():
|
||||
from os.path import dirname
|
||||
import imp
|
||||
fp = None
|
||||
try:
|
||||
fp, pathname, description = imp.find_module('_message', [dirname(__file__)])
|
||||
except ImportError:
|
||||
import _message
|
||||
return _message
|
||||
if fp is not None:
|
||||
try:
|
||||
_mod = imp.load_module('_message', fp, pathname, description)
|
||||
finally:
|
||||
fp.close()
|
||||
return _mod
|
||||
_message = swig_import_helper()
|
||||
del swig_import_helper
|
||||
else:
|
||||
import _message
|
||||
del version_info
|
||||
try:
|
||||
_swig_property = property
|
||||
except NameError:
|
||||
pass # Python < 2.2 doesn't have 'property'.
|
||||
def _swig_setattr_nondynamic(self,class_type,name,value,static=1):
|
||||
if (name == "thisown"): return self.this.own(value)
|
||||
if (name == "this"):
|
||||
if type(value).__name__ == 'SwigPyObject':
|
||||
self.__dict__[name] = value
|
||||
return
|
||||
method = class_type.__swig_setmethods__.get(name,None)
|
||||
if method: return method(self,value)
|
||||
if (not static):
|
||||
self.__dict__[name] = value
|
||||
else:
|
||||
raise AttributeError("You cannot add attributes to %s" % self)
|
||||
|
||||
def _swig_setattr(self,class_type,name,value):
|
||||
return _swig_setattr_nondynamic(self,class_type,name,value,0)
|
||||
|
||||
def _swig_getattr(self,class_type,name):
|
||||
if (name == "thisown"): return self.this.own()
|
||||
method = class_type.__swig_getmethods__.get(name,None)
|
||||
if method: return method(self)
|
||||
raise AttributeError(name)
|
||||
|
||||
def _swig_repr(self):
|
||||
try: strthis = "proxy of " + self.this.__repr__()
|
||||
except: strthis = ""
|
||||
return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,)
|
||||
|
||||
try:
|
||||
_object = object
|
||||
_newclass = 1
|
||||
except AttributeError:
|
||||
class _object : pass
|
||||
_newclass = 0
|
||||
|
||||
|
||||
|
||||
def NumberOfNodes():
|
||||
return _message.NumberOfNodes()
|
||||
NumberOfNodes = _message.NumberOfNodes
|
||||
|
||||
def MyNodeId():
|
||||
return _message.MyNodeId()
|
||||
MyNodeId = _message.MyNodeId
|
||||
|
||||
def PutChar(*args):
|
||||
return _message.PutChar(*args)
|
||||
PutChar = _message.PutChar
|
||||
|
||||
def PutInt(*args):
|
||||
return _message.PutInt(*args)
|
||||
PutInt = _message.PutInt
|
||||
|
||||
def PutLL(*args):
|
||||
return _message.PutLL(*args)
|
||||
PutLL = _message.PutLL
|
||||
|
||||
def Send(*args):
|
||||
return _message.Send(*args)
|
||||
Send = _message.Send
|
||||
|
||||
def Receive(*args):
|
||||
return _message.Receive(*args)
|
||||
Receive = _message.Receive
|
||||
|
||||
def GetChar(*args):
|
||||
return _message.GetChar(*args)
|
||||
GetChar = _message.GetChar
|
||||
|
||||
def GetInt(*args):
|
||||
return _message.GetInt(*args)
|
||||
GetInt = _message.GetInt
|
||||
|
||||
def GetLL(*args):
|
||||
return _message.GetLL(*args)
|
||||
GetLL = _message.GetLL
|
||||
# This file is compatible with both classic and new-style classes.
|
||||
|
||||
|
27
dcj/tool/sample-configs/linux-config.json
Normal file
27
dcj/tool/sample-configs/linux-config.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"c-compiler": "gcc",
|
||||
"c-compiler-flags": [
|
||||
"-O2",
|
||||
"-lm",
|
||||
"-static"
|
||||
],
|
||||
"cpp-compiler": "g++",
|
||||
"cpp-compiler-flags": [
|
||||
"-O2",
|
||||
"-std=gnu++0x",
|
||||
"-lm",
|
||||
"-static"
|
||||
],
|
||||
"java-compiler": "javac",
|
||||
"java-compiler-classpath-arg": "-classpath",
|
||||
"java-include-dirs": [
|
||||
"/usr/lib/jvm/java-7-openjdk-amd64/include"
|
||||
],
|
||||
"java-native-library-linker": "ld",
|
||||
"java-native-library-linker-options": [
|
||||
"-shared"
|
||||
],
|
||||
"java-native-library-name": "libmessage.so",
|
||||
"java-wrapper-file-content": "#!/bin/bash\ncd {0}\n/usr/bin/java -classpath {1} Wrapper\n",
|
||||
"parunner-file": "parunner"
|
||||
}
|
27
dcj/tool/sample-configs/mac-os-config.json
Normal file
27
dcj/tool/sample-configs/mac-os-config.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"c-compiler": "gcc",
|
||||
"c-compiler-flags": [
|
||||
"-O2",
|
||||
"-lm"
|
||||
],
|
||||
"cpp-compiler": "g++",
|
||||
"cpp-compiler-flags": [
|
||||
"-O2",
|
||||
"-std=gnu++0x",
|
||||
"-lm"
|
||||
],
|
||||
"java-compiler": "javac",
|
||||
"java-compiler-classpath-arg": "-classpath",
|
||||
"java-include-dirs": [
|
||||
"/System//Library/Frameworks/JavaVM.framework/Versions/A/Headers"
|
||||
],
|
||||
"java-native-library-linker": "cc",
|
||||
"java-native-library-linker-options": [
|
||||
"-framework",
|
||||
"JavaVM",
|
||||
"-bundle"
|
||||
],
|
||||
"java-native-library-name": "libmessage.jnilib",
|
||||
"java-wrapper-file-content": "#!/bin/bash\ncd {0}\n/usr/bin/java -Djava.library.path={1} -classpath {1} Wrapper\n",
|
||||
"parunner-file": "parunner"
|
||||
}
|
16
dcj/tool/sample-configs/x86_64-mingw-config.json
Normal file
16
dcj/tool/sample-configs/x86_64-mingw-config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"c-compiler": "gcc",
|
||||
"c-compiler-flags": [
|
||||
"-O2",
|
||||
"-lm",
|
||||
"-static"
|
||||
],
|
||||
"cpp-compiler": "g++",
|
||||
"cpp-compiler-flags": [
|
||||
"-O2",
|
||||
"-std=gnu++0x",
|
||||
"-lm",
|
||||
"-static"
|
||||
],
|
||||
"parunner-file": "parunner.exe"
|
||||
}
|
74
dcj/tool/src/parunner/BUILD
Normal file
74
dcj/tool/src/parunner/BUILD
Normal file
@@ -0,0 +1,74 @@
|
||||
# Description:
|
||||
# Auto-imported from github.com/robryk/parunner
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # BSD 3-clause
|
||||
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
go_binary(
|
||||
name = "parunner",
|
||||
srcs = [
|
||||
"binaries_test_unix.go",
|
||||
"binaries_test_windows.go",
|
||||
"comm.go",
|
||||
"filepipe.go",
|
||||
"instance.go",
|
||||
"instance_unix.go",
|
||||
"instance_windows.go",
|
||||
"instances.go",
|
||||
"main.go",
|
||||
"route.go",
|
||||
"util.go",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "filepipe_test",
|
||||
size = "small",
|
||||
srcs = ["filepipe_test.go"],
|
||||
library = ":parunner",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "instance_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"google_init_test.go",
|
||||
"instance_test.go",
|
||||
],
|
||||
data = [
|
||||
"//third_party/golang/parunner/zeus:hanger",
|
||||
"//third_party/golang/parunner/zeus:tester",
|
||||
],
|
||||
library = ":parunner",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "instances_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"google_init_test.go",
|
||||
"instances_test.go",
|
||||
],
|
||||
data = [
|
||||
"//third_party/golang/parunner/zeus:hanger",
|
||||
"//third_party/golang/parunner/zeus:tester",
|
||||
],
|
||||
library = ":parunner",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "route_test",
|
||||
size = "small",
|
||||
srcs = ["route_test.go"],
|
||||
library = ":parunner",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "util_test",
|
||||
size = "small",
|
||||
srcs = ["util_test.go"],
|
||||
library = ":parunner",
|
||||
)
|
12
dcj/tool/src/parunner/COPYING
Normal file
12
dcj/tool/src/parunner/COPYING
Normal file
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2014, Robert Obryk <robryk@gmail.com> and others
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
12
dcj/tool/src/parunner/LICENSE
Normal file
12
dcj/tool/src/parunner/LICENSE
Normal file
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2014, Robert Obryk <robryk@gmail.com> and others
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
dcj/tool/src/parunner/OWNERS
Normal file
3
dcj/tool/src/parunner/OWNERS
Normal file
@@ -0,0 +1,3 @@
|
||||
robryk
|
||||
jbartosik
|
||||
|
16
dcj/tool/src/parunner/README.google
Normal file
16
dcj/tool/src/parunner/README.google
Normal file
@@ -0,0 +1,16 @@
|
||||
URL: https://github.com/robryk/parunner/archive/32a202bb14a55b39ed8031836c5fa9f776744594.zip
|
||||
Version: 32a202bb14a55b39ed8031836c5fa9f776744594
|
||||
License: BSD 3-clause
|
||||
License File: LICENSE
|
||||
|
||||
Description:
|
||||
Utility that can run submissions to distributed programming contests ran on zeus
|
||||
(//recruiting/distributed/zeus) on a single machine.
|
||||
|
||||
Local Modifications:
|
||||
Automated import rewriting by //third_party/golang/update.go.
|
||||
Copied COPYING to LICENSE.
|
||||
Merged incorrectly split go_binary rules in BUILD.
|
||||
Added cc_{library,binary} BUILD rules in zeus/BUILD.
|
||||
Added google_init_test.go to the directory and to go_test rules.
|
||||
Added data dependencies to go_test rules.
|
40
dcj/tool/src/parunner/README.md
Normal file
40
dcj/tool/src/parunner/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
parunner
|
||||
========
|
||||
|
||||
Single-machine runner for [distributed](http://potyczki.mimuw.edu.pl/l/zadania_rozproszone/) [Potyczki Algorytmiczne](http://potyczki.mimuw.edu.pl/) problems ([a](https://sio2.mimuw.edu.pl/pa/c/pa-2014-1/p/mak/) [few](https://sio2.mimuw.edu.pl/pa/c/pa-2014-1/p/kol/) [examples](https://sio2.mimuw.edu.pl/pa/c/pa-2014-1/p/sek/)).
|
||||
|
||||
[](https://drone.io/github.com/robryk/parunner/latest) [](https://godoc.org/github.com/robryk/parunner)
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
In order to run a program that uses [raw zeus interface](https://github.com/robryk/parunner/blob/master/zeus/zeus.h), you need to link it with [zeus/zeus_local.c](https://github.com/robryk/parunner/blob/master/zeus/zeus_local.c) instead of any other implementation of zeus_local. You can then run the program as follows:
|
||||
|
||||
$ parunner -n=number_of_instances path/to/program
|
||||
|
||||
There is an [example](https://github.com/robryk/parunner/blob/master/zeus/example.c) provided. In order to run it, you should:
|
||||
|
||||
1. Compile it: `make -C zeus example`
|
||||
2. Obtain a binary of parunner. If you have a Go toolchain installed, you can compile it by doing `go get github.com/robryk/parunner`. The binary will then be built and written to `$GOPATH/bin/parunner`. There is also a compiled binary for [linux-amd64](https://drone.io/github.com/robryk/parunner/files/parunner) available.
|
||||
3. Run `parunner -n=3 -trace_comm -stdout=tagged zeus/example`. The output should look like this:
|
||||
```
|
||||
robryk@sharya-rana ~/g/s/g/r/parunner> parunner -n=3 -trace_comm -stdout=tagged zeus/example
|
||||
STDOUT 0: Nodeow jest 3, a ja mam numer 0.
|
||||
STDOUT 0: Wysylam wiadomosc do 1.
|
||||
STDOUT 1: Nodeow jest 3, a ja mam numer 1.
|
||||
STDOUT 1: Wysylam wiadomosc do 2.
|
||||
STDOUT 1: Odbieram wiadomosc od 0.
|
||||
STDOUT 2: Nodeow jest 3, a ja mam numer 2.
|
||||
STDOUT 2: Odbieram wiadomosc od 1.
|
||||
COMM: instancja 1:instancja 0 wysyła do mnie wiadomość (13 bajtów) [0]
|
||||
COMM: instancja 2:instancja 1 wysyła do mnie wiadomość (13 bajtów) [0]
|
||||
COMM: instancja 1:czekam na wiadomość od instancji 0 [0]
|
||||
COMM: instancja 1:odebrałam wiadomość od instancji 0 (13 bajtów)
|
||||
STDOUT 1: Odebralem: Hello from 0!
|
||||
COMM: instancja 2:czekam na wiadomość od instancji 1 [0]
|
||||
COMM: instancja 2:odebrałam wiadomość od instancji 1 (13 bajtów)
|
||||
STDOUT 2: Odebralem: Hello from 1!
|
||||
Czas trwania: 0 (najdłużej działająca instancja: 2)
|
||||
```
|
||||
|
||||
For more information on parunner's usage invoke it with no arguments.
|
6
dcj/tool/src/parunner/binaries_test_unix.go
Normal file
6
dcj/tool/src/parunner/binaries_test_unix.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
|
||||
const testerPath = "zeus/tester"
|
||||
const hangerPath = "zeus/hanger"
|
4
dcj/tool/src/parunner/binaries_test_windows.go
Normal file
4
dcj/tool/src/parunner/binaries_test_windows.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
const testerPath = "zeus\\tester.exe"
|
||||
const hangerPath = "zeus\\hanger.exe"
|
218
dcj/tool/src/parunner/comm.go
Normal file
218
dcj/tool/src/parunner/comm.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const magic = 1736434764
|
||||
const recvResponseMagic = magic + 1
|
||||
const sendOpType = 3
|
||||
const recvOpType = 4
|
||||
|
||||
type header struct {
|
||||
Magic uint32
|
||||
NodeCount int32
|
||||
NodeID int32
|
||||
}
|
||||
|
||||
type recvResponse struct {
|
||||
RecvResponseMagic uint32
|
||||
SourceID int32
|
||||
Length int32
|
||||
// Message []byte
|
||||
}
|
||||
|
||||
type recvHeader struct {
|
||||
// OpType byte
|
||||
SourceID int32
|
||||
Time int32 // milliseconds
|
||||
}
|
||||
|
||||
type sendHeader struct {
|
||||
// OpType byte
|
||||
TargetID int32
|
||||
Time int32 // milliseconds
|
||||
Length int32
|
||||
// Message []byte
|
||||
}
|
||||
|
||||
// TODO(robryk): Move this to instance-creation-time options
|
||||
var messageCountLimit = flag.Int("message_count_limit", 1000, "Limit for the number of messages sent per instance")
|
||||
var messageSizeLimit = flag.Int("message_size_limit", 8*1024*1024, "Limit for the total size of messages sent by an instance, in bytes")
|
||||
|
||||
type Message struct {
|
||||
Source int
|
||||
Target int
|
||||
SendTime time.Duration
|
||||
Message []byte
|
||||
}
|
||||
|
||||
// ErrMessageCount is returned when an instance exceeds the per-instance message count limit.
|
||||
// It is usually encapsulated in an InstanceError that specifies the instance ID.
|
||||
type ErrMessageCount struct {
|
||||
}
|
||||
|
||||
func (err ErrMessageCount) Error() string {
|
||||
return fmt.Sprintf("sent message count limit (%d) exceeded", *messageCountLimit)
|
||||
}
|
||||
|
||||
// ErrMessageSize is returned when an instance exceeds the per-instance total messages size limit.
|
||||
// It is usually encapsulated in an InstanceError that specifies the instance ID.
|
||||
type ErrMessageSize struct {
|
||||
}
|
||||
|
||||
func (err ErrMessageSize) Error() string {
|
||||
return fmt.Sprintf("total sent message size limit (%d bytes) exceeded", *messageSizeLimit)
|
||||
}
|
||||
|
||||
func writeMessage(w io.Writer, message *Message) error {
|
||||
rr := recvResponse{
|
||||
RecvResponseMagic: recvResponseMagic,
|
||||
SourceID: int32(message.Source),
|
||||
Length: int32(len(message.Message)),
|
||||
}
|
||||
if err := binary.Write(w, binary.LittleEndian, &rr); err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := w.Write(message.Message); n < len(message.Message) {
|
||||
if err == nil {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeHeader(w io.Writer, id int, instanceCount int) error {
|
||||
h := header{
|
||||
Magic: magic,
|
||||
NodeCount: int32(instanceCount),
|
||||
NodeID: int32(id),
|
||||
}
|
||||
return binary.Write(w, binary.LittleEndian, &h)
|
||||
}
|
||||
|
||||
const (
|
||||
requestSend = iota
|
||||
requestRecv
|
||||
requestRecvAny
|
||||
// requestNop
|
||||
)
|
||||
|
||||
type request struct {
|
||||
requestType int
|
||||
time time.Duration
|
||||
|
||||
// for requestSend:
|
||||
destination int
|
||||
message []byte
|
||||
|
||||
// for requestRecv:
|
||||
source int
|
||||
}
|
||||
|
||||
func (req request) hasResponse() bool {
|
||||
switch req.requestType {
|
||||
case requestRecv:
|
||||
return true
|
||||
case requestRecvAny:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type response struct {
|
||||
message *Message
|
||||
}
|
||||
|
||||
func readRequest(r io.Reader) (*request, error) {
|
||||
var opType [1]byte
|
||||
if _, err := r.Read(opType[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch opType[0] {
|
||||
case sendOpType:
|
||||
var sh sendHeader
|
||||
if err := binary.Read(r, binary.LittleEndian, &sh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sh.Length < 0 || int(sh.Length) > *messageSizeLimit {
|
||||
return nil, fmt.Errorf("invalid size of a message to be sent: %d", sh.Length)
|
||||
}
|
||||
if sh.TargetID < 0 || sh.TargetID >= MaxInstances {
|
||||
return nil, fmt.Errorf("invalid target instance in a send request: %d", sh.TargetID)
|
||||
}
|
||||
message := make([]byte, sh.Length)
|
||||
if _, err := io.ReadFull(r, message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &request{
|
||||
requestType: requestSend,
|
||||
time: time.Duration(sh.Time) * time.Millisecond,
|
||||
destination: int(sh.TargetID),
|
||||
message: message}, nil
|
||||
case recvOpType:
|
||||
var rh recvHeader
|
||||
if err := binary.Read(r, binary.LittleEndian, &rh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rh.SourceID < -1 || rh.SourceID >= MaxInstances {
|
||||
return nil, fmt.Errorf("invalid source instance in a receive request: %d", rh.SourceID)
|
||||
}
|
||||
if rh.SourceID == -1 {
|
||||
return &request{requestType: requestRecvAny, time: time.Duration(rh.Time) * time.Millisecond}, nil
|
||||
} else {
|
||||
return &request{requestType: requestRecv, time: time.Duration(rh.Time) * time.Millisecond, source: int(rh.SourceID)}, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation type 0x%x", opType[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instance) communicate(r io.Reader, w io.Writer, reqCh chan<- *request, respCh <-chan *response) error {
|
||||
i.TimeBlocked = time.Duration(0)
|
||||
// TODO: Figure out what errors should be returned from this function. We currently error if the instance fails to read the header (which is mitigated by delaying the closure of other ends of the pipes), for example.
|
||||
if err := writeHeader(w, i.ID, i.TotalInstances); err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
req, err := readRequest(r)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
//return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
req.time += i.TimeBlocked
|
||||
if req.requestType == requestSend {
|
||||
i.MessagesSent++
|
||||
if i.MessagesSent > *messageCountLimit {
|
||||
return ErrMessageCount{}
|
||||
}
|
||||
i.MessageBytesSent += len(req.message)
|
||||
if i.MessageBytesSent > *messageSizeLimit {
|
||||
return ErrMessageSize{}
|
||||
}
|
||||
}
|
||||
currentTime := req.time
|
||||
hasResponse := req.hasResponse()
|
||||
reqCh <- req
|
||||
if hasResponse {
|
||||
resp, ok := <-respCh
|
||||
if !ok {
|
||||
return fmt.Errorf("Received no response for a receive request")
|
||||
}
|
||||
if resp.message.SendTime > currentTime {
|
||||
i.TimeBlocked += resp.message.SendTime - currentTime
|
||||
}
|
||||
if err := writeMessage(w, resp.message); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
dcj/tool/src/parunner/filepipe.go
Normal file
109
dcj/tool/src/parunner/filepipe.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FilePipe is a tailable buffer backed by a file.
|
||||
type FilePipe struct {
|
||||
f *os.File
|
||||
|
||||
mu sync.Mutex
|
||||
cond sync.Cond
|
||||
size int64
|
||||
closing bool
|
||||
}
|
||||
|
||||
// Create a new FilePipe backed by a temporary file.
|
||||
func NewFilePipe() (*FilePipe, error) {
|
||||
// On some OSes we could remove the file immediately.
|
||||
f, err := ioutil.TempFile("", "filepipe")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fp := &FilePipe{f: f}
|
||||
fp.cond.L = &fp.mu
|
||||
runtime.SetFinalizer(fp, (*FilePipe).Release)
|
||||
return fp, nil
|
||||
}
|
||||
|
||||
// Release releases the resources associated with the filepipe. In particular,
|
||||
// it removes the backing file. No readers should be used concurrently with a
|
||||
// call to Release, nor after a call to Release. Release is idempotent.
|
||||
func (fp *FilePipe) Release() error {
|
||||
fp.Close()
|
||||
if fp.f == nil {
|
||||
return nil
|
||||
}
|
||||
filename := fp.f.Name()
|
||||
err := fp.f.Close()
|
||||
if err1 := os.Remove(filename); err == nil {
|
||||
err = err1
|
||||
}
|
||||
fp.f = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Reader creates a new reader that starts reading from the beginning of
|
||||
// the filepipe's contents and blocks at the end until the filepipe is closed.
|
||||
func (fp *FilePipe) Reader() io.Reader {
|
||||
return &filePipeReader{fp: fp, pos: 0}
|
||||
}
|
||||
|
||||
func (fp *FilePipe) Write(buf []byte) (int, error) {
|
||||
n, err := fp.f.Write(buf)
|
||||
fp.mu.Lock()
|
||||
defer fp.mu.Unlock()
|
||||
if fp.closing {
|
||||
return 0, errors.New("write to a closed filepipe")
|
||||
}
|
||||
fp.size += int64(n)
|
||||
fp.cond.Broadcast()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close finalizes the filepipe's contents. Once Close is called, all readers
|
||||
// that read up to the end of the contents will return io.EOF instead of waiting
|
||||
// for more data. Close is idempotent.
|
||||
func (fp *FilePipe) Close() error {
|
||||
fp.mu.Lock()
|
||||
fp.closing = true
|
||||
fp.cond.Broadcast()
|
||||
fp.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
type filePipeReader struct {
|
||||
fp *FilePipe
|
||||
pos int64
|
||||
}
|
||||
|
||||
func (fpr *filePipeReader) Read(buf []byte) (int, error) {
|
||||
if fpr.fp.f == nil {
|
||||
return 0, errors.New("filepipe already had its resources released")
|
||||
}
|
||||
for {
|
||||
n, err := fpr.fp.f.ReadAt(buf, fpr.pos)
|
||||
fpr.pos += int64(n)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
if err != nil || n > 0 {
|
||||
return n, err
|
||||
}
|
||||
fpr.fp.mu.Lock()
|
||||
for fpr.pos >= fpr.fp.size && !fpr.fp.closing {
|
||||
fpr.fp.cond.Wait()
|
||||
}
|
||||
eof := fpr.pos >= fpr.fp.size && fpr.fp.closing
|
||||
fpr.fp.mu.Unlock()
|
||||
if eof {
|
||||
return 0, io.EOF
|
||||
}
|
||||
}
|
||||
}
|
141
dcj/tool/src/parunner/filepipe_test.go
Normal file
141
dcj/tool/src/parunner/filepipe_test.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// infiniteReader generates an infinite and deterministic stream of bytes
|
||||
type infiniteReader int
|
||||
|
||||
func (ir *infiniteReader) Read(buf []byte) (int, error) {
|
||||
for i := range buf {
|
||||
// We want the cycle to be long to detect wrong read offsets more surely.
|
||||
buf[i] = byte(*ir ^ (*ir >> 8))
|
||||
*ir++
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func testReader() io.Reader {
|
||||
const N = 100 * 1024 // more than 2*32k, so that io.Copy will do 3 reads from it
|
||||
var ir infiniteReader
|
||||
return io.LimitReader(&ir, N)
|
||||
}
|
||||
|
||||
func expectEqual(t *testing.T, got, want []byte) {
|
||||
if bytes.Equal(got, want) {
|
||||
return
|
||||
}
|
||||
size := len(want)
|
||||
if len(got) < size {
|
||||
size = len(got)
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
if want[i] != got[i] {
|
||||
t.Errorf("value read differs from expected on byte %d: got=%d, want=%d", i, got[i], want[i])
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Errorf("value read is %d bytes long, where %d was expected", len(got), len(want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilePipeSimple(t *testing.T) {
|
||||
fp, err := NewFilePipe()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a filepipe: %v", err)
|
||||
}
|
||||
fpr := fp.Reader()
|
||||
_, err = io.Copy(fp, testReader())
|
||||
if err != nil {
|
||||
t.Errorf("Failed to write to a filepipe: %v", err)
|
||||
}
|
||||
err = fp.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to close a filepipe: %v", err)
|
||||
}
|
||||
got, err := ioutil.ReadAll(fpr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read from a filepipe reader: %v", err)
|
||||
}
|
||||
want, err := ioutil.ReadAll(testReader())
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from a testReader: %v", err)
|
||||
}
|
||||
expectEqual(t, got, want)
|
||||
}
|
||||
|
||||
func TestFilePipeConcurrent(t *testing.T) {
|
||||
want, err := ioutil.ReadAll(testReader())
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from a testReader: %v", err)
|
||||
}
|
||||
fp, err := NewFilePipe()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a filepipe: %v", err)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
const P = 10
|
||||
for i := 0; i < P; i++ {
|
||||
wg.Add(1)
|
||||
fpr := fp.Reader()
|
||||
go func(fpr io.Reader) {
|
||||
buf, err := ioutil.ReadAll(fpr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read from a filepipe reader: %v", err)
|
||||
}
|
||||
expectEqual(t, buf, want)
|
||||
wg.Done()
|
||||
}(fpr)
|
||||
}
|
||||
_, err = io.Copy(fp, testReader())
|
||||
if err != nil {
|
||||
t.Errorf("Failed to write to a filepipe: %v", err)
|
||||
}
|
||||
err = fp.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to close a filepipe: %v", err)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestFilePipeRelease(t *testing.T) {
|
||||
fp, err := NewFilePipe()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating a filepipe: %v", err)
|
||||
}
|
||||
fpr := fp.Reader()
|
||||
err = fp.Release()
|
||||
if err != nil {
|
||||
t.Fatalf("error releasing a filepipe: %v", err)
|
||||
}
|
||||
var buf [10]byte
|
||||
_, err = fpr.Read(buf[:])
|
||||
if err == nil {
|
||||
t.Errorf("no error when reading from a destroyed filepipe")
|
||||
}
|
||||
err = fp.Release()
|
||||
if err != nil {
|
||||
t.Fatalf("error releasing a filepipe for the second time: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilePipeClose(t *testing.T) {
|
||||
fp, err := NewFilePipe()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating a filepipe: %v", err)
|
||||
}
|
||||
err = fp.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("error closing a filepipe: %v", err)
|
||||
}
|
||||
_, err = fp.Write([]byte("foo"))
|
||||
if err == nil {
|
||||
t.Errorf("no error when writing to a closed filepipe")
|
||||
}
|
||||
}
|
17
dcj/tool/src/parunner/google_init_test.go
Normal file
17
dcj/tool/src/parunner/google_init_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// This file contains google3-specific code to make the third party tests work
|
||||
// with blaze.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dir := filepath.Join(os.Getenv("TEST_SRCDIR"), "google3/third_party/golang/parunner")
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
panic(fmt.Sprintf("os.Chdir(%q): %v", dir, err))
|
||||
}
|
||||
}
|
91
dcj/tool/src/parunner/instance.go
Normal file
91
dcj/tool/src/parunner/instance.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
ID int
|
||||
TotalInstances int
|
||||
Cmd *exec.Cmd
|
||||
|
||||
RequestChan chan *request
|
||||
ResponseChan chan *response
|
||||
|
||||
// The following fields should not be accessed until the Instance is Waited for.
|
||||
MessagesSent int
|
||||
MessageBytesSent int
|
||||
TimeRunning time.Duration
|
||||
TimeBlocked time.Duration
|
||||
|
||||
errOnce sync.Once
|
||||
err error
|
||||
waitDone chan bool
|
||||
commDone chan bool
|
||||
}
|
||||
|
||||
func (instance *Instance) Start() error {
|
||||
instance.waitDone = make(chan bool)
|
||||
instance.commDone = make(chan bool)
|
||||
|
||||
cmdr, cmdw, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
respr, respw, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := startInstance(instance.Cmd, respr, cmdw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := instance.communicate(cmdr, respw, instance.RequestChan, instance.ResponseChan); err != nil {
|
||||
instance.errOnce.Do(func() {
|
||||
instance.err = err
|
||||
})
|
||||
instance.Cmd.Process.Kill()
|
||||
}
|
||||
cmdr.Close()
|
||||
respw.Close()
|
||||
close(instance.commDone)
|
||||
}()
|
||||
go func() {
|
||||
err := instance.Cmd.Wait()
|
||||
instance.errOnce.Do(func() {
|
||||
instance.err = err
|
||||
})
|
||||
instance.TimeRunning = instance.Cmd.ProcessState.SystemTime() + instance.Cmd.ProcessState.UserTime()
|
||||
// We are doing it this late in order to delay error reports from communicate that are
|
||||
// a result of the pipes closing (broken pipe on write pipe, EOF on read pipe). We
|
||||
// do want to ignore some of those errors (e.g. broken pipe at the very beginning, which
|
||||
// indicates that the program didn't use the communication library at all), so currently
|
||||
// we ignore all of them.
|
||||
// TODO: Do we want to ignore then also when the program has terminated with no errors?
|
||||
// Example: program has exited in the middle of sending a message.
|
||||
respr.Close()
|
||||
cmdw.Close()
|
||||
close(instance.waitDone)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Instance) Wait() error {
|
||||
<-i.waitDone
|
||||
<-i.commDone
|
||||
return i.err
|
||||
}
|
||||
|
||||
var ErrKilled = errors.New("killed by an explicit request")
|
||||
|
||||
func (i *Instance) Kill() error {
|
||||
i.errOnce.Do(func() {
|
||||
i.err = ErrKilled
|
||||
})
|
||||
return i.Cmd.Process.Kill()
|
||||
}
|
208
dcj/tool/src/parunner/instance_test.go
Normal file
208
dcj/tool/src/parunner/instance_test.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func checkedWait(t *testing.T, instance *Instance) error {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
ch <- instance.Wait()
|
||||
}()
|
||||
err := instance.Wait()
|
||||
if err1 := <-ch; err1 != err {
|
||||
t.Errorf("Instance.Wait() gave contradictory return values: %v != %v", err, err1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func TestInstanceSuccess(t *testing.T) {
|
||||
instance := &Instance{ID: 0, TotalInstances: 1, Cmd: exec.Command(testerPath)}
|
||||
if err := instance.Start(); err != nil {
|
||||
t.Fatalf("error starting an instance of tester: %v", err)
|
||||
}
|
||||
if err := checkedWait(t, instance); err != nil {
|
||||
t.Fatalf("error running tester with empty stdin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceFailure(t *testing.T) {
|
||||
cmd := exec.Command(testerPath)
|
||||
cmd.Stdin = strings.NewReader("Q 1\n")
|
||||
instance := &Instance{ID: 0, TotalInstances: 1, Cmd: cmd}
|
||||
if err := instance.Start(); err != nil {
|
||||
t.Fatalf("error starting an instance of tester: %v", err)
|
||||
}
|
||||
if err := checkedWait(t, instance); err == nil {
|
||||
t.Fatalf("no error when running tester with stdin Q 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceKill(t *testing.T) {
|
||||
cmd := exec.Command(testerPath)
|
||||
if _, err := cmd.StdinPipe(); err != nil {
|
||||
t.Fatalf("error in Cmd.StdinPipe: %v", err)
|
||||
}
|
||||
cmd.Stdout = ioutil.Discard
|
||||
instance := &Instance{ID: 0, TotalInstances: 1, Cmd: cmd}
|
||||
if err := instance.Start(); err != nil {
|
||||
t.Fatalf("error starting an instance of tester: %v", err)
|
||||
}
|
||||
waitChan := make(chan error)
|
||||
go func() {
|
||||
waitChan <- checkedWait(t, instance)
|
||||
}()
|
||||
// The instance shouldn't finish of its own accord
|
||||
select {
|
||||
case err := <-waitChan:
|
||||
t.Fatalf("tester has finished prematurely, err=%v", err)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
instance.Kill()
|
||||
if err := <-waitChan; err != ErrKilled {
|
||||
t.Errorf("a killed instance has finished with error %v, instead of %v", err, ErrKilled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceComm(t *testing.T) {
|
||||
if _, err := os.Stat(testerPath); err != nil {
|
||||
t.Fatalf("can't find tester binary: %v", err)
|
||||
}
|
||||
type testcase struct {
|
||||
name string
|
||||
input string
|
||||
expectedOutput string
|
||||
expectedRequests []*request // expectedRequests[].time is a lower bound on the actual time
|
||||
responses []*response
|
||||
}
|
||||
singleCase := func(tc testcase) {
|
||||
cmd := exec.Command(testerPath)
|
||||
cmd.Stdin = strings.NewReader(tc.input)
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
instance := &Instance{
|
||||
ID: 5,
|
||||
TotalInstances: 20,
|
||||
Cmd: cmd,
|
||||
RequestChan: make(chan *request, 1),
|
||||
ResponseChan: make(chan *response, 1),
|
||||
}
|
||||
if err := instance.Start(); err != nil {
|
||||
t.Errorf("test %s: error starting an instance of tester: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
quit := make(chan bool)
|
||||
|
||||
lastReqTime := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
var prevTime time.Duration
|
||||
var i int
|
||||
for req := range instance.RequestChan {
|
||||
if req.time < prevTime {
|
||||
t.Errorf("test %s: request %+v is earlier than %v, the time of the previous request", tc.name, req, prevTime)
|
||||
}
|
||||
if i < len(tc.expectedRequests) {
|
||||
if req.time < tc.expectedRequests[i].time {
|
||||
t.Errorf("test %s: request %+v has time %v, expected at least %v", tc.name, req, req.time, tc.expectedRequests[i].time)
|
||||
}
|
||||
realTime := req.time
|
||||
req.time = tc.expectedRequests[i].time
|
||||
if got, want := req, tc.expectedRequests[i]; !reflect.DeepEqual(got, want) {
|
||||
got.time = realTime
|
||||
t.Errorf("test %s: got request %+v, expected %+v", tc.name, got, want)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("test %s: got request number %d, expected %d total", tc.name, i, len(tc.expectedRequests))
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i < len(tc.expectedRequests) {
|
||||
t.Errorf("test %s: got only %d requests, expected %d", tc.name, i, len(tc.expectedRequests))
|
||||
}
|
||||
lastReqTime <- prevTime
|
||||
<-quit
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer func() { <-quit }()
|
||||
for i, resp := range tc.responses {
|
||||
select {
|
||||
case instance.ResponseChan <- resp:
|
||||
case <-quit:
|
||||
t.Errorf("test %s: instance was done before receiving response number %d", tc.name, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
<-quit
|
||||
}()
|
||||
defer func() {
|
||||
quit <- true
|
||||
quit <- true
|
||||
quit <- true
|
||||
}()
|
||||
|
||||
if err := checkedWait(t, instance); err != nil {
|
||||
t.Fatalf("test %s: error running an instance of tester: %v", tc.name, err)
|
||||
close(instance.RequestChan)
|
||||
return
|
||||
}
|
||||
close(instance.RequestChan)
|
||||
if got, want := strings.Replace(stdout.String(), "\r\n", "\n", -1), tc.expectedOutput; got != want {
|
||||
t.Errorf("test %s: wrong output; got=%q, want=%q", tc.name, got, want)
|
||||
}
|
||||
if rt := <-lastReqTime; instance.TimeRunning < rt {
|
||||
t.Errorf("test %s: instance's last request happened at %v, but instance used only %v CPU time total", tc.name, rt, instance.TimeRunning)
|
||||
}
|
||||
}
|
||||
testcases := []testcase{
|
||||
{"header", "", "5 20\n", []*request{}, []*response{}},
|
||||
{"send after cpuburn", "C\nScfoobar\n", "5 20\n", []*request{&request{requestType: requestSend, destination: 2, message: []byte("foobar")}}, []*response{}},
|
||||
{"send", "Scfoobar\n", "5 20\n", []*request{&request{requestType: requestSend, destination: 2, message: []byte("foobar")}}, []*response{}},
|
||||
{"recv", "Rd\n", "5 20\n3 6 foobaz\n", []*request{&request{requestType: requestRecv, source: 3}}, []*response{&response{&Message{Source: 3, Target: 5, Message: []byte("foobaz")}}}},
|
||||
{"recvany", "R*\n", "5 20\n3 6 foobaz\n", []*request{&request{requestType: requestRecvAny}}, []*response{&response{&Message{Source: 3, Target: 5, Message: []byte("foobaz")}}}},
|
||||
{"blockingTime", "R*\nScblah\n", "5 20\n3 6 foobaz\n", []*request{
|
||||
&request{requestType: requestRecvAny},
|
||||
&request{requestType: requestSend, time: time.Duration(1234), destination: 2, message: []byte("blah")},
|
||||
}, []*response{&response{&Message{Source: 3, Target: 5, SendTime: time.Duration(1234), Message: []byte("foobaz")}}}},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
singleCase(tc)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop receiving in the middle of a message
|
||||
func TestInstanceBrokenPipe(t *testing.T) {
|
||||
cmd := exec.Command(hangerPath)
|
||||
instance := &Instance{
|
||||
ID: 0,
|
||||
TotalInstances: 2,
|
||||
Cmd: cmd,
|
||||
RequestChan: make(chan *request, 1),
|
||||
ResponseChan: make(chan *response, 1),
|
||||
}
|
||||
if err := instance.Start(); err != nil {
|
||||
t.Fatalf("error starting an instance of hanger: %v", err)
|
||||
}
|
||||
go func() {
|
||||
for _ = range instance.RequestChan {
|
||||
}
|
||||
}()
|
||||
defer close(instance.RequestChan)
|
||||
instance.ResponseChan <- &response{&Message{
|
||||
Source: 1,
|
||||
Target: 0,
|
||||
SendTime: time.Duration(0),
|
||||
Message: []byte("abcdefghijlkmnopqrstuvwxyz"), // this message will take >20 bytes on the wire
|
||||
}}
|
||||
if err := checkedWait(t, instance); err != nil {
|
||||
t.Fatalf("error running an instance of hanger: %v", err)
|
||||
}
|
||||
}
|
13
dcj/tool/src/parunner/instance_unix.go
Normal file
13
dcj/tool/src/parunner/instance_unix.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func startInstance(cmd *exec.Cmd, r *os.File, w *os.File) error {
|
||||
cmd.ExtraFiles = []*os.File{r, w}
|
||||
return cmd.Start()
|
||||
}
|
28
dcj/tool/src/parunner/instance_windows.go
Normal file
28
dcj/tool/src/parunner/instance_windows.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func startInstance(cmd *exec.Cmd, r *os.File, w *os.File) error {
|
||||
var rHandle syscall.Handle
|
||||
var wHandle syscall.Handle
|
||||
p, _ := syscall.GetCurrentProcess()
|
||||
if err := syscall.DuplicateHandle(p, syscall.Handle(r.Fd()), p, &rHandle, 0, true, syscall.DUPLICATE_SAME_ACCESS); err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(rHandle)
|
||||
if err := syscall.DuplicateHandle(p, syscall.Handle(w.Fd()), p, &wHandle, 0, true, syscall.DUPLICATE_SAME_ACCESS); err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(wHandle)
|
||||
|
||||
if cmd.Env == nil {
|
||||
cmd.Env = os.Environ()
|
||||
}
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("ZSHANDLE_IN=%d", rHandle), fmt.Sprintf("ZSHANDLE_OUT=%d", wHandle))
|
||||
return cmd.Start()
|
||||
}
|
103
dcj/tool/src/parunner/instances.go
Normal file
103
dcj/tool/src/parunner/instances.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// InstanceError is an error with an associated instance ID
|
||||
type InstanceError struct {
|
||||
ID int
|
||||
Err error
|
||||
}
|
||||
|
||||
func (ie InstanceError) Error() string {
|
||||
return fmt.Sprintf("Error of instance %d: %v", ie.ID, ie.Err)
|
||||
}
|
||||
|
||||
// RunInstances starts each command from cmds in an Instance and
|
||||
// waits either for all of them to finish successfully or for
|
||||
// the first error. In the latter case, all the rest of
|
||||
// the instances are killed. All the instances are then returned
|
||||
// in the slice. RunInstances additionally guarantees the following:
|
||||
// * The instance slice is valid even if the error is non-nil
|
||||
// * All the commands have been started before RunInstances returns
|
||||
// * All the instanced have been waited on before RunInstances returns
|
||||
// * If the error encountered is associated with an instance,
|
||||
// an instance of InstanceError is returned. That instance contains
|
||||
// the instance ID of the instance that caused the error.
|
||||
func RunInstances(cmds []*exec.Cmd, commLog io.Writer) ([]*Instance, error) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
results := make(chan error, 1)
|
||||
is := make([]*Instance, len(cmds))
|
||||
for i, cmd := range cmds {
|
||||
is[i] = &Instance{
|
||||
ID: i,
|
||||
TotalInstances: len(cmds),
|
||||
Cmd: cmd,
|
||||
RequestChan: make(chan *request, 1),
|
||||
ResponseChan: make(chan *response, 1),
|
||||
}
|
||||
if err := is[i].Start(); err != nil {
|
||||
select {
|
||||
case results <- InstanceError{i, err}:
|
||||
default:
|
||||
}
|
||||
close(is[i].RequestChan)
|
||||
continue
|
||||
}
|
||||
defer is[i].Kill()
|
||||
wg.Add(1)
|
||||
go func(i int, instance *Instance) {
|
||||
err := instance.Wait()
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- InstanceError{i, err}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
// The instance leaves the communication channels open. We close the RequestChan
|
||||
// to signal the message router that this instance has finished. In case of an error,
|
||||
// we need to do this after possibly storing the error, so that message router's error
|
||||
// (e.g. ErrDeadlock due to the last nonblocked instance exising) doesn't override ours.
|
||||
close(instance.RequestChan)
|
||||
wg.Done()
|
||||
}(i, is[i])
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
requestChans := make([]<-chan *request, len(is))
|
||||
for i := range requestChans {
|
||||
requestChans[i] = is[i].RequestChan
|
||||
}
|
||||
responseChans := make([]chan<- *response, len(is))
|
||||
for i := range responseChans {
|
||||
responseChans[i] = is[i].ResponseChan
|
||||
}
|
||||
defer func() {
|
||||
for _, ch := range responseChans {
|
||||
close(ch)
|
||||
}
|
||||
}()
|
||||
err := RouteMessages(requestChans, responseChans, commLog)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
wg.Wait()
|
||||
select {
|
||||
case results <- nil:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
return is, <-results
|
||||
}
|
88
dcj/tool/src/parunner/instances_test.go
Normal file
88
dcj/tool/src/parunner/instances_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInstances(t *testing.T) {
|
||||
if _, err := os.Stat(testerPath); err != nil {
|
||||
t.Fatalf("can't find tester binary: %v", err)
|
||||
}
|
||||
type testcase struct {
|
||||
name string
|
||||
inputs []string
|
||||
expectedOutputs []string // only used if len(expectedFails) == 0
|
||||
expectedFails []int // if empty, we expect success; otherwise we expect failure from one of the numerated instances
|
||||
expectedDeadlock bool
|
||||
}
|
||||
testcases := []*testcase{
|
||||
{"no comm", []string{"", "", "", ""}, []string{"0 4\n", "1 4\n", "2 4\n", "3 4\n"}, nil, false},
|
||||
{"fail", []string{"", "Q 1", "Q 2"}, nil, []int{1, 2}, false},
|
||||
{"send with no recv", []string{"", "Safoo\n"}, []string{"0 2\n", "1 2\n"}, nil, false},
|
||||
{"send with recv", []string{"Rb\n", "Safoo\n"}, []string{"0 2\n1 3 foo\n", "1 2\n"}, nil, false},
|
||||
{"send with recvany", []string{"R*\n", "Safoo\n"}, []string{"0 2\n1 3 foo\n", "1 2\n"}, nil, false},
|
||||
{"deadlock", []string{"Scfoo\nR*\n", "R*\n", ""}, nil, nil, true},
|
||||
{"fail and hang", []string{"H\n", "Q 1\n"}, nil, []int{1}, false},
|
||||
{"fail and hanging recv", []string{"R*\n", "Q 1\n"}, nil, []int{1}, false},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
outputs := make([]bytes.Buffer, len(tc.inputs))
|
||||
cmds := make([]*exec.Cmd, len(tc.inputs))
|
||||
for i, input := range tc.inputs {
|
||||
cmds[i] = exec.Command(testerPath)
|
||||
cmds[i].Stdin = strings.NewReader(input)
|
||||
cmds[i].Stdout = &outputs[i]
|
||||
}
|
||||
_, err := RunInstances(cmds, ioutil.Discard)
|
||||
if _, ok := err.(ErrRemainingMessages); ok {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case InstanceError:
|
||||
ok := false
|
||||
for _, i := range tc.expectedFails {
|
||||
if err.ID == i {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("test %s: unexpected error from RunInstances: %v", tc.name, err)
|
||||
}
|
||||
case ErrDeadlock:
|
||||
if !tc.expectedDeadlock {
|
||||
t.Errorf("test %s: unexpected deadlock", tc.name)
|
||||
}
|
||||
default:
|
||||
t.Errorf("test %s: unexpected error from RunInstances: %v", tc.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(tc.expectedFails) != 0 || tc.expectedDeadlock {
|
||||
t.Errorf("test %s: unexpected success of RunInstances", tc.name)
|
||||
continue
|
||||
}
|
||||
for i, want := range tc.expectedOutputs {
|
||||
got := strings.Replace(outputs[i].String(), "\r\n", "\n", -1)
|
||||
if got != want {
|
||||
t.Errorf("test %s: wrong output from instance %d: got=%q, want=%q", tc.name, i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstancesStartError(t *testing.T) {
|
||||
cmds := []*exec.Cmd{exec.Command("/does/not/exist")}
|
||||
_, err := RunInstances(cmds, ioutil.Discard)
|
||||
if err == nil {
|
||||
t.Errorf("expected an error when trying to run a nonexistent binary")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check what happens when we send/recv message to/from an instance that doesn't exist
|
||||
// TODO: check what happens when an instance claims that its CPU time goes backward
|
221
dcj/tool/src/parunner/main.go
Normal file
221
dcj/tool/src/parunner/main.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
)
|
||||
|
||||
const MaxInstances = 100
|
||||
|
||||
var nInstances = flag.Int("n", 1, fmt.Sprintf("Number of instances; must be from the [1,%d] range", MaxInstances))
|
||||
var stdoutHandling = flag.String("stdout", "contest", "Stdout handling: contest, all, tagged, files")
|
||||
var stderrHandling = flag.String("stderr", "all", "Stderr handling: all, tagged, files")
|
||||
var filesPrefix = flag.String("prefix", "", "Filename prefix for files generated by -stdout=files and -stderr=files")
|
||||
var warnRemaining = flag.Bool("warn_unreceived", true, "Warn about messages that remain unreceived after instance's termination")
|
||||
var stats = flag.Bool("print_stats", false, "Print per-instance statistics")
|
||||
var traceCommunications = flag.Bool("trace_comm", false, "Print out a trace of all messages exchanged")
|
||||
|
||||
var binaryPath string
|
||||
|
||||
func writeFile(streamType string, i int, r io.Reader) error {
|
||||
binaryDir, binaryFile := filepath.Split(binaryPath)
|
||||
if idx := strings.LastIndex(binaryFile, "."); idx != -1 {
|
||||
binaryFile = binaryFile[:idx]
|
||||
}
|
||||
basename := filepath.Join(binaryDir, binaryFile)
|
||||
if *filesPrefix != "" {
|
||||
basename = *filesPrefix
|
||||
}
|
||||
filename := fmt.Sprintf("%s.%s.%d", basename, streamType, i)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(f, r)
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [flags] binary_to_run\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, `Output handling modes:
|
||||
contest: Fail if more than one instance write any output. Redirect the output to the standard output of this program.
|
||||
all: Redirect all the instances' outputs to the corresponding output of this program.
|
||||
tagged: Redirect all the instances' outputs to the corresponding output of this program, while prefixing each line with instance number.
|
||||
files: Store output of each instance in a separate file.
|
||||
`)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
|
||||
flag.Usage = Usage
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Fprintf(os.Stderr, "Specify the binary name\n")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
var err error
|
||||
binaryPath, err = filepath.Abs(flag.Arg(0))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Cannot find absolute path of the binary: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *nInstances < 1 || *nInstances > MaxInstances {
|
||||
fmt.Fprintf(os.Stderr, "Number of instances should be from [1,%d], but %d was given\n", MaxInstances, *nInstances)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var writeStdout func(int, io.Reader) error
|
||||
contestStdout := &ContestStdout{Output: os.Stdout}
|
||||
switch *stdoutHandling {
|
||||
case "contest":
|
||||
// This is handled specially (without a pipe) below.
|
||||
case "all":
|
||||
case "tagged":
|
||||
writeStdout = func(i int, r io.Reader) error { return TagStream(fmt.Sprintf("STDOUT %d: ", i), os.Stdout, r) }
|
||||
case "files":
|
||||
writeStdout = func(i int, r io.Reader) error { return writeFile("stdout", i, r) }
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Invalid stdout handling mode: %s", *stdoutHandling)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
var writeStderr func(int, io.Reader) error
|
||||
switch *stderrHandling {
|
||||
case "all":
|
||||
case "tagged":
|
||||
writeStderr = func(i int, r io.Reader) error { return TagStream(fmt.Sprintf("STDERR %d: ", i), os.Stderr, r) }
|
||||
case "files":
|
||||
writeStdout = func(i int, r io.Reader) error { return writeFile("stderr", i, r) }
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Inalid stderr handling mode: %s", *stdoutHandling)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
stdinPipe, err := NewFilePipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stdinPipe.Release()
|
||||
go func() {
|
||||
_, err := io.Copy(stdinPipe, os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = stdinPipe.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
progs := make([]*exec.Cmd, *nInstances)
|
||||
var wg sync.WaitGroup
|
||||
closeAfterWait := []io.Closer{}
|
||||
for i := range progs {
|
||||
cmd := exec.Command(binaryPath)
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
// We don't care about errors from the writer (we expect broken pipe if the process has exited
|
||||
// before reading all of its input), but we do care about errors when reading from the filepipe.
|
||||
if _, err := io.Copy(WrapWriter(w), stdinPipe.Reader()); err != nil {
|
||||
if _, ok := err.(WriterError); !ok {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
w.Close()
|
||||
}()
|
||||
makeFromWrite := func(writeProc func(int, io.Reader) error, w io.Writer) io.Writer {
|
||||
if writeProc == nil {
|
||||
return w
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
closeAfterWait = append(closeAfterWait, pw)
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
err := writeProc(i, pr)
|
||||
if err != nil {
|
||||
// All the errors we can get are not caused by instances' invalid behaviour, but
|
||||
// by system issues (can't create a file, broken pipe on real stdout/err, etc.)
|
||||
log.Fatal(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
return pw
|
||||
}
|
||||
if *stdoutHandling == "contest" {
|
||||
cmd.Stdout = contestStdout.NewWriter(i)
|
||||
} else {
|
||||
cmd.Stdout = makeFromWrite(writeStdout, os.Stdout)
|
||||
}
|
||||
cmd.Stderr = makeFromWrite(writeStderr, os.Stderr)
|
||||
progs[i] = cmd
|
||||
}
|
||||
commLog := ioutil.Discard
|
||||
if *traceCommunications {
|
||||
commLog = os.Stderr
|
||||
}
|
||||
instances, err := RunInstances(progs, commLog)
|
||||
for _, f := range closeAfterWait {
|
||||
f.Close()
|
||||
}
|
||||
wg.Wait()
|
||||
if er, ok := err.(ErrRemainingMessages); ok {
|
||||
if *warnRemaining {
|
||||
m := make(map[int][]int)
|
||||
for _, p := range er.RemainingMessages {
|
||||
m[p.To] = append(m[p.To], p.From)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Warning: following instances had some messages left after they've terminated:\n")
|
||||
for dest, srcs := range m {
|
||||
fmt.Fprintf(os.Stderr, "Instance %d did not receive message from instances: ", dest)
|
||||
for _, src := range srcs {
|
||||
fmt.Fprintf(os.Stderr, "%d ", src)
|
||||
}
|
||||
fmt.Fprintln(os.Stderr)
|
||||
}
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var maxTime time.Duration
|
||||
var lastInstance int
|
||||
for i, instance := range instances {
|
||||
if instanceTime := instance.TimeRunning + instance.TimeBlocked; instanceTime >= maxTime {
|
||||
maxTime = instanceTime
|
||||
lastInstance = i
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Duration: %v (longest running instance: %d)\n", maxTime, lastInstance)
|
||||
if *stats {
|
||||
w := tabwriter.NewWriter(os.Stderr, 2, 1, 1, ' ', 0)
|
||||
io.WriteString(w, "Instance\tTotal time\tCPU time\tTime spent waiting\tSent messages\tSent bytes\n")
|
||||
for i, instance := range instances {
|
||||
fmt.Fprintf(w, "%d\t%v\t%v\t%v\t%d\t%d\n", i, instance.TimeRunning+instance.TimeBlocked, instance.TimeRunning, instance.TimeBlocked, instance.MessagesSent, instance.MessageBytesSent)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
}
|
8
dcj/tool/src/parunner/message/BUILD
Normal file
8
dcj/tool/src/parunner/message/BUILD
Normal file
@@ -0,0 +1,8 @@
|
||||
# Description:
|
||||
# Auto-imported from github.com/robryk/parunner/message
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # BSD 3-clause
|
||||
|
||||
exports_files(["LICENSE"])
|
127
dcj/tool/src/parunner/message/compile
Normal file
127
dcj/tool/src/parunner/message/compile
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -u
|
||||
|
||||
ZEUS_DIR=$(realpath $(dirname $(which $0)))
|
||||
|
||||
CFLAGS="${CFLAGS:-} -O2 -static -I${ZEUS_DIR}"
|
||||
CXXFLAGS="${CXXFLAGS:-} -O2 -static -std=gnu++0x -I${ZEUS_DIR}"
|
||||
CXX=${CXX:-g++}
|
||||
CC=${CC:-gcc}
|
||||
FPC=${FPC:-fpc}
|
||||
|
||||
read -d '' USAGE <<EOF
|
||||
Uzycie: $0 [--debug] source_file [library_file]
|
||||
|
||||
Przyklad:
|
||||
$0 solution.cpp
|
||||
|
||||
Opcja --debug uzywa debugowej wersji biblioteki message. Wersja ta
|
||||
sprawdza, czy typy odbierane sa takie same jak typy wysylane (czy
|
||||
przykladowo nie odebrano chara gdy wyslano inta) oraz czy funkcja
|
||||
Receive jest poprawnie wywolywana.
|
||||
|
||||
EOF
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MESSAGE=message_internal
|
||||
DEBUGPAS=
|
||||
|
||||
if [ "$1" == "--debug" ]; then
|
||||
MESSAGE=message_internal_debug
|
||||
DEBUGPAS=-dmsg_debug
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "$#" -gt 2 -o "$#" -lt 1 ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE="$1"
|
||||
LIBRARY_SOURCE="${2:-}"
|
||||
|
||||
pushd "${ZEUS_DIR}" > /dev/null
|
||||
test -f zeus_local.o || "${CC}" -c -O2 -g zeus_local.c || exit 2
|
||||
test -f "${MESSAGE}.o" || "${CC}" -c -O2 -g "${MESSAGE}.c" || exit 2
|
||||
popd > /dev/null
|
||||
|
||||
PASLIB=0
|
||||
NONPASLIB=0
|
||||
|
||||
case "$LIBRARY_SOURCE" in
|
||||
"")
|
||||
LIBRARY_OBJ=
|
||||
;;
|
||||
*.pas)
|
||||
LIBRARY_OBJ=
|
||||
PASLIB=1
|
||||
;;
|
||||
*.cpp)
|
||||
LIBRARY_OBJ="$(echo "${LIBRARY_SOURCE}" | sed 's/\.cpp$//').impl.o"
|
||||
"${CXX}" -c ${CXXFLAGS} "$LIBRARY_SOURCE" -o "$LIBRARY_OBJ" || exit 2
|
||||
NONPASLIB=1
|
||||
;;
|
||||
*.c)
|
||||
LIBRARY_OBJ="$(echo "${LIBRARY_SOURCE}" | sed 's/\.c$//').impl.o"
|
||||
"${CC}" -c ${CFLAGS} "${LIBRARY_SOURCE}" -o "${LIBRARY_OBJ}" || exit 2
|
||||
NONPASLIB=1
|
||||
;;
|
||||
*)
|
||||
echo "Nieznany jezyk biblioteczki ${LIBRARY_SOURCE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
case "${SOURCE}" in
|
||||
*.pas)
|
||||
if [ "${NONPASLIB}" -eq 1 ]; then
|
||||
echo "Biblioteczka winna byc napisana w tym samym jezyku co program"
|
||||
exit 1
|
||||
fi
|
||||
BASENAME="$(echo "${SOURCE}" | sed 's/\.pas//')"
|
||||
rm "${ZEUS_DIR}/message.ppu"
|
||||
"${FPC}" ${DEBUGPAS} -Fu"${ZEUS_DIR}" -o"${BASENAME}.e" "${SOURCE}" || {
|
||||
if [ "${PASLIB}" -eq 1 ]; then
|
||||
echo "Upewnij sie, ze dodales wlasciwa dyrektywe uses dla biblioteczki"
|
||||
fi
|
||||
exit 2
|
||||
}
|
||||
;;
|
||||
*.cpp)
|
||||
if [ "${PASLIB}" -eq 1 ]; then
|
||||
echo "Biblioteczka winna byc napisana w tym samym jezyku co program"
|
||||
exit 1
|
||||
fi
|
||||
BASENAME="$(echo "$SOURCE" | sed 's/\.cpp//')"
|
||||
"${CXX}" -c ${CXXFLAGS} "${SOURCE}" -o "${BASENAME}.o" || exit 2
|
||||
if [ "${LIBRARY_OBJ}" == "" ]; then
|
||||
"${CXX}" -I"${ZEUS_DIR}" "${ZEUS_DIR}/${MESSAGE}.o" "${ZEUS_DIR}/zeus_local.o" "${BASENAME}.o" -o "${BASENAME}.e" || exit 2
|
||||
else
|
||||
"${CXX}" -I"${ZEUS_DIR}" "${ZEUS_DIR}/${MESSAGE}.o" "${ZEUS_DIR}/zeus_local.o" "${BASENAME}.o" "${LIBRARY_OBJ}" -o "${BASENAME}.e" || exit 2
|
||||
fi
|
||||
;;
|
||||
*.c)
|
||||
if [ "${PASLIB}" -eq 1 ]; then
|
||||
echo "Biblioteczka winna byc napisana w tym samym jezyku co program"
|
||||
exit 1
|
||||
fi
|
||||
BASENAME="$(echo "${SOURCE}" | sed 's/\.c//')"
|
||||
"${CC}" -c ${CFLAGS} "${SOURCE}" -o "${BASENAME}.o" || exit 2
|
||||
if [ "$LIBRARY_OBJ" == "" ]; then
|
||||
"${CXX}" -I"${ZEUS_DIR}" "${ZEUS_DIR}/${MESSAGE}.o" "${ZEUS_DIR}/zeus_local.o" "${BASENAME}.o" -o "${BASENAME}.e" || exit 2
|
||||
else
|
||||
"${CXX}" -I"${ZEUS_DIR}" "${ZEUS_DIR}/${MESSAGE}.o" "${ZEUS_DIR}/zeus_local.o" "${BASENAME}.o" "${LIBRARY_OBJ}" -o "${BASENAME}.e" || exit 2
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Nieznany jezyk pliku zrodlowego ${SOURCE}"
|
||||
exit 2
|
||||
esac
|
||||
|
||||
# vim:ts=2:sts=2:sw=2:et:
|
78
dcj/tool/src/parunner/message/message.h
Normal file
78
dcj/tool/src/parunner/message/message.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// Biblioteka message sluzy do przekazywania wiadomosci pomiedzy instancjami
|
||||
// programu. Wiadomosc moze skladac sie z dowolnej liczby znakow (char) oraz
|
||||
// liczb typu int i long long. Maksymalna liczba wiadmosci, jakie moze
|
||||
// wyslac pojedyncza instancja, to 1000, a ich laczny rozmiar nie moze
|
||||
// przekraczac 8 MB.
|
||||
|
||||
#ifndef MESSAGE_H_
|
||||
#define MESSAGE_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Zwraca liczbe instancji.
|
||||
// Pascal: function NumberOfNodes:longint;
|
||||
|
||||
int NumberOfNodes();
|
||||
|
||||
// Zwraca ID biezacej instancji, z przedzialu {0, ..., NumberOfNodes()-1}.
|
||||
// Pascal: function MyNodeId:longint;
|
||||
int MyNodeId();
|
||||
|
||||
// Kolejkuje `value` do wyslania do instancji `target`.
|
||||
// Pascal: procedure PutChar(target:longint; value:shortint);
|
||||
void PutChar(int target, char value);
|
||||
|
||||
// Kolejkuje `value` do wyslania do instancji `target`.
|
||||
// Pascal: procedure PutInt(target:longint; value:longint);
|
||||
void PutInt(int target, int value);
|
||||
|
||||
// Kolejkuje `value` do wyslania do instancji `target`.
|
||||
// Pascal: procedure PutLL(target:longint; value:int64);
|
||||
void PutLL(int target, long long value);
|
||||
|
||||
// Wysyla zakolejkowana wiadomosc do instancji `target`. Powrot z funkcji
|
||||
// nastepuje natychmiast, nie czekajac na odbior wiadomosci.
|
||||
// Pascal: procedure Send(target:longint);
|
||||
void Send(int target);
|
||||
|
||||
// Odbiera wiadomosc od instancji `source` (lub dowolnej, gdy `source` == -1).
|
||||
// Zwraca numer instancji, od ktorej wiadomosc odebral. Receive wymaga, aby w
|
||||
// momencie jego wywolania poprzednio odebrana wiadomosc od instancji `source`
|
||||
// (lub wszystkie poprzednio odebrane wiadomosci, gdy `source` == -1) byla
|
||||
// w calosci przeczytana.
|
||||
// Pascal: function Receive(source:longint):longint;
|
||||
int Receive(int source);
|
||||
|
||||
// Po odebraniu wiadomosci jej zawartosc nalezy czytac w takiej kolejnosci,
|
||||
// w jakiej byla kolejkowana do wyslania. Np. proba odczytania int-a, podczas
|
||||
// gdy pierwsza zakolejkowana wartosc byl long long, skonczy sie bledem
|
||||
// wykonania (jesli program byl kompilowany z opcja --debug) lub spowoduje
|
||||
// niezdefiniowane zachowanie (bez --debug).
|
||||
|
||||
// Po odebraniu wiadomosci jej zawartosc nalezy czytac w takiej kolejnosci,
|
||||
// w jakiej byla kolejkowana do wyslania. Na przyklad, gdy nadawca jako pierwsza
|
||||
// wartosc zakolejkowal long longa, a odbiorca sprobuje przeczytac chara,
|
||||
// program zakonczy sie bledem wykonania (jesli byl kompilowany z opcja --debug)
|
||||
// lub zachowa sie w niezdefiniowany sposob (bez --debug).
|
||||
|
||||
// Czyta char z odebranej wiadomosci od instancji `source`. Numer instancji
|
||||
// musi byc liczba z przedzialu {0, ..., NumberOfNodes()-1}. W szczegolnosci
|
||||
// nie moze byc rowny -1.
|
||||
// Pascal: function GetChar(source:longint):shortint;
|
||||
char GetChar(int source);
|
||||
|
||||
// Czyta int z odebranej wiadomosci od instancji `source`.
|
||||
// Pascal: function GetInt(source:longint):longint;
|
||||
int GetInt(int source);
|
||||
|
||||
// Czyta long long z odebranej wiadomosci od instancji `source`.
|
||||
// Pascal: function GetLL(source:longint):int64;
|
||||
long long GetLL(int source);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MESSAGE_H_
|
170
dcj/tool/src/parunner/message/message_internal.c
Normal file
170
dcj/tool/src/parunner/message/message_internal.c
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "message.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "zeus.h"
|
||||
|
||||
#define ZEUS(s) zeus_##s
|
||||
|
||||
#define DEBUG 0
|
||||
#define MAX_MESSAGE_SIZE (8 * (1<<20))
|
||||
|
||||
static void Die(const char* s) {
|
||||
fputs(s, stderr);
|
||||
exit(20);
|
||||
}
|
||||
|
||||
int NumberOfNodes() {
|
||||
return ZEUS(NumberOfNodes)();
|
||||
}
|
||||
|
||||
int MyNodeId() {
|
||||
return ZEUS(MyNodeId)();
|
||||
}
|
||||
|
||||
static void CheckNodeId(int node) {
|
||||
if (!DEBUG)
|
||||
return;
|
||||
if (node < 0 || node >= NumberOfNodes())
|
||||
Die("Niepoprawny numer maszyny");
|
||||
}
|
||||
|
||||
typedef struct Buffer {
|
||||
char* buffer;
|
||||
int size;
|
||||
int pos; // for input buffers next byte to be read. for output buffers next byte to be written.
|
||||
} Buffer;
|
||||
|
||||
static int Empty(Buffer* buffer) {
|
||||
return buffer->pos >= buffer->size;
|
||||
}
|
||||
|
||||
static unsigned char GetRawByte(Buffer* buf) {
|
||||
if (Empty(buf)) {
|
||||
Die("Przeczytano za koncem wiadomosci");
|
||||
}
|
||||
char r = buf->buffer[buf->pos++];
|
||||
if (Empty(buf)) {
|
||||
free(buf->buffer);
|
||||
buf->buffer = NULL;
|
||||
buf->pos = 0;
|
||||
buf->size = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void PutRawByte(Buffer* buffer, unsigned char byte) {
|
||||
if (buffer->pos >= buffer->size) {
|
||||
buffer->size = 2*buffer->size;
|
||||
if (buffer->size < 128)
|
||||
buffer->size = 128;
|
||||
buffer->buffer = (char*)realloc(buffer->buffer, buffer->size);
|
||||
assert(buffer->buffer);
|
||||
}
|
||||
buffer->buffer[buffer->pos++] = byte;
|
||||
}
|
||||
|
||||
#define MAX_MACHINES 100
|
||||
|
||||
static Buffer incoming_buffers[MAX_MACHINES];
|
||||
static Buffer outgoing_buffers[MAX_MACHINES];
|
||||
|
||||
char recv_buffer[MAX_MESSAGE_SIZE];
|
||||
|
||||
int Receive(int source) {
|
||||
if (source != -1)
|
||||
CheckNodeId(source);
|
||||
if (DEBUG && source == -1) {
|
||||
int i;
|
||||
for(i=0;i<ZEUS(NumberOfNodes)();i++) {
|
||||
if (!Empty(&incoming_buffers[i]))
|
||||
Die("Receive(-1) z nieprzeczytana wiadomoscia");
|
||||
}
|
||||
}
|
||||
ZEUS(MessageInfo) mi = ZEUS(Receive)(source, recv_buffer, sizeof(recv_buffer));
|
||||
Buffer* buf = &incoming_buffers[mi.sender_id];
|
||||
if (!Empty(buf))
|
||||
Die("Receive() odebral wiadomosc od maszyny z nieprzeczytana wiadomoscia");
|
||||
assert(buf->buffer == NULL);
|
||||
buf->buffer = (char*)malloc(mi.length);
|
||||
assert(buf->buffer);
|
||||
memcpy(buf->buffer, recv_buffer, mi.length);
|
||||
buf->pos = 0;
|
||||
buf->size = mi.length;
|
||||
return mi.sender_id;
|
||||
}
|
||||
|
||||
#define kChar 14
|
||||
#define kInt 15
|
||||
#define kLL 16
|
||||
|
||||
static void GetTag(int source, int expected) {
|
||||
if (!DEBUG)
|
||||
return;
|
||||
int tag = GetRawByte(&incoming_buffers[source]);
|
||||
if (tag != expected)
|
||||
Die("Przeczytano inny typ wartosci niz wyslano");
|
||||
}
|
||||
|
||||
int GetInt(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kInt);
|
||||
int result = 0, i;
|
||||
for(i=0;i<sizeof(int);i++)
|
||||
result |= (int)(GetRawByte(&incoming_buffers[source])) << (8 * i);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutInt(int target, int value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG)
|
||||
PutRawByte(&outgoing_buffers[target], kInt);
|
||||
int i;
|
||||
for(i=0;i<sizeof(int);i++)
|
||||
PutRawByte(&outgoing_buffers[target], (0xff & (value >> (8 * i))));
|
||||
}
|
||||
|
||||
long long GetLL(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kLL);
|
||||
long long result = 0;
|
||||
int i;
|
||||
for(i=0;i<sizeof(long long);i++)
|
||||
result |= (long long)(GetRawByte(&incoming_buffers[source])) << (8 * i);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutLL(int target, long long value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG)
|
||||
PutRawByte(&outgoing_buffers[target], kLL);
|
||||
int i;
|
||||
for(i=0;i<sizeof(long long);i++)
|
||||
PutRawByte(&outgoing_buffers[target], (0xff & (value >> (8 * i))));
|
||||
}
|
||||
|
||||
char GetChar(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kChar);
|
||||
return GetRawByte(&incoming_buffers[source]);
|
||||
}
|
||||
|
||||
void PutChar(int target, char value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG)
|
||||
PutRawByte(&outgoing_buffers[target], kChar);
|
||||
PutRawByte(&outgoing_buffers[target], value);
|
||||
}
|
||||
|
||||
void Send(int target) {
|
||||
CheckNodeId(target);
|
||||
Buffer* buffer = &outgoing_buffers[target];
|
||||
if (buffer->pos > sizeof(recv_buffer))
|
||||
Die("Za dluga wiadomosc");
|
||||
ZEUS(Send)(target, buffer->buffer, buffer->pos);
|
||||
free(buffer->buffer);
|
||||
buffer->buffer = NULL;
|
||||
buffer->pos = buffer->size = 0;
|
||||
}
|
170
dcj/tool/src/parunner/message/message_internal_debug.c
Normal file
170
dcj/tool/src/parunner/message/message_internal_debug.c
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "message.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "zeus.h"
|
||||
|
||||
#define ZEUS(s) zeus_##s
|
||||
|
||||
#define DEBUG 1
|
||||
#define MAX_MESSAGE_SIZE (8 * (1<<20))
|
||||
|
||||
static void Die(const char* s) {
|
||||
fputs(s, stderr);
|
||||
exit(20);
|
||||
}
|
||||
|
||||
int NumberOfNodes() {
|
||||
return ZEUS(NumberOfNodes)();
|
||||
}
|
||||
|
||||
int MyNodeId() {
|
||||
return ZEUS(MyNodeId)();
|
||||
}
|
||||
|
||||
static void CheckNodeId(int node) {
|
||||
if (!DEBUG)
|
||||
return;
|
||||
if (node < 0 || node >= NumberOfNodes())
|
||||
Die("Niepoprawny numer maszyny");
|
||||
}
|
||||
|
||||
typedef struct Buffer {
|
||||
char* buffer;
|
||||
int size;
|
||||
int pos; // for input buffers next byte to be read. for output buffers next byte to be written.
|
||||
} Buffer;
|
||||
|
||||
static int Empty(Buffer* buffer) {
|
||||
return buffer->pos >= buffer->size;
|
||||
}
|
||||
|
||||
static unsigned char GetRawByte(Buffer* buf) {
|
||||
if (Empty(buf)) {
|
||||
Die("Przeczytano za koncem wiadomosci");
|
||||
}
|
||||
char r = buf->buffer[buf->pos++];
|
||||
if (Empty(buf)) {
|
||||
free(buf->buffer);
|
||||
buf->buffer = NULL;
|
||||
buf->pos = 0;
|
||||
buf->size = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void PutRawByte(Buffer* buffer, unsigned char byte) {
|
||||
if (buffer->pos >= buffer->size) {
|
||||
buffer->size = 2*buffer->size;
|
||||
if (buffer->size < 128)
|
||||
buffer->size = 128;
|
||||
buffer->buffer = (char*)realloc(buffer->buffer, buffer->size);
|
||||
assert(buffer->buffer);
|
||||
}
|
||||
buffer->buffer[buffer->pos++] = byte;
|
||||
}
|
||||
|
||||
#define MAX_MACHINES 100
|
||||
|
||||
static Buffer incoming_buffers[MAX_MACHINES];
|
||||
static Buffer outgoing_buffers[MAX_MACHINES];
|
||||
|
||||
char recv_buffer[MAX_MESSAGE_SIZE];
|
||||
|
||||
int Receive(int source) {
|
||||
if (source != -1)
|
||||
CheckNodeId(source);
|
||||
if (DEBUG && source == -1) {
|
||||
int i;
|
||||
for(i=0;i<ZEUS(NumberOfNodes)();i++) {
|
||||
if (!Empty(&incoming_buffers[i]))
|
||||
Die("Receive(-1) z nieprzeczytana wiadomoscia");
|
||||
}
|
||||
}
|
||||
ZEUS(MessageInfo) mi = ZEUS(Receive)(source, recv_buffer, sizeof(recv_buffer));
|
||||
Buffer* buf = &incoming_buffers[mi.sender_id];
|
||||
if (!Empty(buf))
|
||||
Die("Receive() odebral wiadomosc od maszyny z nieprzeczytana wiadomoscia");
|
||||
assert(buf->buffer == NULL);
|
||||
buf->buffer = (char*)malloc(mi.length);
|
||||
assert(buf->buffer);
|
||||
memcpy(buf->buffer, recv_buffer, mi.length);
|
||||
buf->pos = 0;
|
||||
buf->size = mi.length;
|
||||
return mi.sender_id;
|
||||
}
|
||||
|
||||
#define kChar 14
|
||||
#define kInt 15
|
||||
#define kLL 16
|
||||
|
||||
static void GetTag(int source, int expected) {
|
||||
if (!DEBUG)
|
||||
return;
|
||||
int tag = GetRawByte(&incoming_buffers[source]);
|
||||
if (tag != expected)
|
||||
Die("Przeczytano inny typ wartosci niz wyslano");
|
||||
}
|
||||
|
||||
int GetInt(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kInt);
|
||||
int result = 0, i;
|
||||
for(i=0;i<sizeof(int);i++)
|
||||
result |= (int)(GetRawByte(&incoming_buffers[source])) << (8 * i);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutInt(int target, int value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG)
|
||||
PutRawByte(&outgoing_buffers[target], kInt);
|
||||
int i;
|
||||
for(i=0;i<sizeof(int);i++)
|
||||
PutRawByte(&outgoing_buffers[target], (0xff & (value >> (8 * i))));
|
||||
}
|
||||
|
||||
long long GetLL(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kLL);
|
||||
long long result = 0;
|
||||
int i;
|
||||
for(i=0;i<sizeof(long long);i++)
|
||||
result |= (long long)(GetRawByte(&incoming_buffers[source])) << (8 * i);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutLL(int target, long long value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG)
|
||||
PutRawByte(&outgoing_buffers[target], kLL);
|
||||
int i;
|
||||
for(i=0;i<sizeof(long long);i++)
|
||||
PutRawByte(&outgoing_buffers[target], (0xff & (value >> (8 * i))));
|
||||
}
|
||||
|
||||
char GetChar(int source) {
|
||||
CheckNodeId(source);
|
||||
GetTag(source, kChar);
|
||||
return GetRawByte(&incoming_buffers[source]);
|
||||
}
|
||||
|
||||
void PutChar(int target, char value) {
|
||||
CheckNodeId(target);
|
||||
if (DEBUG)
|
||||
PutRawByte(&outgoing_buffers[target], kChar);
|
||||
PutRawByte(&outgoing_buffers[target], value);
|
||||
}
|
||||
|
||||
void Send(int target) {
|
||||
CheckNodeId(target);
|
||||
Buffer* buffer = &outgoing_buffers[target];
|
||||
if (buffer->pos > sizeof(recv_buffer))
|
||||
Die("Za dluga wiadomosc");
|
||||
ZEUS(Send)(target, buffer->buffer, buffer->pos);
|
||||
free(buffer->buffer);
|
||||
buffer->buffer = NULL;
|
||||
buffer->pos = buffer->size = 0;
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
From 9a3a25a8aec5dde88a322f0905f3c3a56d3ad1d5 Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Tue, 28 Apr 2015 00:04:36 +0200
|
||||
Subject: [PATCH] Fix a nil dereference when an instance fails to start.
|
||||
|
||||
---
|
||||
instances.go | 2 +-
|
||||
instances_test.go | 8 ++++++++
|
||||
2 files changed, 9 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git instances.go instances.go
|
||||
index ea19ff8..f782a79 100644
|
||||
--- instances.go
|
||||
+++ instances.go
|
||||
@@ -43,11 +43,11 @@ func RunInstances(cmds []*exec.Cmd, commLog io.Writer) ([]*Instance, error) {
|
||||
ResponseChan: make(chan *response, 1),
|
||||
}
|
||||
if err := is[i].Start(); err != nil {
|
||||
- is[i] = nil
|
||||
select {
|
||||
case results <- InstanceError{i, err}:
|
||||
default:
|
||||
}
|
||||
+ close(is[i].RequestChan)
|
||||
continue
|
||||
}
|
||||
defer is[i].Kill()
|
||||
diff --git instances_test.go instances_test.go
|
||||
index a00f7ef..5c13709 100644
|
||||
--- instances_test.go
|
||||
+++ instances_test.go
|
||||
@@ -76,5 +76,13 @@ func TestInstances(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
+func TestInstancesStartError(t *testing.T) {
|
||||
+ cmds := []*exec.Cmd{exec.Command("/does/not/exist")}
|
||||
+ _, err := RunInstances(cmds, ioutil.Discard)
|
||||
+ if err == nil {
|
||||
+ t.Errorf("expected an error when trying to run a nonexistent binary")
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
// TODO: check what happens when we send/recv message to/from an instance that doesn't exist
|
||||
// TODO: check what happens when an instance claims that its CPU time goes backward
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
25
dcj/tool/src/parunner/patches/0002-go-vet.patch
Normal file
25
dcj/tool/src/parunner/patches/0002-go-vet.patch
Normal file
@@ -0,0 +1,25 @@
|
||||
From 40cf238d611563b9d6af96f0fe950eabb6350874 Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Tue, 28 Apr 2015 00:29:37 +0200
|
||||
Subject: [PATCH] go vet
|
||||
|
||||
---
|
||||
instance_test.go | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git instance_test.go instance_test.go
|
||||
index edd0371..28a378f 100644
|
||||
--- instance_test.go
|
||||
+++ instance_test.go
|
||||
@@ -158,7 +158,7 @@ func TestInstanceComm(t *testing.T) {
|
||||
t.Errorf("test %s: wrong output; got=%q, want=%q", tc.name, got, want)
|
||||
}
|
||||
if rt := <-lastReqTime; instance.TimeRunning < rt {
|
||||
- t.Errorf("test %s: instance's last request happened at %v, but instance used only %v CPU time total", rt, instance.TimeRunning)
|
||||
+ t.Errorf("test %s: instance's last request happened at %v, but instance used only %v CPU time total", tc.name, rt, instance.TimeRunning)
|
||||
}
|
||||
}
|
||||
testcases := []testcase{
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
@@ -0,0 +1,54 @@
|
||||
From 5b52c40f67b2ff06cd893df278e2d3c3e8751797 Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Wed, 6 May 2015 19:57:27 +0200
|
||||
Subject: [PATCH] Use cwd instead of $PATH when looking up the binary.
|
||||
|
||||
This causes parunner blah to actually run ./blah instead of erroring
|
||||
out.
|
||||
---
|
||||
main.go | 11 +++++++++--
|
||||
1 file changed, 9 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git main.go main.go
|
||||
index 74419d7..3fb6b2b 100644
|
||||
--- main.go
|
||||
+++ main.go
|
||||
@@ -63,18 +63,25 @@ func main() {
|
||||
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
|
||||
flag.Usage = Usage
|
||||
flag.Parse()
|
||||
+
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Fprintf(os.Stderr, "Nie podałeś programu do uruchomienia\n")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
- binaryPath = flag.Arg(0)
|
||||
+ var err error
|
||||
+ binaryPath, err = filepath.Abs(flag.Arg(0))
|
||||
+ if err != nil {
|
||||
+ fmt.Fprintf(os.Stderr, "Cannot find absolute path of the binary: %v\n", err)
|
||||
+ os.Exit(1)
|
||||
+ }
|
||||
|
||||
if *nInstances < 1 || *nInstances > MaxInstances {
|
||||
fmt.Fprintf(os.Stderr, "Liczba instancji powinna być z zakresu [1,%d], a podałeś %d\n", MaxInstances, *nInstances)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
+
|
||||
var writeStdout func(int, io.Reader) error
|
||||
contestStdout := &ContestStdout{Output: os.Stdout}
|
||||
switch *stdoutHandling {
|
||||
@@ -122,7 +129,7 @@ func main() {
|
||||
var wg sync.WaitGroup
|
||||
closeAfterWait := []io.Closer{}
|
||||
for i := range progs {
|
||||
- cmd := exec.Command(flag.Arg(0))
|
||||
+ cmd := exec.Command(binaryPath)
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
@@ -0,0 +1,52 @@
|
||||
From 045611330af23d7a9c1e510a8e69f6fa52f6290b Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Wed, 6 May 2015 20:03:05 +0200
|
||||
Subject: [PATCH] Custom error types for limits exceeded
|
||||
|
||||
---
|
||||
comm.go | 19 +++++++++++++++----
|
||||
1 file changed, 15 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git comm.go comm.go
|
||||
index c153cee..785bbc2 100644
|
||||
--- comm.go
|
||||
+++ comm.go
|
||||
@@ -49,8 +49,19 @@ type Message struct {
|
||||
Message []byte
|
||||
}
|
||||
|
||||
-var ErrMessageCount = fmt.Errorf("przekroczony limit (%d) liczby wysłanych wiadomości", MessageCountLimit)
|
||||
-var ErrMessageSize = fmt.Errorf("przekroczony limit (%d bajtów) sumarycznego rozmiaru wysłanych wiadomości", MessageSizeLimit)
|
||||
+type ErrMessageCount struct {
|
||||
+}
|
||||
+
|
||||
+func (err ErrMessageCount) Error() string {
|
||||
+ return fmt.Sprintf("przekroczony limit (%d) liczby wysłanych wiadomości", MessageCountLimit)
|
||||
+}
|
||||
+
|
||||
+type ErrMessageSize struct {
|
||||
+}
|
||||
+
|
||||
+func (err ErrMessageSize) Error() string {
|
||||
+ return fmt.Sprintf("przekroczony limit (%d bajtów) sumarycznego rozmiaru wysłanych wiadomości", MessageSizeLimit)
|
||||
+}
|
||||
|
||||
func writeMessage(w io.Writer, message *Message) error {
|
||||
rr := recvResponse{
|
||||
@@ -175,11 +186,11 @@ func (i *Instance) communicate(r io.Reader, w io.Writer, reqCh chan<- *request,
|
||||
if req.requestType == requestSend {
|
||||
i.MessagesSent++
|
||||
if i.MessagesSent > MessageCountLimit {
|
||||
- return ErrMessageCount
|
||||
+ return ErrMessageCount{}
|
||||
}
|
||||
i.MessageBytesSent += len(req.message)
|
||||
if i.MessageBytesSent > MessageSizeLimit {
|
||||
- return ErrMessageSize
|
||||
+ return ErrMessageSize{}
|
||||
}
|
||||
}
|
||||
currentTime := req.time
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
@@ -0,0 +1,84 @@
|
||||
From 9306327ecb86f6c6037c7fb8eecd10a5b82d2c39 Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Wed, 6 May 2015 20:23:54 +0200
|
||||
Subject: [PATCH] Hack: allow message limits to be set on cmdline
|
||||
|
||||
---
|
||||
comm.go | 20 +++++++++++++-------
|
||||
1 file changed, 13 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git comm.go comm.go
|
||||
index 785bbc2..2d532fb 100644
|
||||
--- comm.go
|
||||
+++ comm.go
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
+ "flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
@@ -39,8 +40,9 @@ type sendHeader struct {
|
||||
// Message []byte
|
||||
}
|
||||
|
||||
-const MessageCountLimit = 1000
|
||||
-const MessageSizeLimit = 8 * 1024 * 1024
|
||||
+// TODO(robryk): Move this to instance-creation-time options
|
||||
+var messageCountLimit = flag.Int("message_count_limit", 1000, "Limit for the number of messages sent per instance")
|
||||
+var messageSizeLimit = flag.Int("message_size_limit", 8*1024*1024, "Limit for the total size of messages sent by an instance, in bytes")
|
||||
|
||||
type Message struct {
|
||||
Source int
|
||||
@@ -49,18 +51,22 @@ type Message struct {
|
||||
Message []byte
|
||||
}
|
||||
|
||||
+// ErrMessageCount is returned when an instance exceeds the per-instance message count limit.
|
||||
+// It is usually encapsulated in an InstanceError that specifies the instance ID.
|
||||
type ErrMessageCount struct {
|
||||
}
|
||||
|
||||
func (err ErrMessageCount) Error() string {
|
||||
- return fmt.Sprintf("przekroczony limit (%d) liczby wysłanych wiadomości", MessageCountLimit)
|
||||
+ return fmt.Sprintf("przekroczony limit (%d) liczby wysłanych wiadomości", *messageCountLimit)
|
||||
}
|
||||
|
||||
+// ErrMessageSize is returned when an instance exceeds the per-instance total messages size limit.
|
||||
+// It is usually encapsulated in an InstanceError that specifies the instance ID.
|
||||
type ErrMessageSize struct {
|
||||
}
|
||||
|
||||
func (err ErrMessageSize) Error() string {
|
||||
- return fmt.Sprintf("przekroczony limit (%d bajtów) sumarycznego rozmiaru wysłanych wiadomości", MessageSizeLimit)
|
||||
+ return fmt.Sprintf("przekroczony limit (%d bajtów) sumarycznego rozmiaru wysłanych wiadomości", *messageSizeLimit)
|
||||
}
|
||||
|
||||
func writeMessage(w io.Writer, message *Message) error {
|
||||
@@ -135,7 +141,7 @@ func readRequest(r io.Reader) (*request, error) {
|
||||
if err := binary.Read(r, binary.LittleEndian, &sh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
- if sh.Length < 0 || sh.Length > MessageSizeLimit {
|
||||
+ if sh.Length < 0 || int(sh.Length) > *messageSizeLimit {
|
||||
return nil, fmt.Errorf("invalid size of a message to be sent: %d", sh.Length)
|
||||
}
|
||||
if sh.TargetID < 0 || sh.TargetID >= MaxInstances {
|
||||
@@ -185,11 +191,11 @@ func (i *Instance) communicate(r io.Reader, w io.Writer, reqCh chan<- *request,
|
||||
req.time += i.TimeBlocked
|
||||
if req.requestType == requestSend {
|
||||
i.MessagesSent++
|
||||
- if i.MessagesSent > MessageCountLimit {
|
||||
+ if i.MessagesSent > *messageCountLimit {
|
||||
return ErrMessageCount{}
|
||||
}
|
||||
i.MessageBytesSent += len(req.message)
|
||||
- if i.MessageBytesSent > MessageSizeLimit {
|
||||
+ if i.MessageBytesSent > *messageSizeLimit {
|
||||
return ErrMessageSize{}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
@@ -0,0 +1,36 @@
|
||||
From 20bc419d7146c11ca9d058eccfcb6c1eacce7650 Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Thu, 28 May 2015 23:14:41 +0200
|
||||
Subject: [PATCH] Fix a testee crash when Send or Receive are called early.
|
||||
|
||||
Send and receive in zeus_local.c didn't check if the library is
|
||||
initialized and thus failed due to an assert if that happened.
|
||||
|
||||
TODO: Add a regression test.
|
||||
---
|
||||
zeus/zeus_local.c | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git zeus/zeus_local.c zeus/zeus_local.c
|
||||
index 8b25897..314a1d0 100644
|
||||
--- zeus/zeus_local.c
|
||||
+++ zeus/zeus_local.c
|
||||
@@ -114,6 +114,7 @@ static int CurrentTime() {
|
||||
#endif
|
||||
|
||||
void ZEUS(Send)(ZEUS(NodeId) target, const char* message, int bytes) {
|
||||
+ Init();
|
||||
assert(target >= 0 && target < nof_nodes);
|
||||
assert(bytes <= MAX_MESSAGE_SIZE);
|
||||
int i;
|
||||
@@ -127,6 +128,7 @@ void ZEUS(Send)(ZEUS(NodeId) target, const char* message, int bytes) {
|
||||
}
|
||||
|
||||
ZEUS(MessageInfo) ZEUS(Receive)(ZEUS(NodeId) source, char* buffer, int buffer_size) {
|
||||
+ Init();
|
||||
assert(source >= -1 && source < nof_nodes);
|
||||
ZEUS(MessageInfo) mi;
|
||||
int i;
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
@@ -0,0 +1,194 @@
|
||||
From 40100b7a3dcc1275b69e13d32374b6d78c43a303 Mon Sep 17 00:00:00 2001
|
||||
From: Robert Obryk <robryk@google.com>
|
||||
Date: Mon, 4 May 2015 21:18:15 +0200
|
||||
Subject: [PATCH] translate user-visible messages to English
|
||||
|
||||
This change is not supposed to be upstreamed in this form. I will try to
|
||||
find a way to keep both language versions in the same codebase.
|
||||
---
|
||||
comm.go | 4 ++--
|
||||
instances.go | 2 +-
|
||||
main.go | 42 +++++++++++++++++++++---------------------
|
||||
route.go | 4 ++--
|
||||
util.go | 2 +-
|
||||
5 files changed, 27 insertions(+), 27 deletions(-)
|
||||
|
||||
diff --git comm.go comm.go
|
||||
index 2d532fb..e043a89 100644
|
||||
--- comm.go
|
||||
+++ comm.go
|
||||
@@ -57,7 +57,7 @@ type ErrMessageCount struct {
|
||||
}
|
||||
|
||||
func (err ErrMessageCount) Error() string {
|
||||
- return fmt.Sprintf("przekroczony limit (%d) liczby wysłanych wiadomości", *messageCountLimit)
|
||||
+ return fmt.Sprintf("sent message count limit (%d) exceeded", *messageCountLimit)
|
||||
}
|
||||
|
||||
// ErrMessageSize is returned when an instance exceeds the per-instance total messages size limit.
|
||||
@@ -66,7 +66,7 @@ type ErrMessageSize struct {
|
||||
}
|
||||
|
||||
func (err ErrMessageSize) Error() string {
|
||||
- return fmt.Sprintf("przekroczony limit (%d bajtów) sumarycznego rozmiaru wysłanych wiadomości", *messageSizeLimit)
|
||||
+ return fmt.Sprintf("total sent message size limit (%d bytes) exceeded", *messageSizeLimit)
|
||||
}
|
||||
|
||||
func writeMessage(w io.Writer, message *Message) error {
|
||||
diff --git instances.go instances.go
|
||||
index f782a79..6e65ef6 100644
|
||||
--- instances.go
|
||||
+++ instances.go
|
||||
@@ -14,7 +14,7 @@ type InstanceError struct {
|
||||
}
|
||||
|
||||
func (ie InstanceError) Error() string {
|
||||
- return fmt.Sprintf("Błąd instancji %d: %v", ie.ID, ie.Err)
|
||||
+ return fmt.Sprintf("Error of instance %d: %v", ie.ID, ie.Err)
|
||||
}
|
||||
|
||||
// RunInstances starts each command from cmds in an Instance and
|
||||
diff --git main.go main.go
|
||||
index 3fb6b2b..54ba230 100644
|
||||
--- main.go
|
||||
+++ main.go
|
||||
@@ -17,13 +17,13 @@ import (
|
||||
|
||||
const MaxInstances = 100
|
||||
|
||||
-var nInstances = flag.Int("n", 1, fmt.Sprintf("Liczba instancji, z zakresu [1,%d]", MaxInstances))
|
||||
-var stdoutHandling = flag.String("stdout", "contest", "Obługa standardowego wyjścia: contest, all, tagged, files")
|
||||
-var stderrHandling = flag.String("stderr", "all", "Obsługa standardowe wyjścia diagnostycznego: all, tagged, files")
|
||||
-var filesPrefix = flag.String("prefix", "", "Prefiks nazwy plików wyjściowych generowanych przez -stdout=files i -stderr=files")
|
||||
-var warnRemaining = flag.Bool("warn_unreceived", true, "Ostrzegaj o wiadomościach, które pozostały nieodebrane po zakończeniu się instancji")
|
||||
-var stats = flag.Bool("print_stats", false, "Na koniec wypisz statystyki dotyczące poszczególnych instancji")
|
||||
-var traceCommunications = flag.Bool("trace_comm", false, "Wypisz na standardowe wyjście diagnostyczne informację o każdej wysłanej i odebranej wiadomości")
|
||||
+var nInstances = flag.Int("n", 1, fmt.Sprintf("Number of instances; must be from the [1,%d] range", MaxInstances))
|
||||
+var stdoutHandling = flag.String("stdout", "contest", "Stdout handling: contest, all, tagged, files")
|
||||
+var stderrHandling = flag.String("stderr", "all", "Stderr handling: all, tagged, files")
|
||||
+var filesPrefix = flag.String("prefix", "", "Filename prefix for files generated by -stdout=files and -stderr=files")
|
||||
+var warnRemaining = flag.Bool("warn_unreceived", true, "Warn about messages that remain unreceived after instance's termination")
|
||||
+var stats = flag.Bool("print_stats", false, "Print per-instance statistics")
|
||||
+var traceCommunications = flag.Bool("trace_comm", false, "Print out a trace of all messages exchanged")
|
||||
|
||||
var binaryPath string
|
||||
|
||||
@@ -49,13 +49,13 @@ func writeFile(streamType string, i int, r io.Reader) error {
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
- fmt.Fprintf(os.Stderr, "Uzycie: %s [opcje] program_do_uruchomienia\n", os.Args[0])
|
||||
+ fmt.Fprintf(os.Stderr, "Usage: %s [flags] binary_to_run\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
- fmt.Fprintf(os.Stderr, `Sposoby obsługi wyjścia:
|
||||
- contest: Wymuszaj, żeby tylko jedna instancja pisała na standardowe wyjście. Przekieruj jej wyjście na standardowe wyjście tego programu.
|
||||
- all: Przekieruj wyjście wszystkich instancji na analogiczne wyjście tego programu.
|
||||
- tagged: Przekieruj wyjście wszystkich instancji na analogiczne wyjście tego programy, dopisując numer instancji na początku każdej linijki.
|
||||
- files: Zapisz wyjście każdej instancji w osobnym pliku.
|
||||
+ fmt.Fprintf(os.Stderr, `Output handling modes:
|
||||
+ contest: Fail if more than one instance write any output. Redirect the output to the standard output of this program.
|
||||
+ all: Redirect all the instances' outputs to the corresponding output of this program.
|
||||
+ tagged: Redirect all the instances' outputs to the corresponding output of this program, while prefixing each line with instance number.
|
||||
+ files: Store output of each instance in a separate file.
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
- fmt.Fprintf(os.Stderr, "Nie podałeś programu do uruchomienia\n")
|
||||
+ fmt.Fprintf(os.Stderr, "Specify the binary name\n")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func main() {
|
||||
}
|
||||
|
||||
if *nInstances < 1 || *nInstances > MaxInstances {
|
||||
- fmt.Fprintf(os.Stderr, "Liczba instancji powinna być z zakresu [1,%d], a podałeś %d\n", MaxInstances, *nInstances)
|
||||
+ fmt.Fprintf(os.Stderr, "Number of instances should be from [1,%d], but %d was given\n", MaxInstances, *nInstances)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func main() {
|
||||
case "files":
|
||||
writeStdout = func(i int, r io.Reader) error { return writeFile("stdout", i, r) }
|
||||
default:
|
||||
- fmt.Fprintf(os.Stderr, "Niewłaściwa metoda obsługi standardowego wyjścia: %s", *stdoutHandling)
|
||||
+ fmt.Fprintf(os.Stderr, "Invalid stdout handling mode: %s", *stdoutHandling)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func main() {
|
||||
case "files":
|
||||
writeStdout = func(i int, r io.Reader) error { return writeFile("stderr", i, r) }
|
||||
default:
|
||||
- fmt.Fprintf(os.Stderr, "Niewłaściwa metoda obsługi standardowego wyjścia diagnostycznego: %s", *stdoutHandling)
|
||||
+ fmt.Fprintf(os.Stderr, "Inalid stderr handling mode: %s", *stdoutHandling)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -186,9 +186,9 @@ func main() {
|
||||
for _, p := range er.RemainingMessages {
|
||||
m[p.To] = append(m[p.To], p.From)
|
||||
}
|
||||
- fmt.Fprintf(os.Stderr, "Uwaga: następujące instancje nie odebrały wszystkich wiadomości dla nich przeznaczonych nim się zakończyły:\n")
|
||||
+ fmt.Fprintf(os.Stderr, "Warning: following instances had some messages left after they've terminated:\n")
|
||||
for dest, srcs := range m {
|
||||
- fmt.Fprintf(os.Stderr, "Instancja %d nie odebrała wiadomości od instancji: ", dest)
|
||||
+ fmt.Fprintf(os.Stderr, "Instance %d did not receive message from instances: ", dest)
|
||||
for _, src := range srcs {
|
||||
fmt.Fprintf(os.Stderr, "%d ", src)
|
||||
}
|
||||
@@ -209,10 +209,10 @@ func main() {
|
||||
lastInstance = i
|
||||
}
|
||||
}
|
||||
- fmt.Fprintf(os.Stderr, "Czas trwania: %v (najdłużej działająca instancja: %d)\n", maxTime, lastInstance)
|
||||
+ fmt.Fprintf(os.Stderr, "Duration: %v (longest running instance: %d)\n", maxTime, lastInstance)
|
||||
if *stats {
|
||||
w := tabwriter.NewWriter(os.Stderr, 2, 1, 1, ' ', 0)
|
||||
- io.WriteString(w, "Instancja\tCzas całkowity\tCzas CPU\tCzas oczekiwania\tWysłane wiadomości\tWysłane bajty\n")
|
||||
+ io.WriteString(w, "Instance\tTotal time\tCPU time\tTime spent waiting\tSent messages\tSent bytes\n")
|
||||
for i, instance := range instances {
|
||||
fmt.Fprintf(w, "%d\t%v\t%v\t%v\t%d\t%d\n", i, instance.TimeRunning+instance.TimeBlocked, instance.TimeRunning, instance.TimeBlocked, instance.MessagesSent, instance.MessageBytesSent)
|
||||
}
|
||||
diff --git route.go route.go
|
||||
index f2df9ad..76875a5 100644
|
||||
--- route.go
|
||||
+++ route.go
|
||||
@@ -16,7 +16,7 @@ type ErrDeadlock struct {
|
||||
}
|
||||
|
||||
func (e ErrDeadlock) Error() string {
|
||||
- return "wszystkie niezakończone instancje są zablokowane"
|
||||
+ return "all instances have either terminated or are deadlocked"
|
||||
}
|
||||
|
||||
// An ErrRemainingMessages represents a situation when some messages were left
|
||||
@@ -28,7 +28,7 @@ type ErrRemainingMessages struct {
|
||||
}
|
||||
|
||||
func (e ErrRemainingMessages) Error() string {
|
||||
- return "po zakończeniu działania pozostały nieodebrane wiadomości"
|
||||
+ return "some messages were left unreceived after all instances have terminated"
|
||||
}
|
||||
|
||||
// requestAndID represents a request r made by instance id
|
||||
diff --git util.go util.go
|
||||
index 7a16ab0..e1d1f71 100644
|
||||
--- util.go
|
||||
+++ util.go
|
||||
@@ -25,7 +25,7 @@ func (w *contestStdoutWriter) Write(buf []byte) (int, error) {
|
||||
if w.cs.chosenInstance == w.id {
|
||||
return w.cs.Output.Write(buf)
|
||||
} else {
|
||||
- return 0, fmt.Errorf("instancja %d zaczęła już wypisywać wyjście", w.cs.chosenInstance)
|
||||
+ return 0, fmt.Errorf("instance %d has already started to write output", w.cs.chosenInstance)
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.2.0.rc0.207.ga3a616c
|
||||
|
198
dcj/tool/src/parunner/route.go
Normal file
198
dcj/tool/src/parunner/route.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// An ErrDeadlock represents a situation in which all of the instances have either
|
||||
// finished or are waiting for a message.
|
||||
type ErrDeadlock struct {
|
||||
// WaitingInstances lists the instances that are still alive and trying to receive a message.
|
||||
WaitingInstances []int
|
||||
// RemainingMessages lists the pairs of instances that have unreceived messages between them.
|
||||
RemainingMessages []struct{ From, To int }
|
||||
}
|
||||
|
||||
func (e ErrDeadlock) Error() string {
|
||||
return "all instances have either terminated or are deadlocked"
|
||||
}
|
||||
|
||||
// An ErrRemainingMessages represents a situation when some messages were left
|
||||
// in the queues (ie. weren't received) when all the instances have finished.
|
||||
// This situation should not be considered an error, but we should warn about it.
|
||||
type ErrRemainingMessages struct {
|
||||
// RemainingMessages lists the pairs of instances that have unreceived messages between them.
|
||||
RemainingMessages []struct{ From, To int }
|
||||
}
|
||||
|
||||
func (e ErrRemainingMessages) Error() string {
|
||||
return "some messages were left unreceived after all instances have terminated"
|
||||
}
|
||||
|
||||
// requestAndID represents a request r made by instance id
|
||||
type requestAndID struct {
|
||||
id int
|
||||
r *request
|
||||
}
|
||||
|
||||
// merge reads requests from a slice of input channels and calls fn for every request in
|
||||
// timestamp order. When fn return a pair (i, b) we assume that from this point on input channel
|
||||
// i is blocked iff b is true. We assume that:
|
||||
// * every input channel produces requests in ascending timestamp order,
|
||||
// * when a channel is blocked it will not produce any requests,
|
||||
// * an unblocked channel will only produce requests with timestamps later than that of
|
||||
// the request that unblocked it most recently,
|
||||
// * an unblocked channel will eventually produce a request or close.
|
||||
// merge returns when all input channels are closed or blocked. merge returns the indexes of
|
||||
// the channels that are blocked.
|
||||
func merge(inputs []<-chan *request, fn func(*requestAndID) (int, bool)) (deadlocked []int) {
|
||||
blocked := make([]bool, len(inputs))
|
||||
lastInputs := make([]*request, len(inputs))
|
||||
for {
|
||||
for i, c := range inputs {
|
||||
if lastInputs[i] != nil || blocked[i] {
|
||||
continue
|
||||
}
|
||||
lastInputs[i] = <-c
|
||||
}
|
||||
firstI := -1
|
||||
for i, v := range lastInputs {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if firstI == -1 || v.time < lastInputs[firstI].time {
|
||||
firstI = i
|
||||
}
|
||||
}
|
||||
if firstI == -1 {
|
||||
// Either all the channels are closed or all the channels that aren't are in blocking requests.
|
||||
// In the latter case a deadlock has occurred, because nothing can unblock them anymore.
|
||||
var blockedInstances []int
|
||||
for i, b := range blocked {
|
||||
if b {
|
||||
blockedInstances = append(blockedInstances, i)
|
||||
}
|
||||
}
|
||||
return blockedInstances
|
||||
}
|
||||
i, block := fn(&requestAndID{id: firstI, r: lastInputs[firstI]})
|
||||
blocked[i] = block
|
||||
lastInputs[firstI] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// A queueSet contains the incoming message queues of one instance.
|
||||
type queueSet struct {
|
||||
queues map[int][]*Message
|
||||
receiveFn func() (*response, bool)
|
||||
output chan<- *response
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func newQueueSet(output chan<- *response, logger *log.Logger) *queueSet {
|
||||
return &queueSet{
|
||||
queues: make(map[int][]*Message),
|
||||
output: output,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (qs *queueSet) dequeue(from int) *Message {
|
||||
ms := qs.queues[from]
|
||||
if len(ms) > 1 {
|
||||
qs.queues[from] = ms[1:]
|
||||
} else {
|
||||
delete(qs.queues, from)
|
||||
}
|
||||
return ms[0]
|
||||
}
|
||||
|
||||
// handleRequest handles a receive request from this instance or a send request
|
||||
// to this instance. handleRequest returns true iff the instance is now blocked
|
||||
// and won't emit any requests itself until unblocked by an incoming message.
|
||||
func (qs *queueSet) handleRequest(req *requestAndID) (blocked bool) {
|
||||
switch req.r.requestType {
|
||||
case requestSend:
|
||||
qs.logger.Printf("instancja %d wysyła do mnie wiadomość (%d bajtów) [%v]", req.id, len(req.r.message), req.r.time)
|
||||
qs.queues[req.id] = append(qs.queues[req.id],
|
||||
&Message{
|
||||
Source: req.id,
|
||||
Target: req.r.destination,
|
||||
SendTime: req.r.time,
|
||||
Message: req.r.message,
|
||||
})
|
||||
case requestRecv:
|
||||
qs.logger.Printf("czekam na wiadomość od instancji %d [%v]", req.r.source, req.r.time)
|
||||
if qs.receiveFn != nil {
|
||||
panic("two simultaneous receives")
|
||||
}
|
||||
qs.receiveFn = func() (*response, bool) {
|
||||
if _, ok := qs.queues[req.r.source]; ok {
|
||||
return &response{message: qs.dequeue(req.r.source)}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
case requestRecvAny:
|
||||
qs.logger.Printf("czekam na wiadomość od dowolnej instancji [%v]", req.r.time)
|
||||
if qs.receiveFn != nil {
|
||||
panic("two simultaneous receives")
|
||||
}
|
||||
qs.receiveFn = func() (*response, bool) {
|
||||
for i := range qs.queues {
|
||||
return &response{message: qs.dequeue(i)}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if qs.receiveFn != nil {
|
||||
if response, ok := qs.receiveFn(); ok {
|
||||
qs.logger.Printf("odebrałam wiadomość od instancji %d (%d bajtów)", response.message.Source, len(response.message.Message))
|
||||
qs.output <- response
|
||||
qs.receiveFn = nil
|
||||
}
|
||||
}
|
||||
return qs.receiveFn != nil
|
||||
}
|
||||
|
||||
// RouteMessages processes requests (send and receives) from a set of instances and sends back responses
|
||||
// to requests that require them. It should be given two slices of equal size: requestChans[i] should
|
||||
// be the channel that provides the requests from instance i and responses to that instance will be delivered
|
||||
// to responseChans[i]. The function will return once all requests are processed and all input channels are closed,
|
||||
// or once an error occurs. The function leaves output channels open. The function will output debugging information
|
||||
// to the logOutput.
|
||||
//
|
||||
// Prerequisites:
|
||||
// Each output channel must be buffered.
|
||||
// A request that requires a response must not be followed by another request until the response is read.
|
||||
func RouteMessages(requestChans []<-chan *request, responseChans []chan<- *response, logOutput io.Writer) error {
|
||||
const logPrefix = "COMM: instancja %2d:"
|
||||
queueSets := make([]*queueSet, len(requestChans))
|
||||
for i, output := range responseChans {
|
||||
queueSets[i] = newQueueSet(output, log.New(logOutput, fmt.Sprintf(logPrefix, i), 0))
|
||||
}
|
||||
blocked := merge(requestChans, func(req *requestAndID) (int, bool) {
|
||||
var target int
|
||||
switch req.r.requestType {
|
||||
case requestSend:
|
||||
target = req.r.destination
|
||||
default:
|
||||
target = req.id
|
||||
}
|
||||
return target, queueSets[target].handleRequest(req)
|
||||
})
|
||||
var remaining []struct{ From, To int }
|
||||
for i, qs := range queueSets {
|
||||
for j := range qs.queues {
|
||||
remaining = append(remaining, struct{ From, To int }{j, i})
|
||||
}
|
||||
}
|
||||
if len(blocked) > 0 {
|
||||
return ErrDeadlock{WaitingInstances: blocked, RemainingMessages: remaining}
|
||||
}
|
||||
if len(remaining) > 0 {
|
||||
return ErrRemainingMessages{RemainingMessages: remaining}
|
||||
}
|
||||
return nil
|
||||
}
|
126
dcj/tool/src/parunner/route_test.go
Normal file
126
dcj/tool/src/parunner/route_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fakeInstance struct {
|
||||
requestChan chan *request
|
||||
responseChan chan *response
|
||||
fakeTime time.Duration
|
||||
}
|
||||
|
||||
func newFakeInstance() *fakeInstance {
|
||||
return &fakeInstance{
|
||||
requestChan: make(chan *request, 1),
|
||||
responseChan: make(chan *response, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (fi *fakeInstance) Send(destination int, contents []byte) {
|
||||
fi.fakeTime++
|
||||
fi.requestChan <- &request{
|
||||
requestType: requestSend,
|
||||
time: fi.fakeTime,
|
||||
destination: destination,
|
||||
message: contents,
|
||||
}
|
||||
}
|
||||
|
||||
func (fi *fakeInstance) RecvFrom(source int) *Message {
|
||||
fi.fakeTime++
|
||||
fi.requestChan <- &request{
|
||||
requestType: requestRecv,
|
||||
time: fi.fakeTime,
|
||||
source: source,
|
||||
}
|
||||
resp := <-fi.responseChan
|
||||
if resp.message.SendTime > fi.fakeTime {
|
||||
fi.fakeTime = resp.message.SendTime
|
||||
}
|
||||
resp.message.SendTime = 0
|
||||
return resp.message
|
||||
}
|
||||
|
||||
func (fi *fakeInstance) Recv() *Message {
|
||||
fi.fakeTime++
|
||||
fi.requestChan <- &request{
|
||||
requestType: requestRecvAny,
|
||||
time: fi.fakeTime,
|
||||
}
|
||||
resp := <-fi.responseChan
|
||||
if resp.message.SendTime > fi.fakeTime {
|
||||
fi.fakeTime = resp.message.SendTime
|
||||
}
|
||||
resp.message.SendTime = 0
|
||||
return resp.message
|
||||
}
|
||||
|
||||
func (fi *fakeInstance) Close() {
|
||||
close(fi.requestChan)
|
||||
}
|
||||
|
||||
func setupFakes(n int) []*fakeInstance {
|
||||
fis := make([]*fakeInstance, n)
|
||||
for i := range fis {
|
||||
fis[i] = newFakeInstance()
|
||||
}
|
||||
return fis
|
||||
}
|
||||
|
||||
func routeFakes(fis []*fakeInstance) error {
|
||||
requestChans := make([]<-chan *request, len(fis))
|
||||
responseChans := make([]chan<- *response, len(fis))
|
||||
for i, fi := range fis {
|
||||
requestChans[i] = fi.requestChan
|
||||
responseChans[i] = fi.responseChan
|
||||
}
|
||||
return RouteMessages(requestChans, responseChans, ioutil.Discard)
|
||||
}
|
||||
|
||||
// equivalentMessages returns true if the two messages are equal or differ in the SendTime only
|
||||
func equivalentMessages(a, b *Message) bool {
|
||||
return a.Source == b.Source && a.Target == b.Target && bytes.Equal(a.Message, b.Message)
|
||||
}
|
||||
|
||||
func TestRouterSimple(t *testing.T) {
|
||||
fakes := setupFakes(2)
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
if err := routeFakes(fakes); err != nil {
|
||||
t.Errorf("RouteMessages unexcpectedly failed: %v", err)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
fakes[0].Send(1, []byte("foobar"))
|
||||
fakes[0].Send(0, []byte("foobaz"))
|
||||
if got, want := fakes[0].RecvFrom(0), (&Message{Source: 0, Target: 0, Message: []byte("foobaz")}); !equivalentMessages(got, want) {
|
||||
t.Errorf("unexpected message received: got=%+v, want=%+v", got, want)
|
||||
}
|
||||
if got, want := fakes[0].Recv(), (&Message{Source: 1, Target: 0, Message: []byte("barbaz")}); !equivalentMessages(got, want) {
|
||||
t.Errorf("unexpected message received: got=%+v, want=%+v", got, want)
|
||||
}
|
||||
fakes[0].Close()
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
fakes[1].Send(0, []byte("barbaz"))
|
||||
fakes[1].Recv()
|
||||
fakes[1].Close()
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
<-done
|
||||
}
|
||||
|
||||
// TODO: test the timestamp-ordering mechanism
|
||||
// TODO: test deadlock detection (check for false positives too)
|
||||
// TODO: test remaining messages detection
|
62
dcj/tool/src/parunner/util.go
Normal file
62
dcj/tool/src/parunner/util.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ContestStdout struct {
|
||||
Output io.Writer
|
||||
chosenInstance int
|
||||
chooseInstance sync.Once
|
||||
}
|
||||
|
||||
type contestStdoutWriter struct {
|
||||
cs *ContestStdout
|
||||
id int
|
||||
}
|
||||
|
||||
func (w *contestStdoutWriter) Write(buf []byte) (int, error) {
|
||||
w.cs.chooseInstance.Do(func() {
|
||||
w.cs.chosenInstance = w.id
|
||||
})
|
||||
if w.cs.chosenInstance == w.id {
|
||||
return w.cs.Output.Write(buf)
|
||||
} else {
|
||||
return 0, fmt.Errorf("instance %d has already started to write output", w.cs.chosenInstance)
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContestStdout) NewWriter(id int) io.Writer {
|
||||
return &contestStdoutWriter{cs: cs, id: id}
|
||||
}
|
||||
|
||||
func TagStream(tag string, w io.Writer, r io.Reader) error {
|
||||
sc := bufio.NewScanner(r)
|
||||
for sc.Scan() {
|
||||
if _, err := fmt.Fprintf(w, "%s%s\n", tag, sc.Text()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return sc.Err()
|
||||
}
|
||||
|
||||
type WriterError error
|
||||
|
||||
type wrappedWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w wrappedWriter) Write(buf []byte) (int, error) {
|
||||
n, err := w.Writer.Write(buf)
|
||||
if err != nil {
|
||||
err = WriterError(err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func WrapWriter(w io.Writer) io.Writer {
|
||||
return wrappedWriter{w}
|
||||
}
|
65
dcj/tool/src/parunner/util_test.go
Normal file
65
dcj/tool/src/parunner/util_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContestStdout(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
cs := &ContestStdout{Output: &buf}
|
||||
|
||||
const N = 10
|
||||
var wg sync.WaitGroup
|
||||
token := make(chan bool, 1)
|
||||
token <- true
|
||||
expectedOutput := make(chan string, 1)
|
||||
for i := 0; i < N; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
s := fmt.Sprintf("%d\nfoobarbazblah", i)
|
||||
w := cs.NewWriter(i)
|
||||
_, err := io.Copy(w, strings.NewReader(s))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-token:
|
||||
default:
|
||||
t.Errorf("instance %d was also able to write", i)
|
||||
return
|
||||
}
|
||||
expectedOutput <- s
|
||||
}(i)
|
||||
}
|
||||
want := <-expectedOutput
|
||||
got := buf.String()
|
||||
if got != want {
|
||||
t.Errorf("wrong output of ContestStdout: got=%q, want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagStream(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{"foo\n", "PREFIXfoo\n"},
|
||||
{"foo", "PREFIXfoo\n"},
|
||||
{"foo\n\nbar", "PREFIXfoo\nPREFIX\nPREFIXbar\n"},
|
||||
} {
|
||||
var buf bytes.Buffer
|
||||
if err := TagStream("PREFIX", &buf, strings.NewReader(tc.input)); err != nil {
|
||||
t.Errorf("TagStream failed: %v", err)
|
||||
continue
|
||||
}
|
||||
if got := buf.String(); got != tc.output {
|
||||
t.Errorf("TagStream returned invalid output: got=%q, want=%q", got, tc.output)
|
||||
}
|
||||
}
|
||||
}
|
31
dcj/tool/src/parunner/zeus/BUILD
Normal file
31
dcj/tool/src/parunner/zeus/BUILD
Normal file
@@ -0,0 +1,31 @@
|
||||
# Description:
|
||||
# Auto-imported from github.com/robryk/parunner/zeus
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # BSD 3-clause
|
||||
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
cc_library(
|
||||
name = "zeus_local",
|
||||
srcs = ["zeus_local.c"],
|
||||
hdrs = ["zeus.h"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "example",
|
||||
srcs = ["example.c"],
|
||||
deps = [":zeus_local"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "hanger",
|
||||
srcs = ["hanger.c"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "tester",
|
||||
srcs = ["tester.c"],
|
||||
deps = [":zeus_local"],
|
||||
)
|
8
dcj/tool/src/parunner/zeus/Makefile
Normal file
8
dcj/tool/src/parunner/zeus/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
CFLAGS += -Wall -O2 -g
|
||||
|
||||
all: example hanger tester
|
||||
|
||||
example: example.c zeus_local.c
|
||||
hanger: hanger.c
|
||||
tester: tester.c zeus_local.c
|
||||
.PHONY: all
|
28
dcj/tool/src/parunner/zeus/example.c
Normal file
28
dcj/tool/src/parunner/zeus/example.c
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "zeus.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_LEN 256
|
||||
|
||||
int main() {
|
||||
int my_id = ZEUS(MyNodeId)();
|
||||
int nof_nodes = ZEUS(NumberOfNodes)();
|
||||
printf("Nodeow jest %d, a ja mam numer %d.\n", nof_nodes, my_id);
|
||||
if (my_id < nof_nodes - 1) {
|
||||
char msg[MAX_LEN];
|
||||
snprintf(msg, sizeof(msg), "Hello from %d!", my_id);
|
||||
msg[MAX_LEN-1] = '\0';
|
||||
printf("Wysylam wiadomosc do %d.\n", my_id + 1);
|
||||
ZEUS(Send)(my_id + 1, msg, strlen(msg));
|
||||
}
|
||||
if (my_id > 0) {
|
||||
printf("Odbieram wiadomosc od %d.\n", my_id - 1);
|
||||
char msg[MAX_LEN];
|
||||
fflush(stdout);
|
||||
ZEUS(MessageInfo) mi = ZEUS(Receive)(my_id - 1, msg, sizeof(msg) - 1);
|
||||
msg[mi.length] = '\0';
|
||||
printf("Odebralem: %s\n", msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
47
dcj/tool/src/parunner/zeus/hanger.c
Normal file
47
dcj/tool/src/parunner/zeus/hanger.c
Normal file
@@ -0,0 +1,47 @@
|
||||
// A binary that terminates in the middle of receiving a message
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
const char command[] = {
|
||||
0x04, // receive
|
||||
0xff, // source = int32(-1) little endian
|
||||
0xff,
|
||||
0xff,
|
||||
0xff,
|
||||
0x01, // time = int32(1) little endian
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
static int GetFd(int dir) {
|
||||
const char* names[2] = { "ZSHANDLE_IN", "ZSHANDLE_OUT" };
|
||||
char* handle_s = getenv(names[dir]);
|
||||
if (handle_s == NULL)
|
||||
return -1;
|
||||
int handle = atoi(handle_s);
|
||||
return _open_osfhandle(handle, dir == 0 ? _O_RDONLY : _O_APPEND);
|
||||
}
|
||||
#else
|
||||
static int GetFd(int dir) {
|
||||
return 3 + dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int main() {
|
||||
char buf[20];
|
||||
FILE* cmdin = fdopen(GetFd(0), "r");
|
||||
assert(cmdin);
|
||||
FILE* cmdout = fdopen(GetFd(1), "w");
|
||||
assert(cmdout);
|
||||
assert(fwrite(command, sizeof(command), 1, cmdout) == 1);
|
||||
assert(fflush(cmdout) == 0);
|
||||
assert(fread(buf, 1, sizeof(buf), cmdin) >= 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
68
dcj/tool/src/parunner/zeus/tester.c
Normal file
68
dcj/tool/src/parunner/zeus/tester.c
Normal file
@@ -0,0 +1,68 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#ifdef WIN32
|
||||
#include "windows.h"
|
||||
#endif
|
||||
#include "zeus.h"
|
||||
|
||||
int main() {
|
||||
char buf[256];
|
||||
char messagebuf[256];
|
||||
printf("%d %d\n", ZEUS(MyNodeId)(), ZEUS(NumberOfNodes)());
|
||||
fflush(stdout);
|
||||
while (fgets(buf, sizeof(buf), stdin) != NULL) {
|
||||
if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
|
||||
buf[strlen(buf)-1] = '\0';
|
||||
if (strlen(buf) == 0)
|
||||
continue;
|
||||
switch (buf[0]) {
|
||||
case 'R':
|
||||
{
|
||||
int source;
|
||||
if (buf[1] == '*')
|
||||
source = -1;
|
||||
else
|
||||
source = buf[1] - 'a';
|
||||
ZEUS(MessageInfo) mi = ZEUS(Receive)(source, messagebuf, sizeof(messagebuf) - 1);
|
||||
messagebuf[mi.length] = '\0';
|
||||
printf("%d %d %s\n", mi.sender_id, mi.length, messagebuf);
|
||||
fflush(stdout);
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
{
|
||||
int dest = buf[1] - 'a';
|
||||
ZEUS(Send)(dest, buf + 2, strlen(buf + 2));
|
||||
}
|
||||
break;
|
||||
case 'Q':
|
||||
{
|
||||
int code = buf[1] - '0';
|
||||
exit(code);
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
{
|
||||
volatile int x = 0;
|
||||
for(x=0;x<(1<<25);x++) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'H':
|
||||
{
|
||||
#ifdef WIN32
|
||||
Sleep(100000);
|
||||
#else
|
||||
pause();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
72
dcj/tool/src/parunner/zeus/zeus.h
Normal file
72
dcj/tool/src/parunner/zeus/zeus.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2014, Onufry Wojtaszczuk <onufryw@gmail.com>
|
||||
#ifndef RECRUITING_DISTRIBUTED_API_ZEUS_H_
|
||||
#define RECRUITING_DISTRIBUTED_API_ZEUS_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ZEUS(s) zeus_##s
|
||||
|
||||
// The basic contestant-available API for the Zeus distributed contest.
|
||||
|
||||
// The number of nodes on which the solution is running.
|
||||
int ZEUS(NumberOfNodes)();
|
||||
|
||||
// The number (in the range [0 .. NumberOfNodes()-1]) of the node on which this
|
||||
// process is running.
|
||||
typedef int ZEUS(NodeId);
|
||||
ZEUS(NodeId) ZEUS(MyNodeId());
|
||||
|
||||
// It is guaranteed that messages sent between two given nodes will arrive in
|
||||
// order.
|
||||
// No ordering guarantees are given between messages to different nodes (in
|
||||
// particular, if A sends a message to B, then to C, and C sends a message to B
|
||||
// after receiving the message from A, it is possible for B to receive C's
|
||||
// message first).
|
||||
|
||||
// Send |bytes| bytes of |message| to node |target|. This is asynchronous (that
|
||||
// is, it does not wait for the receiver to Receive the message).
|
||||
// If |message| is shorter than |bytes|, behaviour is undefined.
|
||||
// If |target| is not a valid node id (not in [0 .. NumberOfNodes()-1]), will
|
||||
// crash.
|
||||
void ZEUS(Send)(ZEUS(NodeId) target, const char *message, int bytes);
|
||||
|
||||
typedef struct {
|
||||
// Id of the sending node.
|
||||
ZEUS(NodeId) sender_id;
|
||||
// Length of the received message in bytes.
|
||||
int length;
|
||||
} ZEUS(MessageInfo);
|
||||
|
||||
// Receive a message from the |source| node id, and copy the contents of the
|
||||
// message to |buffer|, up to |buffer_size| bytes. No extra characters (in
|
||||
// particular, a trailing '\0') will be appended to the message.
|
||||
// A special case is |source| equal to -1, in which case a message from any
|
||||
// other node can be received.
|
||||
// This call is blocking - that is, the function will not return until a message
|
||||
// is received.
|
||||
// A std::pair will be returned.
|
||||
// First element of the pair will be the id of the sending node.
|
||||
// Second element of the pair will be the length of the received message
|
||||
// in bytes.
|
||||
//
|
||||
// If the received message is larger than |buffer_size|, will crash.
|
||||
// If the received message is smaller than |buffer_size|, the rest of the buffer
|
||||
// contents are not modified.
|
||||
// If |buffer| is smaller than |buffer_size|, behaviour is undefined.
|
||||
// If |source| is neither -1 nor a valid node ID, will crash.
|
||||
ZEUS(MessageInfo) ZEUS(Receive)(ZEUS(NodeId) source, char *buffer, int buffer_size);
|
||||
|
||||
// Returns the list of nodes from which we have unreceived messages (thus,
|
||||
// calling Receive() with one of the returned node ids as the argument will
|
||||
// not block). The order in which the node IDs are given is not specified. Each
|
||||
// ID will be given once.
|
||||
//std::vector<NodeId> Poll(); // NOTE: NOT IMPLEMENTED
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RECRUITING_DISTRIBUTED_API_ZEUS_H_
|
147
dcj/tool/src/parunner/zeus/zeus_local.c
Normal file
147
dcj/tool/src/parunner/zeus/zeus_local.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "zeus.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define MAX_MESSAGE_SIZE (8*1024*1024)
|
||||
#define MAGIC 1736434764
|
||||
#define SEND 3
|
||||
#define RECV 4
|
||||
|
||||
static int initialized;
|
||||
static FILE* cmdin;
|
||||
static FILE* cmdout;
|
||||
static int nof_nodes;
|
||||
static int node_id;
|
||||
|
||||
static unsigned char ReadByte() {
|
||||
unsigned char c;
|
||||
assert(fread(&c, 1, 1, cmdin) == 1);
|
||||
return c;
|
||||
}
|
||||
|
||||
static int ReadInt() {
|
||||
int v = 0;
|
||||
int i;
|
||||
for(i=0;i<4;i++)
|
||||
v |= (int)(ReadByte()) << (8 * i);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void WriteByte(unsigned char c) {
|
||||
assert(fwrite(&c, 1, 1, cmdout) == 1);
|
||||
}
|
||||
|
||||
static void WriteInt(int v) {
|
||||
int i;
|
||||
for(i=0;i<4;i++)
|
||||
WriteByte((v >> (8 * i)) & 0xff);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static int GetFd(int dir) {
|
||||
const char* names[2] = { "ZSHANDLE_IN", "ZSHANDLE_OUT" };
|
||||
char* handle_s = getenv(names[dir]);
|
||||
if (handle_s == NULL)
|
||||
return -1;
|
||||
int handle = atoi(handle_s);
|
||||
return _open_osfhandle(handle, dir == 0 ? _O_RDONLY : _O_APPEND);
|
||||
}
|
||||
#else
|
||||
static int GetFd(int dir) {
|
||||
return 3 + dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void Init() {
|
||||
if (initialized)
|
||||
return;
|
||||
cmdin = fdopen(GetFd(0), "r");
|
||||
assert(cmdin != NULL);
|
||||
cmdout = fdopen(GetFd(1), "w");
|
||||
assert(cmdout != NULL);
|
||||
if (ReadInt() != MAGIC)
|
||||
assert(0);
|
||||
nof_nodes = ReadInt();
|
||||
assert(1 <= nof_nodes);
|
||||
node_id = ReadInt();
|
||||
assert(0 <= node_id && node_id < nof_nodes);
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
int ZEUS(NumberOfNodes)() {
|
||||
Init();
|
||||
return nof_nodes;
|
||||
}
|
||||
|
||||
ZEUS(NodeId) ZEUS(MyNodeId)() {
|
||||
Init();
|
||||
return node_id;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static int CurrentTime() {
|
||||
HANDLE me = GetCurrentProcess();
|
||||
FILETIME lpCreationTime, lpExitTime, lpKernelTime, lpUserTime;
|
||||
GetProcessTimes(me, &lpCreationTime, &lpExitTime, &lpKernelTime, &lpUserTime);
|
||||
ULONGLONG cTime =
|
||||
lpUserTime.dwLowDateTime +
|
||||
lpKernelTime.dwLowDateTime +
|
||||
(((ULONGLONG) lpUserTime.dwHighDateTime) << 32) +
|
||||
(((ULONGLONG) lpKernelTime.dwHighDateTime) << 32);
|
||||
return (int)(cTime / 10000);
|
||||
}
|
||||
#else
|
||||
static int CurrentTime() {
|
||||
static int warned;
|
||||
int time = clock();
|
||||
if (time == -1) {
|
||||
if (!warned) {
|
||||
warned = 1;
|
||||
fprintf(stderr, "Warning: clock() returned -1; time measurements will be bogus.\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return time * 1000 / CLOCKS_PER_SEC;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ZEUS(Send)(ZEUS(NodeId) target, const char* message, int bytes) {
|
||||
Init();
|
||||
assert(target >= 0 && target < nof_nodes);
|
||||
assert(bytes <= MAX_MESSAGE_SIZE);
|
||||
int i;
|
||||
WriteByte(SEND);
|
||||
WriteInt(target);
|
||||
WriteInt(CurrentTime());
|
||||
WriteInt(bytes);
|
||||
for(i=0;i<bytes;i++)
|
||||
WriteByte(message[i]);
|
||||
fflush(cmdout);
|
||||
}
|
||||
|
||||
ZEUS(MessageInfo) ZEUS(Receive)(ZEUS(NodeId) source, char* buffer, int buffer_size) {
|
||||
Init();
|
||||
assert(source >= -1 && source < nof_nodes);
|
||||
ZEUS(MessageInfo) mi;
|
||||
int i;
|
||||
WriteByte(RECV);
|
||||
WriteInt(source);
|
||||
WriteInt(CurrentTime());
|
||||
fflush(cmdout);
|
||||
if (ReadInt() != MAGIC + 1)
|
||||
assert(0);
|
||||
mi.sender_id = ReadInt();
|
||||
mi.length = ReadInt();
|
||||
assert(mi.length <= buffer_size);
|
||||
for(i=0;i<mi.length;i++)
|
||||
buffer[i] = ReadByte();
|
||||
return mi;
|
||||
}
|
25
dcj/tool/src/swig/message.i
Normal file
25
dcj/tool/src/swig/message.i
Normal file
@@ -0,0 +1,25 @@
|
||||
%module message
|
||||
|
||||
%{
|
||||
extern int NumberOfNodes();
|
||||
extern int MyNodeId();
|
||||
extern void PutChar(int target, char value);
|
||||
extern void PutInt(int target, int value);
|
||||
extern void PutLL(int target, long long value);
|
||||
extern void Send(int target);
|
||||
extern int Receive(int source);
|
||||
extern char GetChar(int source);
|
||||
extern int GetInt(int source);
|
||||
extern long long GetLL(int source);
|
||||
%}
|
||||
|
||||
extern int NumberOfNodes();
|
||||
extern int MyNodeId();
|
||||
extern void PutChar(int target, char value);
|
||||
extern void PutInt(int target, int value);
|
||||
extern void PutLL(int target, long long value);
|
||||
extern void Send(int target);
|
||||
extern int Receive(int source);
|
||||
extern char GetChar(int source);
|
||||
extern int GetInt(int source);
|
||||
extern long long GetLL(int source);
|
Reference in New Issue
Block a user