diff --git a/.gitignore b/.gitignore index 10e5b09..f86e8ea 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ rel/example_project doc _build rebar.lock + +*.DS_Store diff --git a/rebar.config b/rebar.config index 11b89f8..741c563 100644 --- a/rebar.config +++ b/rebar.config @@ -5,3 +5,6 @@ {branch, "master"}}} ]}. {escript_incl_apps, [getopt]}. +{edoc_opts, [ + {private, true} + ]}. diff --git a/src/effi.erl b/src/effi.erl index 42ad154..f7db546 100644 --- a/src/effi.erl +++ b/src/effi.erl @@ -16,6 +16,12 @@ %% See the License for the specific language governing permissions and %% limitations under the License. +%% @doc The standalone application entry point is {@link main/1}. +%% The create_port callback defined here is an abstract way to execute child +%% processes in foreign languages. +%% There are two foreign language interfaces, both implementing this callback, +%% {@link effi_script} (e.g., Perl, Python) and {@link effi_interact} (e.g., Bash, R). + %% @author Jörgen Brandt @@ -36,10 +42,11 @@ %% Callback definitions %% ------------------------------------------------------------ --callback create_port( Lang, Script, Dir ) -> {port(), string()} +-callback create_port( Lang, Script, Dir, Prof ) -> {port(), string()} when Lang :: atom(), Script :: string(), - Dir :: string(). + Dir :: string(), + Prof :: effi_profiling:profilingsettings(). %% ------------------------------------------------------------ %% Type definitions @@ -61,14 +68,14 @@ when Lang :: atom(), %% API export %% ------------------------------------------------------------ --export( [check_run/5, main/1] ). +-export( [check_run/6, main/1, get_optspec_lst/0] ). %% ------------------------------------------------------------ %% API functions %% ------------------------------------------------------------ %% main/1 -% +%% @doc Parses command line arguments and processes the request file using {@link runscript/4}. -spec main( ArgList::[string()] ) -> ok. main( [] ) -> @@ -91,7 +98,8 @@ main( CmdLine ) -> false -> {dir, Dir} = lists:keyfind( dir, 1, OptList ), {refactor, Refactor} = lists:keyfind( refactor, 1, OptList ), - runscript( Dir, Refactor, NonOptList ) + Prof = effi_profiling:get_profiling_settings_from_commandline_args(OptList, NonOptList), + runscript( Dir, Refactor, Prof, NonOptList ) end end end; @@ -99,21 +107,24 @@ main( CmdLine ) -> error( {Reason, Data} ) end. -%% check_run/5 -% --spec check_run( Lam, Fa, R, Dir, LibMap ) -> result() +%% check_run/6 +%% @doc Tries to process a parsed request file (Lam, Fa, R, LibMap) using {@link run/5} and handles possible failures. +%% Also measures the execution time. If successful, returns a summary, as generated by {@link get_summary/10}. +-spec check_run( Lam, Fa, R, Dir, LibMap, Prof ) -> result() when Lam :: lam(), Fa :: #{string() => [str()]}, R :: pos_integer(), Dir :: string(), - LibMap :: #{atom() => [string()]}. + LibMap :: #{atom() => [string()]}, + Prof :: effi_profiling:profilingsettings(). -check_run( Lam, Fa, R, Dir, LibMap ) +check_run( Lam, Fa, R, Dir, LibMap, Prof ) when is_tuple( Lam ), is_map( Fa ), is_integer( R ), R > 0, is_list( Dir ), - is_map( LibMap ) -> + is_map( LibMap ), + is_tuple(Prof) -> % take start time Tstart = trunc( os:system_time()/1000000 ), @@ -129,7 +140,7 @@ when is_tuple( Lam ), [] -> % run - case run( Lam, Fa, Dir, LibMap ) of + case run( Lam, Fa, Dir, LibMap, Prof ) of {failed, script_error, Data} -> {failed, script_error, R, Data}; {finished, RMap, Out} -> @@ -146,33 +157,42 @@ when is_tuple( Lam ), InputSizeMap = gather_file_size( Li, Fa, Dir ), OutputSizeMap = gather_file_size( Lo, RMap, Dir ), + % read the contents of the generated profiling file + ProfilingResult = case file:read_file( effi_profiling:out_file( Prof ) ) of + {error, _R1} -> <<>>; + {ok, X} -> X + end, + % delete the profiling file + file:delete( effi_profiling:out_file( Prof ) ), + % generate summary {finished, get_summary( Lam, Fa, R, RMap, Out, Tstart, Tdur, - InputSizeMap, OutputSizeMap )} + InputSizeMap, OutputSizeMap, ProfilingResult )} end end end. - -%% ------------------------------------------------------------ -%% Internal functions -%% ------------------------------------------------------------ - -%% print_usage/0 -% -print_usage() -> getopt:usage( get_optspec_lst(), "effi", " " ). - %% opt_spec_list/0 -% +%% @doc Returns the command line parameters that effi can parse, in a format that the getopt module understands. get_optspec_lst() -> [ {version, $v, "version", undefined, "Show Effi version"}, {help, $h, "help", undefined, "Show command line options"}, {cite, $c, "cite", undefined, "Show Bibtex entry for citation"}, {dir, $d, "dir", {string, "."}, "Working directory"}, - {refactor, $r, "refactor", {boolean, false}, "Refactor output files"} + {refactor, $r, "refactor", {boolean, false}, "Refactor output files"}, + {profiling, $p, "profiling", {boolean, false}, "Profile the process using the Pegasus Kickstart tool"}, + {profile_file, $x, "profile-out", {string, "_profile.xml"}, "Output file for profiling results"} ]. +%% ------------------------------------------------------------ +%% Internal functions +%% ------------------------------------------------------------ + +%% print_usage/0 +% +print_usage() -> getopt:usage( get_optspec_lst(), "effi", " " ). + %% print_bibtex_entry/0 % print_bibtex() -> io:format( "~n~s~n~n", [get_bibtex()] ). @@ -214,9 +234,9 @@ get_banner() -> print_vsn() -> io:format( "~s~n", [?VSN] ). -%% runscript/3 -% -runscript( Dir, Refactor, [RequestFile, SumFile] ) -> +%% runscript/4 +%% @doc Parses a request file, processes it using {@link check_run/6} and writes a summary to a file. +runscript( Dir, Refactor, ProfilingSetting, [RequestFile, SumFile] ) -> % read script from file B = case file:read_file( RequestFile ) of @@ -232,7 +252,7 @@ runscript( Dir, Refactor, [RequestFile, SumFile] ) -> end, % run script - Summary = case check_run( Lam, Fa, R, Dir, LibMap ) of + Summary = case check_run( Lam, Fa, R, Dir, LibMap, ProfilingSetting ) of {failed, script_error, R, {ActScript, Out}} -> @@ -286,12 +306,13 @@ runscript( Dir, Refactor, [RequestFile, SumFile] ) -> -runscript( _Dir, _Refactor, NonOptList ) -> +runscript( _Dir, _Refactor, _DoProfiling, NonOptList ) -> error( {request_and_summary_expected, NonOptList} ). -%% get_summary/5 -% --spec get_summary( Lam, Fa, R, Ret, Out, Tstart, Tdur, InSizeMap, OutSizeMap ) -> #{atom() => term()} +%% get_summary/10 +%% @doc takes information about an invocation and returns it as a map. +%% If the atom none is given for ProfilingResults, the results map will not contain +-spec get_summary( Lam, Fa, R, Ret, Out, Tstart, Tdur, InSizeMap, OutSizeMap, ProfilingResults ) -> #{atom() => term()} when Lam :: lam(), Fa :: #{string => [str()]}, R :: pos_integer(), @@ -300,9 +321,10 @@ when Lam :: lam(), Tstart :: integer(), Tdur :: integer(), InSizeMap :: #{string() => pos_integer()}, - OutSizeMap :: #{string() => pos_integer()}. + OutSizeMap :: #{string() => pos_integer()}, + ProfilingResults :: binary(). -get_summary( Lam, Fa, R, Ret, Out, Tstart, Tdur, InSizeMap, OutSizeMap ) +get_summary( Lam, Fa, R, Ret, Out, Tstart, Tdur, InSizeMap, OutSizeMap, ProfilingResults ) when is_tuple( Lam ), is_map( Fa ), is_map( Ret ), is_list( Out ), is_integer( Tstart ), Tstart >= 0, is_integer( Tdur ), Tdur >= 0, is_integer( R ), R > 0, @@ -318,8 +340,8 @@ when is_tuple( Lam ), is_map( Fa ), is_map( Ret ), is_list( Out ), out => Out, state => ok, out_size_map => OutSizeMap, - in_size_map => InSizeMap}. - + in_size_map => InSizeMap, + profiling_results => ProfilingResults}. gather_file_size( ParamLst, Fa, Dir ) -> @@ -356,47 +378,56 @@ acc_file( {str, File}, Acc, Dir ) -> true -> Acc end. --spec run( Lam, Fa, Dir, LibMap ) -> Result +-spec run( Lam, Fa, Dir, LibMap, Prof ) -> Result when Lam :: lam(), Fa :: #{string() => [str()]}, Dir :: string(), LibMap :: #{atom() => [string()]}, + Prof :: effi_profiling:profilingsettings(), Result :: {finished, #{string() => [str()]}, [binary()]} | {failed, script_error, {iolist(), [binary()]}}. -%% run/4 -% -run( Lam, Fa, Dir, LibMap ) +%% run/5 +%% @doc Creates a port using {@link create_port/5} and listens to it, i.e., collects and parses the output from the stdout. +%% Returns a tuple containing a status message (e.g., finished, failed) a result map and the program output. +run( Lam, Fa, Dir, LibMap, Prof ) when is_tuple( Lam ), is_map( Fa ), is_list( Dir ), - is_map( LibMap ) -> + is_map( LibMap ), + is_tuple(Prof) -> % create port - {Port, ActScript} = create_port( Lam, Fa, Dir, LibMap ), + {Port, ActScript} = create_port( Lam, Fa, Dir, LibMap, Prof ), % receive result listen_port( Port, ActScript ). -%% create_port/3 -% --spec create_port( Lam, Fa, Dir, LibMap ) -> {port(), string()} +%% create_port/5 +%% @doc This is a wrapper for the language specific port creation function. +%% Hands over the name of the language module (e.g., effi_python), the script to execute, +%% the working directory and the profiling options to the language specific create_port/4 function. +-spec create_port( Lam, Fa, Dir, LibMap, Prof ) -> {port(), string()} when Lam :: lam(), Fa :: #{string() => [str()]}, Dir :: string(), - LibMap :: #{atom() => [string()]}. + LibMap :: #{atom() => [string()]}, + Prof :: effi_profiling:profilingsettings(). -create_port( Lam, Fa, Dir, LibMap ) +create_port( Lam, Fa, Dir, LibMap, Prof ) when is_tuple( Lam ), is_map( Fa ), is_list( Dir ), - is_map( LibMap ) -> + is_map( LibMap ), + is_tuple(Prof) -> {lam, _Line, _LamName, Sign, Body} = Lam, {forbody, Lang, Script} = Body, {sign, Lo, Li} = Sign, + + % the module to call, depends on the language, e.g., effi_python Mod = list_to_atom("effi_" ++ atom_to_list(Lang)), % get Foreign Function Interface type @@ -426,12 +457,12 @@ when is_tuple( Lam ), Script2 = io_lib:format( "~s~n~s~n~s~n~s~n", [LibPath, Assign, Script1, Suffix] ), % run script - {_Port, _ActScript} = apply( FfiType, create_port, [Mod, Script2, Dir] ). + {_Port, _ActScript} = apply( FfiType, create_port, [Mod, Script2, Dir, Prof] ). %% listen_port/2 -% +%% @doc Processes the output of the child process (in a foreign language) and builds a result map from it. listen_port( Port, ActScript ) -> listen_port( Port, ActScript, <<>>, #{}, [] ). @@ -497,7 +528,6 @@ listen_port( Port, ActScript, LineAcc, ResultAcc, OutAcc ) -> end. - %% ============================================================================= %% Unit Tests %% ============================================================================= @@ -514,11 +544,10 @@ greet_bash_test_() -> Body = {forbody, bash, Script}, Lam = {lam, 12, "greet", Sign, Body}, Fa = #{"person" => [{str, "Jorgen"}]}, - - {finished, ResultMap, _} = run( Lam, Fa, Dir, #{} ), - + Prof = effi_profiling:get_profiling_settings(false, "profile.xml"), + {finished, ResultMap, _} = run( Lam, Fa, Dir, #{}, Prof ), + Result = maps:get( "out", ResultMap ), - ?_assertEqual( [{str, "Hello Jorgen"}], Result ). -endif. diff --git a/src/effi_interact.erl b/src/effi_interact.erl index fbf8fcf..07feb95 100644 --- a/src/effi_interact.erl +++ b/src/effi_interact.erl @@ -16,6 +16,9 @@ %% See the License for the specific language governing permissions and %% limitations under the License. +%% @doc Prototype for interactively interpreted languages, (e.g., Python). +%% As opposed to executing scripts directly ({@link effi_script}). + %% @author Jörgen Brandt @@ -49,24 +52,28 @@ %% Callback function exports %% ------------------------------------------------------------ --export( [create_port/3] ). +-export( [create_port/4] ). %% ------------------------------------------------------------ %% Callback functions %% ------------------------------------------------------------ -%% create_port/3 +%% create_port/4 % -create_port( Mod, Script, Dir ) +create_port( Mod, Script, Dir, Prof ) when is_atom( Mod ), is_list( Script ), - is_list( Dir ) -> + is_list( Dir ), + is_tuple( Prof ) -> % get interpreter Interpreter = apply( Mod, interpreter, [] ), + % get dynamic instrumentation wrapper + ProfilingWrapper = effi_profiling:wrapper_call( Prof ), + % get prefix Prefix = apply( Mod, prefix, [] ), @@ -77,7 +84,11 @@ when is_atom( Mod ), ActScript = string:join( [Prefix, Script, Suffix, ""], "\n" ), % run ticket - Port = open_port( {spawn, Interpreter}, + Command = case effi_profiling:is_on( Prof ) of + true -> string:join( [ProfilingWrapper, Interpreter], " " ); + false -> Interpreter + end, + Port = open_port( {spawn, Command}, [exit_status, stderr_to_stdout, binary, diff --git a/src/effi_profiling.erl b/src/effi_profiling.erl new file mode 100644 index 0000000..3478571 --- /dev/null +++ b/src/effi_profiling.erl @@ -0,0 +1,248 @@ +%% -*- erlang -*- +%% +%% Cuneiform: A Functional Language for Large Scale Scientific Data Analysis +%% +%% Copyright 2016 Jörgen Brandt, Marc Bux, and Ulf Leser +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc Tools for using pegasus kickstart as a lightweight dynamic instrumentation +%% wrapper. Contains logic to generate the wrapper instruction {@link wrapper_call/2}. +%% This is used in the create_port functions in {@link effi_interact} and {@link effi_script} +%% to collect information like peak memory usage, execution time, machine load averages, etc. +%% Also provides a simple configuration data structure (profilingsettings) including +%% accessor functions (e.g., {@link is_on/1}) to provide more flexibility in +%% changing the data structure. + +%% @author Carl Witt + + +-module( effi_profiling ). +-author( "Carl Witt " ). +-vsn( "0.1.1-snapshot" ). + +%% ------------------------------------------------------------ +%% Includes +%% ------------------------------------------------------------ + +-include( "effi.hrl" ). + +-ifdef( TEST ). +-include_lib( "eunit/include/eunit.hrl" ). +-endif. + +%% ------------------------------------------------------------ +%% Type definitions +%% ------------------------------------------------------------ + +% whether to enable profiling and where to write the results to +-type profilingsettings() :: {profiling, + Enabled::boolean(), % whether to instrument processes + OutFile::string() % where to write the profiling results (pegasus-kickstart generates an XML file) +}. + +%% ------------------------------------------------------------ +%% API export +%% ------------------------------------------------------------ + +-export( [get_profiling_settings_from_commandline_args/2, get_profiling_settings/2, + is_on/1, out_file/1, + wrapper_call/1, effi_arguments_for/1 ] ). + +%% ------------------------------------------------------------ +%% API functions +%% ------------------------------------------------------------ + +%% @doc Gives the prefix needed to wrap a foreign language command with the pegasus-kickstart profiling tool. +%% If profiling is off, returns an empty string. +%% e.g. {profiling, true, "./path/to/out.xml"} => "pegasus-kickstart -o - -l ./path/to/out.xml" +-spec wrapper_call( Prof ) -> string() +when Prof :: profilingsettings(). + +wrapper_call( Prof ) +when is_tuple( Prof ) -> + case is_on( Prof ) of + false -> ""; + true -> + % set the output file of kickstart to be in Dir + OutfileArgument = string:concat( "-l ", out_file( Prof ) ), + % profiler call which to which the actual application is passed + % connect the profiled process' stdin, stdout, and stderr to the default file descriptors + string:concat( "pegasus-kickstart -o - -i - -e - ", OutfileArgument ) + end. + +%% effi_arguments_for/1 +%% @doc generates the command line arguments for a given profiling setting, +%% e.g. {profiling, true, "./example.xml"} => "--profiling --profile-out ./example.xml" +%% This does the opposite of {@link get_profiling_settings_from_commandline_args/2}. +-spec effi_arguments_for( ProfilingSettings ) -> string() +when ProfilingSettings :: profilingsettings(). + +effi_arguments_for( {profiling, DoProfiling, Filename} ) -> + case DoProfiling of + false -> ""; + true -> + % Get the effi command line switch for profiling + {profiling, _, ProfSwitchName, _, _} = lists:keyfind( profiling, 1, effi:get_optspec_lst() ), + ProfArg = string:concat("--", ProfSwitchName), + + ProfOutArg = case out_file_name_fallback( Filename, none ) of + % the name is the default, not specifying it will have the same effect + {refactored, _} -> ""; + % the name is sensible (not the default) + {unchanged, _} -> + % Get the effi command line parameter for profile output file + {profile_file, _, ProfOutName, _, _} = lists:keyfind( profile_file, 1, effi:get_optspec_lst() ), + string:join(["--", ProfOutName, " ", Filename], "") + end, + + string:join( [ProfArg, ProfOutArg], " " ) + end. + +%% get_profiling_settings_from_commandline_args/2 +%% @doc Generate a profiling settings data structure, based on the command line arguments passed to effi. +%% Parses the values of the --profiling and --profile-out parameters. +%% This does the opposite of {@link effi_arguments_for/1}. +-spec get_profiling_settings_from_commandline_args( OptList, NonOptList ) -> ProfilingSettings +when + OptList :: [], + NonOptList :: [], + ProfilingSettings :: profilingsettings(). + +get_profiling_settings_from_commandline_args( OptList, NonOptList ) +when is_list(OptList), + is_list(NonOptList) -> + {profiling, DoProfiling} = lists:keyfind( profiling, 1, OptList ), + if + DoProfiling -> + % working directory of effi instantiation + {dir, Dir} = lists:keyfind( dir, 1, OptList ), + % the profile file parameter + {profile_file, OutFileName} = lists:keyfind( profile_file, 1, OptList ), + [RequestFile, _] = NonOptList, + {_changed, ProfileFileName} = out_file_name_fallback( OutFileName, filename:join( Dir, filename:basename( RequestFile ) ) ), + get_profiling_settings( true, ProfileFileName ); + true -> + get_profiling_settings( false, "" ) + end. + +%% out_file_name_fallback/2 +%% @doc checks whether the given file name equals the command line arguments default file name +%% and generates one based on a given base file name: e.g., the name of the request file, by +%% appending the suffix _profile.xml. If the given name is not the default, leaves it unchanged. +%% e.g., ("<requestfile>_profile.xml", "./path/to/request_file") => {refactored, "./path/to/request_file_profile.xml"} +%% e.g., ("./path/to/profile.xml", "./path/to/request_file") => {unchanged, "./path/to/profile.xml"} +-spec out_file_name_fallback( OutFileName, BaseFileName ) -> {atom(), string()} +when + OutFileName :: string(), + BaseFileName :: string(). + +out_file_name_fallback( OutFileName, BaseFileName ) -> + % get the default for the profile file parameter + {profile_file, _short, _long, {string, DefaultOutName}, _desc} = lists:keyfind( profile_file, 1, effi:get_optspec_lst()), + % if the default name is given then generate one, otherwise use it as is + case OutFileName == DefaultOutName of + true -> + {refactored, string:concat(BaseFileName, "_profile.xml")}; + false -> + {unchanged, OutFileName} + end. + +%% get_profiling_settings/2 +%% @doc Generates a profiling settings data structure, by explicitly setting +%% profiling to true or false and specifying the profile output name +-spec get_profiling_settings( DoProfiling, OutFileName ) -> ProfilingSettings +when + DoProfiling :: boolean(), + OutFileName :: string(), + ProfilingSettings :: profilingsettings(). + +get_profiling_settings( DoProfiling, OutFileName ) +when is_atom( DoProfiling ), + is_list( OutFileName ) -> + if + DoProfiling == true -> + {profiling, true, OutFileName}; + true -> + {profiling, false, ""} + end. + +%% is_on/1 +%% @doc Extract whether profiling is on or off from the profiling settings data structure +-spec is_on( ProfilingSettings ) -> boolean() +when ProfilingSettings :: profilingsettings(). + +is_on( {profiling, DoProfiling, _Filename } ) -> + DoProfiling. + +%% out_file/1 +%% @doc Extract the name of the profiling results file. +-spec out_file( ProfilingSettings ) -> string() +when ProfilingSettings :: profilingsettings(). + +out_file( {profiling, _DoProfiling, Filename } ) -> + Filename. + +%% ============================================================================= +%% Unit Tests +%% ============================================================================= + +-ifdef( TEST ). + +%% @hidden +from_explicit_test_() -> + + Prof = get_profiling_settings( true, "profile.xml" ), + + [ + ?_assertEqual( true, is_on(Prof) ), + ?_assertEqual( "profile.xml", out_file(Prof) ) + ]. + +%% @hidden +from_command_line_default_out_name_test_() -> + + CmdLine = "--profiling --dir workingdir// request.txt summary.txt", + {ok, {OptList, NonOptList}} = getopt:parse( effi:get_optspec_lst(), CmdLine ), + Prof = get_profiling_settings_from_commandline_args( OptList, NonOptList ), + + [ + ?_assertEqual( true, is_on(Prof) ), + ?_assertEqual( "workingdir/request.txt_profile.xml", out_file(Prof) ) + ]. + +%% @hidden +from_command_line_test_() -> + + CmdLine = "--profiling --profile-out profile.xml request.txt summary.txt", + {ok, {OptList, NonOptList}} = getopt:parse( effi:get_optspec_lst(), CmdLine ), + Prof = get_profiling_settings_from_commandline_args(OptList, NonOptList), + + [ + ?_assertEqual( true, is_on(Prof) ), + ?_assertEqual( "profile.xml", out_file(Prof) ) + ]. + +%% @hidden +effi_arguments_for_test_() -> + ?_assertEqual( "--profiling --profile-out ./example.xml", effi_arguments_for({profiling, true, "./example.xml"}) ). + +%% @hidden +out_file_name_fallback_test_() -> + [ + ?_assertEqual({refactored, "./path/to/request_file_profile.xml"}, out_file_name_fallback("_profile.xml", "./path/to/request_file")), + ?_assertEqual({unchanged, "./path/to/profile.xml"}, out_file_name_fallback("./path/to/profile.xml", "./path/to/request_file")) + ]. + + +-endif. diff --git a/src/effi_script.erl b/src/effi_script.erl index de2a4fc..4d54bd1 100644 --- a/src/effi_script.erl +++ b/src/effi_script.erl @@ -16,6 +16,9 @@ %% See the License for the specific language governing permissions and %% limitations under the License. +%% @doc Implements foreign language calls implemented as an executable file, +%% as opposed to executing a script using an interpreter explicitly (see {@link effi_interact}). + %% @author Jörgen Brandt @@ -39,7 +42,6 @@ -define( SCRIPT_FILE, "_script" ). -define( SCRIPT_MODE, 8#700 ). - %% ------------------------------------------------------------ %% Callback definitions %% ------------------------------------------------------------ @@ -58,20 +60,21 @@ %% Callback function exports %% ------------------------------------------------------------ --export( [create_port/3] ). +-export( [create_port/4] ). %% ------------------------------------------------------------ %% Callback functions %% ------------------------------------------------------------ -%% create_port/3 +%% create_port/4 % -create_port( Lang, Script, Dir ) +create_port( Lang, Script, Dir, Prof ) when is_atom( Lang ), is_list( Script ), - is_list( Dir ) -> + is_list( Dir ), + is_tuple(Prof) -> % get shebang Shebang = apply( Lang, shebang, [] ), @@ -85,6 +88,9 @@ when is_atom( Lang ), % get file extension Ext = apply( Lang, extension, [] ), + % get the call to the dynamic instrumentation wrapper, e.g. pegasus-kickstart /path/to/script.py + ProfilingWrapper = string:concat( effi_profiling:wrapper_call( Prof ), " " ), + % compose script filename ScriptFile = lists:flatten( [Dir, $/, ?SCRIPT_FILE, Ext] ), @@ -95,7 +101,7 @@ when is_atom( Lang ), file:change_mode( ScriptFile, ?SCRIPT_MODE ), % run ticket - Port = open_port( {spawn, ScriptFile}, + Port = open_port( {spawn, string:join([ProfilingWrapper, ScriptFile], " ")}, [exit_status, stderr_to_stdout, binary,