diff --git a/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl b/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl index d884bcc..74d035e 100644 --- a/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl +++ b/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl @@ -1,3 +1,29 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- -module(code_path_no_recurse_rt). -export([files/0, run/1]). diff --git a/inttest/erlc/erlc_rt.erl b/inttest/erlc/erlc_rt.erl index 50cdb83..f9fac1e 100644 --- a/inttest/erlc/erlc_rt.erl +++ b/inttest/erlc/erlc_rt.erl @@ -1,5 +1,29 @@ -%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 ft=erlang et +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- -module(erlc_rt). -export([files/0, run/1]). diff --git a/inttest/erlc/src/._do_not_compile.erl b/inttest/erlc/src/._do_not_compile.erl index c9d743b..f26f987 100644 --- a/inttest/erlc/src/._do_not_compile.erl +++ b/inttest/erlc/src/._do_not_compile.erl @@ -1,4 +1,4 @@ syntax error -this is file is here to verify that rebar does not try to +this file is here to verify that rebar does not try to compile files like OS X resource forks and should not be processed at all diff --git a/inttest/logging/logging_rt.erl b/inttest/logging/logging_rt.erl index 2b8e54b..d3e1c0f 100644 --- a/inttest/logging/logging_rt.erl +++ b/inttest/logging/logging_rt.erl @@ -1,3 +1,29 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- -module(logging_rt). -export([files/0, run/1]). @@ -72,7 +98,7 @@ check_output1(Cmd, Captured, Expected, Unexpected) -> false; {match, [Match]} -> retest:log( - console, + error, "Unexpected output when running cmd '~s':~n~s~n", [Cmd, Match]), {true, Match} diff --git a/inttest/profile/profile_rt.erl b/inttest/profile/profile_rt.erl new file mode 100644 index 0000000..1b87768 --- /dev/null +++ b/inttest/profile/profile_rt.erl @@ -0,0 +1,180 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(profile_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [ + {copy, "../../rebar", "rebar"} + ]. + +run(_Dir) -> + Cmd = "./rebar list-deps", + FprofFiles = ["fprof.trace", "fprof.analysis"], + EflameFiles = ["eflame.trace", "eflame.svg"], + + %% run a simple command (list-deps) without profiling + SharedExpected = "==> profile_rt \\(list-deps\\)", + %% run Cmd without profiling + retest:log(info, "Check '~s' without profiling~n", [Cmd]), + ok = check(Cmd, should_succeed, [SharedExpected], ["Profiling!"], + [], FprofFiles ++ EflameFiles), + + %% run Cmd with fprof profiling + retest:log(info, "Check '~s' with fprof profiling~n", [Cmd]), + ok = check(Cmd ++ " -p", should_succeed, + [SharedExpected, "Profiling!", "See fprof\.analysis"], + ["See eflame\.svg"], + FprofFiles, EflameFiles), + delete_files(FprofFiles), + + %% run Cmd with explicitly selected fprof profiling + retest:log(info, "Check '~s' with explicitly selected fprof profiling~n", + [Cmd]), + ok = check(Cmd ++ " -p profiler=fprof", should_succeed, + [SharedExpected, "Profiling!", "See fprof\.analysis"], + ["See eflame\.svg"], + FprofFiles, EflameFiles), + delete_files(FprofFiles), + + case code:lib_dir(eflame) of + {error, bad_name} -> + retest:log(info, + "eflame not found in code path. skip eflame test~n"), + ok; + _EflameDir -> + %% run Cmd with eflame profiling + retest:log(info, "Check '~s' with eflame profiling~n", [Cmd]), + ok = check(Cmd ++ " -p profiler=eflame", should_succeed, + [SharedExpected, "Profiling!", "See eflame\.svg"], + ["See fprof\.analysis"], + EflameFiles, FprofFiles), + delete_files(EflameFiles) + end, + + ok. + +check(Cmd, FailureMode, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles) -> + case {retest:sh(Cmd), FailureMode} of + {{error, _}=Error, should_succeed} -> + retest:log(error, "cmd '~s' failed:~n~p~n", [Cmd, Error]), + Error; + {{ok, CapturedOutput}, should_succeed} -> + JoinedOutput = string:join(CapturedOutput, "\n"), + check1(Cmd, JoinedOutput, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles); + {{error, {stopped, {_Rc, CapturedOutput}}}, should_fail} -> + JoinedOutput = string:join(CapturedOutput, "\n"), + check1(Cmd, JoinedOutput, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles) + end. + +check1(Cmd, CapturedOutput, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles) -> + ReOpts = [{capture, all, list}], + ExMatches = + lists:zf( + fun(Pattern) -> + case re:run(CapturedOutput, Pattern, ReOpts) of + nomatch -> + retest:log(error, + "Expected pattern '~s' missing " + "in the following output:~n" + "=== BEGIN ===~n~s~n=== END ===~n", + [Pattern, CapturedOutput]), + {true, Pattern}; + {match, _} -> + false + end + end, ExpectedOutput), + + UnExMatches = + lists:zf( + fun(Pattern) -> + case re:run(CapturedOutput, Pattern, ReOpts) of + nomatch -> + false; + {match, [Match]} -> + retest:log( + console, + "Unexpected output when running cmd '~s':~n~s~n", + [Cmd, Match]), + {true, Match} + end + end, UnexpectedOutput), + + ExFiles = + lists:zf( + fun(File) -> + case filelib:is_regular(File) of + true -> + false; + false -> + retest:log(error, + "Expected file missing: ~s~n", [File]), + {true, File} + end + end, ExpectedFiles), + + UnExFiles = + lists:zf( + fun(File) -> + case filelib:is_regular(File) of + true -> + retest:log(error, + "Unexpected file found: ~s~n", [File]), + {true, File}; + false -> + false + end + end, UnexpectedFiles), + + case {ExMatches, UnExMatches, ExFiles, UnExFiles} of + {[], [], [], []} -> + ok; + _ -> + error + end. + +delete_files(Files) -> + lists:foreach(fun(File) -> ok = file:delete(File) end, Files). + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff --git a/priv/shell-completion/bash/rebar b/priv/shell-completion/bash/rebar index 375566c..8a67996 100644 --- a/priv/shell-completion/bash/rebar +++ b/priv/shell-completion/bash/rebar @@ -53,6 +53,7 @@ _rebar() appid= \ overlay_vars= \ previous_release= \ + profiler= \ nodeid= \ root_dir= \ skip_deps=true \ diff --git a/priv/shell-completion/zsh/_rebar b/priv/shell-completion/zsh/_rebar index 0218f9e..a994e31 100644 --- a/priv/shell-completion/zsh/_rebar +++ b/priv/shell-completion/zsh/_rebar @@ -63,6 +63,7 @@ _rebar () { 'appid[Application id]:' \ 'overlay_vars[Overlay variables file]:' \ 'previous_release[Previous release path]:' \ + 'profiler[Select profiler]::flag:(fprof eflame)' \ 'nodeid[Node id]:' \ 'root_dir[Reltool config root directory]::directory:_files -/' \ 'skip_deps[Skip deps]::flag:(true false)' \ diff --git a/rebar.config b/rebar.config index 1c62a55..6e5e175 100644 --- a/rebar.config +++ b/rebar.config @@ -19,6 +19,7 @@ [{"(XC - UC) || (XU - X - B - (\"escript\":\"foldl\"/\"3\") - (\"eunit_test\":\"function_wrapper\"/\"2\") + - (\"eflame\":\"apply\"/\"5\") - (\"abnfc\":\"file\"/\"2\") - (\"erlydtl\":\"compile\"/\"3\") - (\"lfe_comp\":\"file\"/\"2\") diff --git a/src/rebar.erl b/src/rebar.erl index a43da5f..52eb1e7 100644 --- a/src/rebar.erl +++ b/src/rebar.erl @@ -104,13 +104,8 @@ run(RawArgs) -> case rebar_config:get_xconf(BaseConfig1, enable_profiling, false) of true -> - io:format("Profiling!\n"), - try - fprof:apply(fun run_aux/2, [BaseConfig1, Cmds]) - after - ok = fprof:profile(), - ok = fprof:analyse([{dest, "fprof.analysis"}]) - end; + ?CONSOLE("Profiling!\n", []), + profile(BaseConfig1, Cmds); false -> run_aux(BaseConfig1, Cmds) end. @@ -160,6 +155,42 @@ init_config1(BaseConfig) -> AbsCwd = filename:absname(rebar_utils:get_cwd()), rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd). +profile(BaseConfig1, Commands) -> + Profiler = rebar_config:get_global(BaseConfig1, profiler, "fprof"), + profile(BaseConfig1, Commands, list_to_atom(Profiler)). + +profile(Config, Commands, fprof) -> + try + fprof:apply(fun run_aux/2, [Config, Commands]) + after + ok = fprof:profile(), + ok = fprof:analyse([{dest, "fprof.analysis"}]), + ?CONSOLE("See fprof.analysis (generated from fprof.trace)~n", []), + ok + end; +profile(Config, Commands, eflame) -> + case code:lib_dir(eflame) of + {error, bad_name} -> + ?ABORT("eflame not found in code path~n", []), + ok; + EflameDir -> + Trace = "eflame.trace", + try + eflame:apply(normal_with_children, Trace, + rebar, run, [Config, Commands]) + after + %% generate flame graph + Script = filename:join(EflameDir, "stack_to_flame.sh"), + Svg = "eflame.svg", + %% stack_to_flame.sh < eflame.trace > eflame.png + Cmd = ?FMT("~s < ~s > ~s", [Script, Trace, Svg]), + {ok, []} = rebar_utils:sh(Cmd, [{use_stdout, false}, + abort_on_error]), + ?CONSOLE("See eflame.svg (generated from eflame.trace)~n", []), + ok + end + end. + run_aux(BaseConfig, Commands) -> %% Make sure crypto is running case crypto:start() of @@ -439,7 +470,10 @@ option_spec_list() -> {defines, $D, undefined, string, "Define compiler macro"}, {jobs, $j, "jobs", integer, JobsHelp}, {config, $C, "config", string, "Rebar config file to use"}, - {profile, $p, "profile", undefined, "Profile this run of rebar"}, + {profile, $p, "profile", undefined, + "Profile this run of rebar. Via profiler= you can optionally select " + "either fprof (default) or eflame. The result can be found in " + "fprof.analysis or eflame.svg."}, {keep_going, $k, "keep-going", undefined, "Keep running after a command fails"}, {recursive, $r, "recursive", boolean,