diff options
Diffstat (limited to 'lib/tools/src')
-rw-r--r-- | lib/tools/src/Makefile | 19 | ||||
-rw-r--r-- | lib/tools/src/cover.erl | 1109 | ||||
-rw-r--r-- | lib/tools/src/cover_web.erl | 23 | ||||
-rw-r--r-- | lib/tools/src/cprof.erl | 21 | ||||
-rw-r--r-- | lib/tools/src/eprof.erl | 47 | ||||
-rw-r--r-- | lib/tools/src/fprof.erl | 21 | ||||
-rw-r--r-- | lib/tools/src/instrument.erl | 21 | ||||
-rw-r--r-- | lib/tools/src/lcnt.erl | 397 | ||||
-rw-r--r-- | lib/tools/src/make.erl | 21 | ||||
-rw-r--r-- | lib/tools/src/tags.erl | 30 | ||||
-rw-r--r-- | lib/tools/src/tools.app.src | 45 | ||||
-rw-r--r-- | lib/tools/src/tools.appup.src | 41 | ||||
-rw-r--r-- | lib/tools/src/xref.erl | 21 | ||||
-rw-r--r-- | lib/tools/src/xref.hrl | 25 | ||||
-rw-r--r-- | lib/tools/src/xref_base.erl | 19 | ||||
-rw-r--r-- | lib/tools/src/xref_compiler.erl | 25 | ||||
-rw-r--r-- | lib/tools/src/xref_parser.yrl | 19 | ||||
-rw-r--r-- | lib/tools/src/xref_reader.erl | 55 | ||||
-rw-r--r-- | lib/tools/src/xref_scanner.erl | 27 | ||||
-rw-r--r-- | lib/tools/src/xref_utils.erl | 23 |
20 files changed, 1208 insertions, 801 deletions
diff --git a/lib/tools/src/Makefile b/lib/tools/src/Makefile index e606b97a48..9fcfb79628 100644 --- a/lib/tools/src/Makefile +++ b/lib/tools/src/Makefile @@ -3,16 +3,17 @@ # # Copyright Ericsson AB 1996-2013. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. +# 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 # -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# 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. # # %CopyrightEnd% # diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 13d9aefb0c..8d1cb96504 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2013. All Rights Reserved. +%% Copyright Ericsson AB 2001-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -77,8 +78,11 @@ compile/1, compile/2, compile_module/1, compile_module/2, compile_directory/0, compile_directory/1, compile_directory/2, compile_beam/1, compile_beam_directory/0, compile_beam_directory/1, - analyse/1, analyse/2, analyse/3, analyze/1, analyze/2, analyze/3, + analyse/0, analyse/1, analyse/2, analyse/3, + analyze/0, analyze/1, analyze/2, analyze/3, + analyse_to_file/0, analyse_to_file/1, analyse_to_file/2, analyse_to_file/3, + analyze_to_file/0, analyze_to_file/1, analyze_to_file/2, analyze_to_file/3, async_analyse_to_file/1,async_analyse_to_file/2, async_analyse_to_file/3, async_analyze_to_file/1, @@ -89,8 +93,9 @@ flush/1, stop/0, stop/1]). -export([remote_start/1,get_main_node/0]). -%-export([bump/5]). --export([transform/4]). % for test purposes + +%% Used internally to ensure we upgrade the code to the latest version. +-export([main_process_loop/1,remote_process_loop/1]). -record(main_state, {compiled=[], % [{Module,File}] imported=[], % [{Module,File,ImportFile}] @@ -108,9 +113,9 @@ line = '_' % integer() }). -define(BUMP_REC_NAME,bump). +-define(CHUNK_SIZE, 20000). -record(vars, {module, % atom() Module name - vsn, % atom() init_info=[], % [{M,F,A,C,L}] @@ -132,7 +137,7 @@ -define(SERVER, cover_server). %% Line doesn't matter. --define(BLOCK(Expr), {block,0,[Expr]}). +-define(BLOCK(Expr), {block,erl_anno:new(0),[Expr]}). -define(BLOCK1(Expr), if element(1, Expr) =:= block -> @@ -181,10 +186,11 @@ start(Node) when is_atom(Node) -> start(Nodes) -> call({start_nodes,remove_myself(Nodes,[])}). -%% compile(ModFile) -> -%% compile(ModFile, Options) -> -%% compile_module(ModFile) -> Result -%% compile_module(ModFile, Options) -> Result +%% compile(ModFiles) -> +%% compile(ModFiles, Options) -> +%% compile_module(ModFiles) -> Result +%% compile_module(ModFiles, Options) -> Result +%% ModFiles = ModFile | [ModFile] %% ModFile = Module | File %% Module = atom() %% File = string() @@ -198,18 +204,27 @@ compile(ModFile, Options) -> compile_module(ModFile) when is_atom(ModFile); is_list(ModFile) -> compile_module(ModFile, []). -compile_module(Module, Options) when is_atom(Module), is_list(Options) -> - compile_module(atom_to_list(Module), Options); -compile_module(File, Options) when is_list(File), is_list(Options) -> - WithExt = case filename:extension(File) of - ".erl" -> - File; - _ -> - File++".erl" - end, - AbsFile = filename:absname(WithExt), - [R] = compile_modules([AbsFile], Options), - R. +compile_module(ModFile, Options) when is_atom(ModFile); + is_list(ModFile), is_integer(hd(ModFile)) -> + [R] = compile_module([ModFile], Options), + R; +compile_module(ModFiles, Options) when is_list(Options) -> + AbsFiles = + [begin + File = + case ModFile of + _ when is_atom(ModFile) -> atom_to_list(ModFile); + _ when is_list(ModFile) -> ModFile + end, + WithExt = case filename:extension(File) of + ".erl" -> + File; + _ -> + File++".erl" + end, + filename:absname(WithExt) + end || ModFile <- ModFiles], + compile_modules(AbsFiles, Options). %% compile_directory() -> %% compile_directory(Dir) -> @@ -230,25 +245,9 @@ compile_directory(Dir) when is_list(Dir) -> compile_directory(Dir, Options) when is_list(Dir), is_list(Options) -> case file:list_dir(Dir) of {ok, Files} -> - - %% Filter out all erl files (except cover.erl) - ErlFileNames = - lists:filter(fun("cover.erl") -> - false; - (File) -> - case filename:extension(File) of - ".erl" -> true; - _ -> false - end - end, - Files), - - %% Create a list of .erl file names (incl path) and call - %% compile_modules/2 with the list of file names. - ErlFiles = lists:map(fun(ErlFileName) -> - filename:join(Dir, ErlFileName) - end, - ErlFileNames), + ErlFiles = [filename:join(Dir, File) || + File <- Files, + filename:extension(File) =:= ".erl"], compile_modules(ErlFiles, Options); Error -> Error @@ -256,13 +255,14 @@ compile_directory(Dir, Options) when is_list(Dir), is_list(Options) -> compile_modules(Files,Options) -> Options2 = filter_options(Options), - compile_modules(Files,Options2,[]). + %% compile_modules(Files,Options2,[]). + call({compile, Files, Options2}). -compile_modules([File|Files], Options, Result) -> - R = call({compile, File, Options}), - compile_modules(Files,Options,[R|Result]); -compile_modules([],_Opts,Result) -> - reverse(Result). +%% compile_modules([File|Files], Options, Result) -> +%% R = call({compile, File, Options}), +%% compile_modules(Files,Options,[R|Result]); +%% compile_modules([],_Opts,Result) -> +%% lists:reverse(Result). filter_options(Options) -> lists:filter(fun(Option) -> @@ -280,30 +280,17 @@ filter_options(Options) -> %% ModFile - see compile/1 %% Result - see compile/1 %% Reason = non_existing | already_cover_compiled -compile_beam(Module) when is_atom(Module) -> - case code:which(Module) of - non_existing -> +compile_beam(ModFile0) when is_atom(ModFile0); + is_list(ModFile0), is_integer(hd(ModFile0)) -> + case compile_beams([ModFile0]) of + [{error,{non_existing,_}}] -> + %% Backwards compatibility {error,non_existing}; - ?TAG -> - compile_beam(Module,?TAG); - File -> - compile_beam(Module,File) + [Result] -> + Result end; -compile_beam(File) when is_list(File) -> - {WithExt,WithoutExt} - = case filename:rootname(File,".beam") of - File -> - {File++".beam",File}; - Rootname -> - {File,Rootname} - end, - AbsFile = filename:absname(WithExt), - Module = list_to_atom(filename:basename(WithoutExt)), - compile_beam(Module,AbsFile). - -compile_beam(Module,File) -> - call({compile_beam,Module,File}). - +compile_beam(ModFiles) when is_list(ModFiles) -> + compile_beams(ModFiles). %% compile_beam_directory(Dir) -> [Result] | {error,Reason} @@ -320,43 +307,60 @@ compile_beam_directory() -> compile_beam_directory(Dir) when is_list(Dir) -> case file:list_dir(Dir) of {ok, Files} -> - - %% Filter out all beam files (except cover.beam) - BeamFileNames = - lists:filter(fun("cover.beam") -> - false; - (File) -> - case filename:extension(File) of - ".beam" -> true; - _ -> false - end - end, - Files), - - %% Create a list of .beam file names (incl path) and call - %% compile_beam/1 for each such file name - BeamFiles = lists:map(fun(BeamFileName) -> - filename:join(Dir, BeamFileName) - end, - BeamFileNames), + BeamFiles = [filename:join(Dir, File) || + File <- Files, + filename:extension(File) =:= ".beam"], compile_beams(BeamFiles); Error -> Error end. -compile_beams(Files) -> - compile_beams(Files,[]). -compile_beams([File|Files],Result) -> - R = compile_beam(File), - compile_beams(Files,[R|Result]); -compile_beams([],Result) -> - reverse(Result). - +compile_beams(ModFiles0) -> + ModFiles = get_mods_and_beams(ModFiles0,[]), + call({compile_beams,ModFiles}). -%% analyse(Module) -> -%% analyse(Module, Analysis) -> -%% analyse(Module, Level) -> -%% analyse(Module, Analysis, Level) -> {ok,Answer} | {error,Error} +get_mods_and_beams([Module|ModFiles],Acc) when is_atom(Module) -> + case code:which(Module) of + non_existing -> + get_mods_and_beams(ModFiles,[{error,{non_existing,Module}}|Acc]); + File -> + get_mods_and_beams([{Module,File}|ModFiles],Acc) + end; +get_mods_and_beams([File|ModFiles],Acc) when is_list(File) -> + {WithExt,WithoutExt} + = case filename:rootname(File,".beam") of + File -> + {File++".beam",File}; + Rootname -> + {File,Rootname} + end, + AbsFile = filename:absname(WithExt), + Module = list_to_atom(filename:basename(WithoutExt)), + get_mods_and_beams([{Module,AbsFile}|ModFiles],Acc); +get_mods_and_beams([{Module,File}|ModFiles],Acc) -> + %% Check for duplicates + case lists:keyfind(Module,2,Acc) of + {ok,Module,File} -> + %% Duplicate, but same file so ignore + get_mods_and_beams(ModFiles,Acc); + {ok,Module,_OtherFile} -> + %% Duplicate and differnet file - error + get_mods_and_beams(ModFiles,[{error,{duplicate,Module}}|Acc]); + _ -> + get_mods_and_beams(ModFiles,[{ok,Module,File}|Acc]) + end; +get_mods_and_beams([],Acc) -> + lists:reverse(Acc). + + +%% analyse(Modules) -> +%% analyse(Analysis) -> +%% analyse(Level) -> +%% analyse(Modules, Analysis) -> +%% analyse(Modules, Level) -> +%% analyse(Analysis, Level) +%% analyse(Modules, Analysis, Level) -> {ok,Answer} | {error,Error} +%% Modules = Module | [Module] %% Module = atom() %% Analysis = coverage | calls %% Level = line | clause | function | module @@ -369,48 +373,74 @@ compile_beams([],Result) -> %% N = A = C = integer() %% Value = {Cov,NotCov} | Calls %% Cov = NotCov = Calls = integer() -%% Error = {not_cover_compiled,Module} +%% Error = {not_cover_compiled,Module} | not_main_node +-define(is_analysis(__A__), + (__A__=:=coverage orelse __A__=:=calls)). +-define(is_level(__L__), + (__L__=:=line orelse __L__=:=clause orelse + __L__=:=function orelse __L__=:=module)). +analyse() -> + analyse('_'). + +analyse(Analysis) when ?is_analysis(Analysis) -> + analyse('_', Analysis); +analyse(Level) when ?is_level(Level) -> + analyse('_', Level); analyse(Module) -> analyse(Module, coverage). -analyse(Module, Analysis) when Analysis=:=coverage; Analysis=:=calls -> + +analyse(Analysis, Level) when ?is_analysis(Analysis) andalso + ?is_level(Level) -> + analyse('_', Analysis, Level); +analyse(Module, Analysis) when ?is_analysis(Analysis) -> analyse(Module, Analysis, function); -analyse(Module, Level) when Level=:=line; Level=:=clause; Level=:=function; - Level=:=module -> +analyse(Module, Level) when ?is_level(Level) -> analyse(Module, coverage, Level). -analyse(Module, Analysis, Level) when is_atom(Module), - Analysis=:=coverage; Analysis=:=calls, - Level=:=line; Level=:=clause; - Level=:=function; Level=:=module -> + +analyse(Module, Analysis, Level) when ?is_analysis(Analysis), + ?is_level(Level) -> call({{analyse, Analysis, Level}, Module}). +analyze() -> analyse( ). analyze(Module) -> analyse(Module). analyze(Module, Analysis) -> analyse(Module, Analysis). analyze(Module, Analysis, Level) -> analyse(Module, Analysis, Level). -%% analyse_to_file(Module) -> -%% analyse_to_file(Module, Options) -> -%% analyse_to_file(Module, OutFile) -> -%% analyse_to_file(Module, OutFile, Options) -> {ok,OutFile} | {error,Error} +%% analyse_to_file() -> +%% analyse_to_file(Modules) -> +%% analyse_to_file(Modules, Options) -> +%% Modules = Module | [Module] %% Module = atom() %% OutFile = string() %% Options = [Option] -%% Option = html +%% Option = html | {outfile,filename()} | {outdir,dirname()} %% Error = {not_cover_compiled,Module} | no_source_code_found | %% {file,File,Reason} %% File = string() %% Reason = term() -analyse_to_file(Module) when is_atom(Module) -> - analyse_to_file(Module, outfilename(Module,[]), []). -analyse_to_file(Module, []) when is_atom(Module) -> - analyse_to_file(Module, outfilename(Module,[]), []); -analyse_to_file(Module, Options) when is_atom(Module), - is_list(Options), is_atom(hd(Options)) -> - analyse_to_file(Module, outfilename(Module,Options), Options); -analyse_to_file(Module, OutFile) when is_atom(Module), is_list(OutFile) -> - analyse_to_file(Module, OutFile, []). -analyse_to_file(Module, OutFile, Options) when is_atom(Module), is_list(OutFile) -> - call({{analyse_to_file, OutFile, Options}, Module}). - +%% +%% Kept for backwards compatibility: +%% analyse_to_file(Modules, OutFile) -> +%% analyse_to_file(Modules, OutFile, Options) -> {ok,OutFile} | {error,Error} +analyse_to_file() -> + analyse_to_file('_'). +analyse_to_file(Arg) -> + case is_options(Arg) of + true -> + analyse_to_file('_',Arg); + false -> + analyse_to_file(Arg,[]) + end. +analyse_to_file(Module, OutFile) when is_list(OutFile), is_integer(hd(OutFile)) -> + %% Kept for backwards compatibility + analyse_to_file(Module, [{outfile,OutFile}]); +analyse_to_file(Module, Options) when is_list(Options) -> + call({{analyse_to_file, Options}, Module}). +analyse_to_file(Module, OutFile, Options) when is_list(OutFile) -> + %% Kept for backwards compatibility + analyse_to_file(Module,[{outfile,OutFile}|Options]). + +analyze_to_file() -> analyse_to_file(). analyze_to_file(Module) -> analyse_to_file(Module). analyze_to_file(Module, OptOrOut) -> analyse_to_file(Module, OptOrOut). analyze_to_file(Module, OutFile, Options) -> @@ -423,6 +453,15 @@ async_analyse_to_file(Module, OutFileOrOpts) -> async_analyse_to_file(Module, OutFile, Options) -> do_spawn(?MODULE, analyse_to_file, [Module, OutFile, Options]). +is_options([html]) -> + true; % this is not 100% safe - could be a module named html... +is_options([html|Opts]) -> + is_options(Opts); +is_options([{Opt,_}|_]) when Opt==outfile; Opt==outdir -> + true; +is_options(_) -> + false. + do_spawn(M,F,A) -> spawn_link(fun() -> case apply(M,F,A) of @@ -440,13 +479,16 @@ async_analyze_to_file(Module, OutFileOrOpts) -> async_analyze_to_file(Module, OutFile, Options) -> async_analyse_to_file(Module, OutFile, Options). -outfilename(Module,Opts) -> - case lists:member(html,Opts) of - true -> - atom_to_list(Module)++".COVER.html"; - false -> - atom_to_list(Module)++".COVER.out" - end. +outfilename(undefined, Module, HTML) -> + outfilename(Module, HTML); +outfilename(OutDir, Module, HTML) -> + filename:join(OutDir, outfilename(Module, HTML)). + +outfilename(Module, true) -> + atom_to_list(Module)++".COVER.html"; +outfilename(Module, false) -> + atom_to_list(Module)++".COVER.out". + %% export(File) %% export(File,Module) -> ok | {error,Reason} @@ -591,7 +633,7 @@ init_main(Starter) -> ,{write_concurrency, true} ]), ets:new(?COVER_CLAUSE_TABLE, [set, public, named_table]), - ets:new(?BINARY_TABLE, [set, named_table]), + ets:new(?BINARY_TABLE, [set, public, named_table]), ets:new(?COLLECTION_TABLE, [set, public, named_table]), ets:new(?COLLECTION_CLAUSE_TABLE, [set, public, named_table]), net_kernel:monitor_nodes(true), @@ -605,49 +647,19 @@ main_process_loop(State) -> reply(From, {ok,StartedNodes}), main_process_loop(State1); - {From, {compile, File, Options}} -> - case do_compile(File, Options) of - {ok, Module} -> - remote_load_compiled(State#main_state.nodes,[{Module,File}]), - reply(From, {ok, Module}), - Compiled = add_compiled(Module, File, - State#main_state.compiled), - Imported = remove_imported(Module,State#main_state.imported), - main_process_loop(State#main_state{compiled = Compiled, - imported = Imported}); - error -> - reply(From, {error, File}), - main_process_loop(State) - end; + {From, {compile, Files, Options}} -> + {R,S} = do_compile(Files, Options, State), + reply(From,R), + %% This module (cover) could have been reloaded. Make + %% sure we run the new code. + ?MODULE:main_process_loop(S); - {From, {compile_beam, Module, BeamFile0}} -> - Compiled0 = State#main_state.compiled, - case get_beam_file(Module,BeamFile0,Compiled0) of - {ok,BeamFile} -> - UserOptions = get_compile_options(Module,BeamFile), - {Reply,Compiled} = - case do_compile_beam(Module,BeamFile,UserOptions) of - {ok, Module} -> - remote_load_compiled(State#main_state.nodes, - [{Module,BeamFile}]), - C = add_compiled(Module,BeamFile,Compiled0), - {{ok,Module},C}; - error -> - {{error, BeamFile}, Compiled0}; - {error,Reason} -> % no abstract code - {{error, {Reason, BeamFile}}, Compiled0} - end, - reply(From,Reply), - Imported = remove_imported(Module,State#main_state.imported), - main_process_loop(State#main_state{compiled = Compiled, - imported = Imported}); - {error,no_beam} -> - %% The module has first been compiled from .erl, and now - %% someone tries to compile it from .beam - reply(From, - {error,{already_cover_compiled,no_beam_found,Module}}), - main_process_loop(State) - end; + {From, {compile_beams, ModsAndFiles}} -> + {R,S} = do_compile_beams(ModsAndFiles,State), + reply(From,R), + %% This module (cover) could have been reloaded. Make + %% sure we run the new code. + ?MODULE:main_process_loop(S); {From, {export,OutFile,Module}} -> spawn(fun() -> @@ -732,6 +744,16 @@ main_process_loop(State) -> unregister(?SERVER), reply(From, ok); + {From, {{analyse, Analysis, Level}, '_'}} -> + R = analyse_all(Analysis, Level, State), + reply(From, R), + main_process_loop(State); + + {From, {{analyse, Analysis, Level}, Modules}} when is_list(Modules) -> + R = analyse_list(Modules, Analysis, Level, State), + reply(From, R), + main_process_loop(State); + {From, {{analyse, Analysis, Level}, Module}} -> S = try Loaded = is_loaded(Module, State), @@ -748,15 +770,23 @@ main_process_loop(State) -> end, main_process_loop(S); - {From, {{analyse_to_file, OutFile, Opts},Module}} -> + {From, {{analyse_to_file, Opts},'_'}} -> + R = analyse_all_to_file(Opts, State), + reply(From,R), + main_process_loop(State); + + {From, {{analyse_to_file, Opts},Modules}} when is_list(Modules) -> + R = analyse_list_to_file(Modules, Opts, State), + reply(From,R), + main_process_loop(State); + + {From, {{analyse_to_file, Opts},Module}} -> S = try Loaded = is_loaded(Module, State), - spawn(fun() -> - ?SPAWN_DBG(analyse_to_file, - {Module,OutFile, Opts}), + spawn_link(fun() -> + ?SPAWN_DBG(analyse_to_file,{Module,Opts}), do_parallel_analysis_to_file( - Module, OutFile, Opts, - Loaded, From, State) + Module, Opts, Loaded, From, State) end), State catch throw:Reason -> @@ -857,7 +887,7 @@ remote_process_loop(State) -> {remote,load_compiled,Compiled} -> Compiled1 = load_compiled(Compiled,State#remote_state.compiled), remote_reply(State#remote_state.main_node, ok), - remote_process_loop(State#remote_state{compiled=Compiled1}); + ?MODULE:remote_process_loop(State#remote_state{compiled=Compiled1}); {remote,unload,UnloadedModules} -> unload(UnloadedModules), @@ -874,11 +904,15 @@ remote_process_loop(State) -> {remote,collect,Module,CollectorPid} -> self() ! {remote,collect,Module,CollectorPid, ?SERVER}; - {remote,collect,Module,CollectorPid,From} -> + {remote,collect,Modules0,CollectorPid,From} -> + Modules = case Modules0 of + '_' -> [M || {M,_} <- State#remote_state.compiled]; + _ -> Modules0 + end, spawn(fun() -> ?SPAWN_DBG(remote_collect, - {Module, CollectorPid, From}), - do_collect(Module, CollectorPid, From) + {Modules, CollectorPid, From}), + do_collect(Modules, CollectorPid, From) end), remote_process_loop(State); @@ -919,39 +953,51 @@ remote_process_loop(State) -> end. -do_collect(Module, CollectorPid, From) -> - AllMods = - case Module of - '_' -> ets:tab2list(?COVER_CLAUSE_TABLE); - _ -> ets:lookup(?COVER_CLAUSE_TABLE, Module) - end, - - %% Sending clause by clause in order to avoid large lists +do_collect(Modules, CollectorPid, From) -> pmap( - fun({_Mod,Clauses}) -> - lists:map(fun(Clause) -> - send_collected_data(Clause, CollectorPid) - end,Clauses) - end,AllMods), + fun(Module) -> + Pattern = {#bump{module=Module, _='_'}, '$1'}, + MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}], + Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE), + send_chunks(Match, CollectorPid, []) + end,Modules), CollectorPid ! done, remote_reply(From, ok). -send_collected_data({M,F,A,C,_L}, CollectorPid) -> - Pattern = - {#bump{module=M, function=F, arity=A, clause=C}, '_'}, - Bumps = ets:match_object(?COVER_TABLE, Pattern), - %% Reset - lists:foreach(fun({Bump,_N}) -> - ets:insert(?COVER_TABLE, {Bump,0}) - end, - Bumps), - CollectorPid ! {chunk,Bumps}. +send_chunks('$end_of_table', _CollectorPid, Mons) -> + get_downs(Mons); +send_chunks({Chunk,Continuation}, CollectorPid, Mons) -> + Mon = spawn_monitor( + fun() -> + lists:foreach(fun({Bump,_N}) -> + ets:insert(?COVER_TABLE, {Bump,0}) + end, + Chunk) end), + send_chunk(CollectorPid,Chunk), + send_chunks(ets:select(Continuation), CollectorPid, [Mon|Mons]). -reload_originals([{Module,_File}|Compiled]) -> - do_reload_original(Module), - reload_originals(Compiled); -reload_originals([]) -> - ok. +send_chunk(CollectorPid,Chunk) -> + CollectorPid ! {chunk,Chunk,self()}, + receive continue -> ok end. + +get_downs([]) -> + ok; +get_downs(Mons) -> + receive + {'DOWN', Ref, _Type, Pid, _Reason} = Down -> + case lists:member({Pid,Ref},Mons) of + true -> + get_downs(lists:delete({Pid,Ref},Mons)); + false -> + %% This should be handled somewhere else + self() ! Down, + get_downs(Mons) + end + end. + +reload_originals(Compiled) -> + Modules = [M || {M,_} <- Compiled], + pmap(fun do_reload_original/1, Modules). do_reload_original(Module) -> case code:which(Module) of @@ -972,14 +1018,24 @@ load_compiled([{Module,File,Binary,InitialTable}|Compiled],Acc) -> %% Make sure the #bump{} records are available *before* the %% module is loaded. insert_initial_data(InitialTable), - NewAcc = - case code:load_binary(Module, ?TAG, Binary) of - {module,Module} -> - add_compiled(Module, File, Acc); - _ -> - do_clear(Module), - Acc - end, + Sticky = case code:is_sticky(Module) of + true -> + code:unstick_mod(Module), + true; + false -> + false + end, + NewAcc = case code:load_binary(Module, ?TAG, Binary) of + {module,Module} -> + add_compiled(Module, File, Acc); + _ -> + do_clear(Module), + Acc + end, + case Sticky of + true -> code:stick_mod(Module); + false -> ok + end, load_compiled(Compiled,NewAcc); load_compiled([],Acc) -> Acc. @@ -1094,15 +1150,40 @@ remote_load_compiled(_Nodes, [], [], _ModNum) -> ok; remote_load_compiled(Nodes, Compiled, Acc, ModNum) when Compiled == []; ModNum == ?MAX_MODS -> + RemoteLoadData = get_downs_r(Acc), lists:foreach( fun(Node) -> - remote_call(Node,{remote,load_compiled,Acc}) + remote_call(Node,{remote,load_compiled,RemoteLoadData}) end, Nodes), remote_load_compiled(Nodes, Compiled, [], 0); remote_load_compiled(Nodes, [MF | Rest], Acc, ModNum) -> remote_load_compiled( - Nodes, Rest, [get_data_for_remote_loading(MF) | Acc], ModNum + 1). + Nodes, Rest, + [spawn_job_r(fun() -> get_data_for_remote_loading(MF) end) | Acc], + ModNum + 1). + +spawn_job_r(Fun) -> + spawn_monitor(fun() -> exit(Fun()) end). + +get_downs_r([]) -> + []; +get_downs_r(Mons) -> + receive + {'DOWN', Ref, _Type, Pid, R={_,_,_,_}} -> + [R|get_downs_r(lists:delete({Pid,Ref},Mons))]; + {'DOWN', Ref, _Type, Pid, Reason} = Down -> + case lists:member({Pid,Ref},Mons) of + true -> + %% Something went really wrong - don't hang! + exit(Reason); + false -> + %% This should be handled somewhere else + self() ! Down, + get_downs_r(Mons) + end + end. + %% Read all data needed for loading a cover compiled module on a remote node %% Binary is the beam code for the module and InitialTable is the initial @@ -1139,11 +1220,11 @@ remote_reset(Module,Nodes) -> Nodes). %% Collect data from remote nodes - used for analyse or stop(Node) -remote_collect(Module,Nodes,Stop) -> +remote_collect(Modules,Nodes,Stop) -> pmap(fun(Node) -> ?SPAWN_DBG(remote_collect, - {Module, Nodes, Stop}), - do_collection(Node, Module, Stop) + {Modules, Nodes, Stop}), + do_collection(Node, Modules, Stop) end, Nodes). @@ -1164,8 +1245,9 @@ do_collection(Node, Module, Stop) -> collector_proc() -> ?SPAWN_DBG(collector_proc, []), receive - {chunk,Chunk} -> + {chunk,Chunk,From} -> insert_in_collection_table(Chunk), + From ! continue, collector_proc(); done -> ok @@ -1257,12 +1339,12 @@ add_imported(M, F1, ImportFile, [{M,_F2,ImportFiles}|Imported], Acc) -> dont_import; false -> NewEntry = {M, F1, [ImportFile | ImportFiles]}, - {ok, reverse([NewEntry | Acc]) ++ Imported} + {ok, lists:reverse([NewEntry | Acc]) ++ Imported} end; add_imported(M, F, ImportFile, [H|Imported], Acc) -> add_imported(M, F, ImportFile, Imported, [H|Acc]); add_imported(M, F, ImportFile, [], Acc) -> - {ok, reverse([{M, F, [ImportFile]} | Acc])}. + {ok, lists:reverse([{M, F, [ImportFile]} | Acc])}. %% Removes a module from the list of imported modules and writes a warning %% This is done when a module is compiled. @@ -1285,6 +1367,19 @@ add_compiled(Module, File, [H|Compiled]) -> add_compiled(Module, File, []) -> [{Module,File}]. +are_loaded([Module|Modules], State, Loaded, Imported, Error) -> + try is_loaded(Module,State) of + {loaded,File} -> + are_loaded(Modules, State, [{Module,File}|Loaded], Imported, Error); + {imported,File,_} -> + are_loaded(Modules, State, Loaded, [{Module,File}|Imported], Error) + catch throw:_ -> + are_loaded(Modules, State, Loaded, Imported, + [{not_cover_compiled,Module}|Error]) + end; +are_loaded([], _State, Loaded, Imported, Error) -> + {Loaded, Imported, Error}. + is_loaded(Module, State) -> case get_file(Module, State#main_state.compiled) of {ok, File} -> @@ -1359,18 +1454,75 @@ get_compiled_still_loaded(Nodes,Compiled0) -> %%%--Compilation--------------------------------------------------------- -%% do_compile(File, Options) -> {ok,Module} | {error,Error} -do_compile(File, UserOptions) -> +do_compile_beams(ModsAndFiles, State) -> + Result0 = pmap(fun({ok,Module,File}) -> + do_compile_beam(Module,File,State); + (Error) -> + Error + end, + ModsAndFiles), + Compiled = [{M,F} || {ok,M,F} <- Result0], + remote_load_compiled(State#main_state.nodes,Compiled), + fix_state_and_result(Result0,State,[]). + +do_compile_beam(Module,BeamFile0,State) -> + case get_beam_file(Module,BeamFile0,State#main_state.compiled) of + {ok,BeamFile} -> + UserOptions = get_compile_options(Module,BeamFile), + case do_compile_beam1(Module,BeamFile,UserOptions) of + {ok, Module} -> + {ok,Module,BeamFile}; + error -> + {error, BeamFile}; + {error,Reason} -> % no abstract code + {error, {Reason, BeamFile}} + end; + {error,no_beam} -> + %% The module has first been compiled from .erl, and now + %% someone tries to compile it from .beam + {error,{already_cover_compiled,no_beam_found,Module}} + end. + +fix_state_and_result([{ok,Module,BeamFile}|Rest],State,Acc) -> + Compiled = add_compiled(Module,BeamFile,State#main_state.compiled), + Imported = remove_imported(Module,State#main_state.imported), + NewState = State#main_state{compiled=Compiled,imported=Imported}, + fix_state_and_result(Rest,NewState,[{ok,Module}|Acc]); +fix_state_and_result([Error|Rest],State,Acc) -> + fix_state_and_result(Rest,State,[Error|Acc]); +fix_state_and_result([],State,Acc) -> + {lists:reverse(Acc),State}. + + +do_compile(Files, Options, State) -> + Result0 = pmap(fun(File) -> + do_compile(File, Options) + end, + Files), + Compiled = [{M,F} || {ok,M,F} <- Result0], + remote_load_compiled(State#main_state.nodes,Compiled), + fix_state_and_result(Result0,State,[]). + +do_compile(File, Options) -> + case do_compile1(File, Options) of + {ok, Module} -> + {ok,Module,File}; + error -> + {error,File} + end. + +%% do_compile1(File, Options) -> {ok,Module} | error +do_compile1(File, UserOptions) -> Options = [debug_info,binary,report_errors,report_warnings] ++ UserOptions, case compile:file(File, Options) of {ok, Module, Binary} -> - do_compile_beam(Module,Binary,UserOptions); + do_compile_beam1(Module,Binary,UserOptions); error -> error end. %% Beam is a binary or a .beam file name -do_compile_beam(Module,Beam,UserOptions) -> +do_compile_beam1(Module,Beam,UserOptions) -> %% Clear database do_clear(Module), @@ -1383,9 +1535,9 @@ do_compile_beam(Module,Beam,UserOptions) -> {error,E}; encrypted_abstract_code=E -> {error,E}; - {Vsn,Code} -> + {raw_abstract_v1,Code} -> Forms0 = epp:interpret_file_attribute(Code), - {Forms,Vars} = transform(Vsn, Forms0, Module, Beam), + {Forms,Vars} = transform(Forms0, Module), %% We need to recover the source from the compilation %% info otherwise the newly compiled module will have @@ -1400,7 +1552,7 @@ do_compile_beam(Module,Beam,UserOptions) -> {module, Module} -> %% Store info about all function clauses in database - InitInfo = reverse(Vars#vars.init_info), + InitInfo = lists:reverse(Vars#vars.init_info), ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}), %% Store binary code so it can be loaded on remote nodes @@ -1411,7 +1563,11 @@ do_compile_beam(Module,Beam,UserOptions) -> _Error -> do_clear(Module), error - end + end; + {_VSN,_Code} -> + %% Wrong version of abstract code. Just report that there + %% is no abstract code. + {error,no_abstract_code} end. get_abstract_code(Module, Beam) -> @@ -1445,28 +1601,9 @@ get_compile_info(Module, Beam) -> [] end. -transform(Vsn, Code, Module, Beam) when Vsn=:=abstract_v1; Vsn=:=abstract_v2 -> - Vars0 = #vars{module=Module, vsn=Vsn}, +transform(Code, Module) -> MainFile=find_main_filename(Code), - {ok, MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), - - %% Add module and export information to the munged forms - %% Information about module_info must be removed as this function - %% is added at compilation - {ok, {Module, [{exports,Exports1}]}} = beam_lib:chunks(Beam, [exports]), - Exports2 = lists:filter(fun(Export) -> - case Export of - {module_info,_} -> false; - _ -> true - end - end, - Exports1), - Forms = [{attribute,1,module,Module}, - {attribute,2,export,Exports2}]++ MungedForms, - {Forms,Vars}; -transform(Vsn=raw_abstract_v1, Code, Module, _Beam) -> - MainFile=find_main_filename(Code), - Vars0 = #vars{module=Module, vsn=Vsn}, + Vars0 = #vars{module=Module}, {ok,MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), {MungedForms,Vars}. @@ -1486,7 +1623,7 @@ transform_2([Form0|Forms],MungedForms,Vars,MainFile,Switch) -> transform_2(Forms,[MungedForm|MungedForms],Vars2,MainFile,NewSwitch) end; transform_2([],MungedForms,Vars,_,_) -> - {ok, reverse(MungedForms), Vars}. + {ok, lists:reverse(MungedForms), Vars}. %% Expand short-circuit Boolean expressions. expand(Expr) -> @@ -1500,18 +1637,18 @@ expand({clause,Line,Pattern,Guards,Body}, Vs, N) -> expand({op,_Line,'andalso',ExprL,ExprR}, Vs, N) -> {ExpandedExprL,N2} = expand(ExprL, Vs, N), {ExpandedExprR,N3} = expand(ExprR, Vs, N2), - LineL = element(2, ExpandedExprL), + Anno = element(2, ExpandedExprL), {bool_switch(ExpandedExprL, ExpandedExprR, - {atom,LineL,false}, + {atom,Anno,false}, Vs, N3), N3 + 1}; expand({op,_Line,'orelse',ExprL,ExprR}, Vs, N) -> {ExpandedExprL,N2} = expand(ExprL, Vs, N), {ExpandedExprR,N3} = expand(ExprR, Vs, N2), - LineL = element(2, ExpandedExprL), + Anno = element(2, ExpandedExprL), {bool_switch(ExpandedExprL, - {atom,LineL,true}, + {atom,Anno,true}, ExpandedExprR, Vs, N3), N3 + 1}; @@ -1553,14 +1690,9 @@ aux_var(Vars, N) -> end. %% This code traverses the abstract code, stored as the abstract_code -%% chunk in the BEAM file, as described in absform(3) for Erlang/OTP R8B -%% (Vsn=abstract_v2). -%% The abstract format after preprocessing differs slightly from the abstract -%% format given eg using epp:parse_form, this has been noted in comments. -%% The switch is turned off when we encounter other files then the main file. +%% chunk in the BEAM file, as described in absform(3). +%% The switch is turned off when we encounter other files than the main file. %% This way we will be able to exclude functions defined in include files. -munge({function,0,module_info,_Arity,_Clauses},_Vars,_MainFile,_Switch) -> - ignore; % module_info will be added again when the forms are recompiled munge({function,Line,Function,Arity,Clauses},Vars,_MainFile,on) -> Vars2 = Vars#vars{function=Function, arity=Arity, @@ -1618,14 +1750,14 @@ munge_clauses([Clause|Clauses], Vars, Lines, MClauses) -> MClauses]) end; munge_clauses([], Vars, Lines, MungedClauses) -> - {reverse(MungedClauses), Vars#vars{lines = Lines}}. + {lists:reverse(MungedClauses), Vars#vars{lines = Lines}}. munge_body(Expr, Vars) -> munge_body(Expr, Vars, [], []). munge_body([Expr|Body], Vars, MungedBody, LastExprBumpLines) -> %% Here is the place to add a call to cover:bump/6! - Line = element(2, Expr), + Line = erl_anno:line(element(2, Expr)), Lines = Vars#vars.lines, case lists:member(Line,Lines) of true -> % already a bump at this line @@ -1662,7 +1794,7 @@ munge_body([Expr|Body], Vars, MungedBody, LastExprBumpLines) -> munge_body(Body, Vars3, MungedExprs1, NewBumps) end; munge_body([], Vars, MungedBody, _LastExprBumpLines) -> - {reverse(MungedBody), Vars}. + {lists:reverse(MungedBody), Vars}. %%% Fix last expression (OTP-8188). A typical example: %%% @@ -1742,6 +1874,8 @@ fix_expr(T, Line, Bump) when is_tuple(T) -> fix_expr(E, _Line, _Bump) -> E. +fix_clauses([], _Line, _Bump) -> + []; fix_clauses(Cs, Line, Bump) -> case bumps_line(lists:last(Cs), Line) of true -> @@ -1759,17 +1893,18 @@ fix_cls([Cl | Cls], Line, Bump) -> false -> {clause,CL,P,G,Body} = Cl, UniqueVarName = list_to_atom(lists:concat(["$cover$ ",Line])), - V = {var,0,UniqueVarName}, + A = erl_anno:new(0), + V = {var,A,UniqueVarName}, [Last|Rest] = lists:reverse(Body), - Body1 = lists:reverse(Rest, [{match,0,V,Last},Bump,V]), + Body1 = lists:reverse(Rest, [{match,A,V,Last},Bump,V]), [{clause,CL,P,G,Body1} | fix_cls(Cls, Line, Bump)] end. bumps_line(E, L) -> try bumps_line1(E, L) catch true -> true end. -bumps_line1({call,0,{remote,0,{atom,0,ets},{atom,0,update_counter}}, - [{atom,0,?COVER_TABLE},{tuple,0,[_,_,_,_,_,{integer,0,Line}]},_]}, +bumps_line1({call,_,{remote,_,{atom,_,ets},{atom,_,update_counter}}, + [{atom,_,?COVER_TABLE},{tuple,_,[_,_,_,_,_,{integer,_,Line}]},_]}, Line) -> throw(true); bumps_line1([E | Es], Line) -> @@ -1783,15 +1918,16 @@ bumps_line1(_, _) -> %%% End of fix of last expression. bump_call(Vars, Line) -> - {call,0,{remote,0,{atom,0,ets},{atom,0,update_counter}}, - [{atom,0,?COVER_TABLE}, - {tuple,0,[{atom,0,?BUMP_REC_NAME}, - {atom,0,Vars#vars.module}, - {atom,0,Vars#vars.function}, - {integer,0,Vars#vars.arity}, - {integer,0,Vars#vars.clause}, - {integer,0,Line}]}, - {integer,0,1}]}. + A = erl_anno:new(0), + {call,A,{remote,A,{atom,A,ets},{atom,A,update_counter}}, + [{atom,A,?COVER_TABLE}, + {tuple,A,[{atom,A,?BUMP_REC_NAME}, + {atom,A,Vars#vars.module}, + {atom,A,Vars#vars.function}, + {integer,A,Vars#vars.arity}, + {integer,A,Vars#vars.clause}, + {integer,A,Line}]}, + {integer,A,1}]}. munge_expr({match,Line,ExprL,ExprR}, Vars) -> {MungedExprL, Vars2} = munge_expr(ExprL, Vars), @@ -1800,16 +1936,35 @@ munge_expr({match,Line,ExprL,ExprR}, Vars) -> munge_expr({tuple,Line,Exprs}, Vars) -> {MungedExprs, Vars2} = munge_exprs(Exprs, Vars, []), {{tuple,Line,MungedExprs}, Vars2}; -munge_expr({record,Line,Expr,Exprs}, Vars) -> - %% Only for Vsn=raw_abstract_v1 - {MungedExprName, Vars2} = munge_expr(Expr, Vars), +munge_expr({record,Line,Name,Exprs}, Vars) -> + {MungedExprFields, Vars2} = munge_exprs(Exprs, Vars, []), + {{record,Line,Name,MungedExprFields}, Vars2}; +munge_expr({record,Line,Arg,Name,Exprs}, Vars) -> + {MungedArg, Vars2} = munge_expr(Arg, Vars), {MungedExprFields, Vars3} = munge_exprs(Exprs, Vars2, []), - {{record,Line,MungedExprName,MungedExprFields}, Vars3}; + {{record,Line,MungedArg,Name,MungedExprFields}, Vars3}; munge_expr({record_field,Line,ExprL,ExprR}, Vars) -> - %% Only for Vsn=raw_abstract_v1 - {MungedExprL, Vars2} = munge_expr(ExprL, Vars), - {MungedExprR, Vars3} = munge_expr(ExprR, Vars2), - {{record_field,Line,MungedExprL,MungedExprR}, Vars3}; + {MungedExprR, Vars2} = munge_expr(ExprR, Vars), + {{record_field,Line,ExprL,MungedExprR}, Vars2}; +munge_expr({map,Line,Fields}, Vars) -> + %% EEP 43 + {MungedFields, Vars2} = munge_exprs(Fields, Vars, []), + {{map,Line,MungedFields}, Vars2}; +munge_expr({map,Line,Arg,Fields}, Vars) -> + %% EEP 43 + {MungedArg, Vars2} = munge_expr(Arg, Vars), + {MungedFields, Vars3} = munge_exprs(Fields, Vars2, []), + {{map,Line,MungedArg,MungedFields}, Vars3}; +munge_expr({map_field_assoc,Line,Name,Value}, Vars) -> + %% EEP 43 + {MungedName, Vars2} = munge_expr(Name, Vars), + {MungedValue, Vars3} = munge_expr(Value, Vars2), + {{map_field_assoc,Line,MungedName,MungedValue}, Vars3}; +munge_expr({map_field_exact,Line,Name,Value}, Vars) -> + %% EEP 43 + {MungedName, Vars2} = munge_expr(Name, Vars), + {MungedValue, Vars3} = munge_expr(Value, Vars2), + {{map_field_exact,Line,MungedName,MungedValue}, Vars3}; munge_expr({cons,Line,ExprH,ExprT}, Vars) -> {MungedExprH, Vars2} = munge_expr(ExprH, Vars), {MungedExprT, Vars3} = munge_expr(ExprT, Vars2), @@ -1871,18 +2026,12 @@ munge_expr({'try',Line,Body,Clauses,CatchClauses,After}, Vars) -> {MungedAfter, Vars4} = munge_body(After, Vars3), {{'try',Line,MungedBody,MungedClauses,MungedCatchClauses,MungedAfter}, Vars4}; -%% Difference in abstract format after preprocessing: Funs get an extra -%% element Extra. -%% NOT NECESSARY FOR Vsn=raw_abstract_v1 -munge_expr({'fun',Line,{function,Name,Arity},_Extra}, Vars) -> - {{'fun',Line,{function,Name,Arity}}, Vars}; -munge_expr({'fun',Line,{clauses,Clauses},_Extra}, Vars) -> - {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), - {{'fun',Line,{clauses,MungedClauses}}, Vars2}; munge_expr({'fun',Line,{clauses,Clauses}}, Vars) -> - %% Only for Vsn=raw_abstract_v1 {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), {{'fun',Line,{clauses,MungedClauses}}, Vars2}; +munge_expr({named_fun,Line,Name,Clauses}, Vars) -> + {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), + {{named_fun,Line,Name,MungedClauses}, Vars2}; munge_expr({bin,Line,BinElements}, Vars) -> {MungedBinElements,Vars2} = munge_exprs(BinElements, Vars, []), {{bin,Line,MungedBinElements}, Vars2}; @@ -1901,7 +2050,7 @@ munge_exprs([Expr|Exprs], Vars, MungedExprs) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_exprs(Exprs, Vars2, [MungedExpr|MungedExprs]); munge_exprs([], Vars, MungedExprs) -> - {reverse(MungedExprs), Vars}. + {lists:reverse(MungedExprs), Vars}. %% Every qualifier is decorated with a counter. munge_qualifiers(Qualifiers, Vars) -> @@ -1920,7 +2069,7 @@ munge_qs([Expr|Qs], Vars, MQs) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_qs1(Qs, L, MungedExpr, Vars, Vars2, MQs); munge_qs([], Vars, MQs) -> - {reverse(MQs), Vars}. + {lists:reverse(MQs), Vars}. munge_qs1(Qs, Line, NQ, Vars, Vars2, MQs) -> case new_bumps(Vars2, Vars) of @@ -1946,10 +2095,21 @@ common_elems(L1, L2) -> collect(Nodes) -> %% local node AllClauses = ets:tab2list(?COVER_CLAUSE_TABLE), - pmap(fun move_modules/1,AllClauses), - + Mon1 = spawn_monitor(fun() -> pmap(fun move_modules/1,AllClauses) end), + %% remote nodes - remote_collect('_',Nodes,false). + Mon2 = spawn_monitor(fun() -> remote_collect('_',Nodes,false) end), + get_downs([Mon1,Mon2]). + +%% Collect data for a list of modules +collect(Modules,Nodes) -> + MS = [{{'$1','_'},[{'==','$1',M}],['$_']} || M <- Modules], + Clauses = ets:select(?COVER_CLAUSE_TABLE,MS), + Mon1 = spawn_monitor(fun() -> pmap(fun move_modules/1,Clauses) end), + + %% remote nodes + Mon2 = spawn_monitor(fun() -> remote_collect('_',Nodes,false) end), + get_downs([Mon1,Mon2]). %% Collect data for one module collect(Module,Clauses,Nodes) -> @@ -1957,25 +2117,26 @@ collect(Module,Clauses,Nodes) -> move_modules({Module,Clauses}), %% remote nodes - remote_collect(Module,Nodes,false). + remote_collect([Module],Nodes,false). %% When analysing, the data from the local ?COVER_TABLE is moved to the %% ?COLLECTION_TABLE. Resetting data in ?COVER_TABLE move_modules({Module,Clauses}) -> ets:insert(?COLLECTION_CLAUSE_TABLE,{Module,Clauses}), - move_clauses(Clauses). + Pattern = {#bump{module=Module, _='_'}, '_'}, + MatchSpec = [{Pattern,[],['$_']}], + Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE), + do_move_module(Match). -move_clauses([{M,F,A,C,_L}|Clauses]) -> - Pattern = {#bump{module=M, function=F, arity=A, clause=C}, '_'}, - Bumps = ets:match_object(?COVER_TABLE,Pattern), +do_move_module({Bumps,Continuation}) -> lists:foreach(fun({Key,Val}) -> ets:insert(?COVER_TABLE, {Key,0}), insert_in_collection_table(Key,Val) end, Bumps), - move_clauses(Clauses); -move_clauses([]) -> + do_move_module(ets:select(Continuation)); +do_move_module('$end_of_table') -> ok. %% Given a .beam file, find the .erl file. Look first in same directory as @@ -1993,7 +2154,13 @@ find_source(Module, File0) -> throw_file(filename:join([BeamDir, "..", "src", Base])), %% Not in ../src: look for source path in compile info, but %% first look relative the beam directory. - Info = lists:keyfind(source, 1, Module:module_info(compile)), + Info = + try lists:keyfind(source, 1, Module:module_info(compile)) + catch error:undef -> + %% The module might have been imported + %% and the beam not available + throw({beam, File0}) + end, false == Info andalso throw({beam, File0}), %% stripped {source, SrcFile} = Info, throw_file(splice(BeamDir, SrcFile)), %% below ../src @@ -2033,6 +2200,26 @@ splice(BeamDir, SrcFile) -> revsplit(Path) -> lists:reverse(filename:split(Path)). +analyse_list(Modules, Analysis, Level, State) -> + {LoadedMF, ImportedMF, Error} = are_loaded(Modules, State, [], [], []), + Loaded = [M || {M,_} <- LoadedMF], + Imported = [M || {M,_} <- ImportedMF], + collect(Loaded, State#main_state.nodes), + MS = [{{'$1','_'},[{'==','$1',M}],['$_']} || M <- Loaded ++ Imported], + AllClauses = ets:select(?COLLECTION_CLAUSE_TABLE,MS), + Fun = fun({Module,Clauses}) -> + do_analyse(Module, Analysis, Level, Clauses) + end, + {result, lists:flatten(pmap(Fun, AllClauses)), Error}. + +analyse_all(Analysis, Level, State) -> + collect(State#main_state.nodes), + AllClauses = ets:tab2list(?COLLECTION_CLAUSE_TABLE), + Fun = fun({Module,Clauses}) -> + do_analyse(Module, Analysis, Level, Clauses) + end, + {result, lists:flatten(pmap(Fun, AllClauses)), []}. + do_parallel_analysis(Module, Analysis, Level, Loaded, From, State) -> analyse_info(Module,State#main_state.imported), C = case Loaded of @@ -2047,7 +2234,7 @@ do_parallel_analysis(Module, Analysis, Level, Loaded, From, State) -> Clauses end, R = do_analyse(Module, Analysis, Level, C), - reply(From, R). + reply(From, {ok,R}). %% do_analyse(Module, Analysis, Level, Clauses)-> {ok,Answer} | {error,Error} %% Clauses = [{Module,Function,Arity,Clause,Lines}] @@ -2066,37 +2253,44 @@ do_analyse(Module, Analysis, line, _Clauses) -> {{Module,L}, N} end end, - Answer = lists:keysort(1, lists:map(Fun, Bumps)), - {ok, Answer}; -do_analyse(_Module, Analysis, clause, Clauses) -> - Fun = case Analysis of - coverage -> - fun({M,F,A,C,Ls}) -> - Pattern = {#bump{module=M,function=F,arity=A, - clause=C},0}, - Bumps = ets:match_object(?COLLECTION_TABLE, Pattern), - NotCov = length(Bumps), - {{M,F,A,C}, {Ls-NotCov, NotCov}} - end; - calls -> - fun({M,F,A,C,_Ls}) -> - Pattern = {#bump{module=M,function=F,arity=A, - clause=C},'_'}, - Bumps = ets:match_object(?COLLECTION_TABLE, Pattern), - {_Bump, Calls} = hd(lists:keysort(1, Bumps)), - {{M,F,A,C}, Calls} - end - end, - Answer = lists:map(Fun, Clauses), - {ok, Answer}; + lists:keysort(1, lists:map(Fun, Bumps)); +do_analyse(Module, Analysis, clause, _Clauses) -> + Pattern = {#bump{module=Module},'_'}, + Bumps = lists:keysort(1,ets:match_object(?COLLECTION_TABLE, Pattern)), + analyse_clause(Analysis,Bumps); do_analyse(Module, Analysis, function, Clauses) -> - {ok, ClauseResult} = do_analyse(Module, Analysis, clause, Clauses), - Result = merge_clauses(ClauseResult, merge_fun(Analysis)), - {ok, Result}; + ClauseResult = do_analyse(Module, Analysis, clause, Clauses), + merge_clauses(ClauseResult, merge_fun(Analysis)); do_analyse(Module, Analysis, module, Clauses) -> - {ok, FunctionResult} = do_analyse(Module, Analysis, function, Clauses), + FunctionResult = do_analyse(Module, Analysis, function, Clauses), Result = merge_functions(FunctionResult, merge_fun(Analysis)), - {ok, {Module,Result}}. + {Module,Result}. + +analyse_clause(_,[]) -> + []; +analyse_clause(coverage, + [{#bump{module=M,function=F,arity=A,clause=C},_}|_]=Bumps) -> + analyse_clause_cov(Bumps,{M,F,A,C},0,0,[]); +analyse_clause(calls,Bumps) -> + analyse_clause_calls(Bumps,{x,x,x,x},[]). + +analyse_clause_cov([{#bump{module=M,function=F,arity=A,clause=C},N}|Bumps], + {M,F,A,C}=Clause,Ls,NotCov,Acc) -> + analyse_clause_cov(Bumps,Clause,Ls+1,if N==0->NotCov+1; true->NotCov end,Acc); +analyse_clause_cov([{#bump{module=M1,function=F1,arity=A1,clause=C1},_}|_]=Bumps, + Clause,Ls,NotCov,Acc) -> + analyse_clause_cov(Bumps,{M1,F1,A1,C1},0,0,[{Clause,{Ls-NotCov,NotCov}}|Acc]); +analyse_clause_cov([],Clause,Ls,NotCov,Acc) -> + lists:reverse(Acc,[{Clause,{Ls-NotCov,NotCov}}]). + +analyse_clause_calls([{#bump{module=M,function=F,arity=A,clause=C},_}|Bumps], + {M,F,A,C}=Clause,Acc) -> + analyse_clause_calls(Bumps,Clause,Acc); +analyse_clause_calls([{#bump{module=M1,function=F1,arity=A1,clause=C1},N}|Bumps], + _Clause,Acc) -> + analyse_clause_calls(Bumps,{M1,F1,A1,C1},[{{M1,F1,A1,C1},N}|Acc]); +analyse_clause_calls([],_Clause,Acc) -> + lists:reverse(Acc). merge_fun(coverage) -> fun({Cov1,NotCov1}, {Cov2,NotCov2}) -> @@ -2113,7 +2307,7 @@ merge_clauses([{{M,F,A,_C1},R1},{{M,F,A,C2},R2}|Clauses], MFun, Result) -> merge_clauses([{{M,F,A,_C},R}|Clauses], MFun, Result) -> merge_clauses(Clauses, MFun, [{{M,F,A},R}|Result]); merge_clauses([], _Fun, Result) -> - reverse(Result). + lists:reverse(Result). merge_functions([{_MFA,R}|Functions], MFun) -> merge_functions(Functions, MFun, R); @@ -2125,7 +2319,50 @@ merge_functions([{_MFA,R}|Functions], MFun, Result) -> merge_functions([], _MFun, Result) -> Result. -do_parallel_analysis_to_file(Module, OutFile, Opts, Loaded, From, State) -> +analyse_list_to_file(Modules, Opts, State) -> + {LoadedMF, ImportedMF, Error} = are_loaded(Modules, State, [], [], []), + collect([M || {M,_} <- LoadedMF], State#main_state.nodes), + OutDir = proplists:get_value(outdir,Opts), + HTML = lists:member(html,Opts), + Fun = fun({Module,File}) -> + OutFile = outfilename(OutDir,Module,HTML), + do_analyse_to_file(Module,File,OutFile,HTML,State) + end, + {Ok,Error1} = split_ok_error(pmap(Fun, LoadedMF++ImportedMF),[],[]), + {result,Ok,Error ++ Error1}. + +analyse_all_to_file(Opts, State) -> + collect(State#main_state.nodes), + AllModules = get_all_modules(State), + OutDir = proplists:get_value(outdir,Opts), + HTML = lists:member(html,Opts), + Fun = fun({Module,File}) -> + OutFile = outfilename(OutDir,Module,HTML), + do_analyse_to_file(Module,File,OutFile,HTML,State) + end, + {Ok,Error} = split_ok_error(pmap(Fun, AllModules),[],[]), + {result,Ok,Error}. + +get_all_modules(State) -> + get_all_modules(State#main_state.compiled ++ State#main_state.imported,[]). +get_all_modules([{Module,File}|Rest],Acc) -> + get_all_modules(Rest,[{Module,File}|Acc]); +get_all_modules([{Module,File,_}|Rest],Acc) -> + case lists:keymember(Module,1,Acc) of + true -> get_all_modules(Rest,Acc); + false -> get_all_modules(Rest,[{Module,File}|Acc]) + end; +get_all_modules([],Acc) -> + Acc. + +split_ok_error([{ok,R}|Result],Ok,Error) -> + split_ok_error(Result,[R|Ok],Error); +split_ok_error([{error,R}|Result],Ok,Error) -> + split_ok_error(Result,Ok,[R|Error]); +split_ok_error([],Ok,Error) -> + {Ok,Error}. + +do_parallel_analysis_to_file(Module, Opts, Loaded, From, State) -> File = case Loaded of {loaded, File0} -> [{Module,Clauses}] = @@ -2136,24 +2373,32 @@ do_parallel_analysis_to_file(Module, OutFile, Opts, Loaded, From, State) -> {imported, File0, _} -> File0 end, + HTML = lists:member(html,Opts), + OutFile = + case proplists:get_value(outfile,Opts) of + undefined -> + outfilename(proplists:get_value(outdir,Opts),Module,HTML); + F -> + F + end, + reply(From, do_analyse_to_file(Module,File,OutFile,HTML,State)). + +do_analyse_to_file(Module,File,OutFile,HTML,State) -> case find_source(Module, File) of {beam,_BeamFile} -> - reply(From, {error,no_source_code_found}); + {error,{no_source_code_found,Module}}; ErlFile -> analyse_info(Module,State#main_state.imported), - HTML = lists:member(html,Opts), - R = do_analyse_to_file(Module,OutFile, - ErlFile,HTML), - reply(From, R) + do_analyse_to_file1(Module,OutFile,ErlFile,HTML) end. -%% do_analyse_to_file(Module,OutFile,ErlFile) -> {ok,OutFile} | {error,Error} +%% do_analyse_to_file1(Module,OutFile,ErlFile) -> {ok,OutFile} | {error,Error} %% Module = atom() %% OutFile = ErlFile = string() -do_analyse_to_file(Module, OutFile, ErlFile, HTML) -> - case file:open(ErlFile, [read]) of +do_analyse_to_file1(Module, OutFile, ErlFile, HTML) -> + case file:open(ErlFile, [read,raw,read_ahead]) of {ok, InFd} -> - case file:open(OutFile, [write]) of + case file:open(OutFile, [write,raw,delayed_write]) of {ok, OutFd} -> if HTML -> Encoding = encoding(ErlFile), @@ -2191,9 +2436,14 @@ do_analyse_to_file(Module, OutFile, ErlFile, HTML) -> "**************************************" "\n\n"]), - print_lines(Module, InFd, OutFd, 1, HTML), + Pattern = {#bump{module=Module,line='$1',_='_'},'$2'}, + MS = [{Pattern,[],[{{'$1','$2'}}]}], + CovLines = lists:keysort(1,ets:select(?COLLECTION_TABLE, MS)), + print_lines(Module, CovLines, InFd, OutFd, 1, HTML), - if HTML -> io:format(OutFd,"</pre>\n</body>\n</html>\n",[]); + if + HTML -> + file:write(OutFd, "</pre>\n</body>\n</html>\n"); true -> ok end, @@ -2210,21 +2460,19 @@ do_analyse_to_file(Module, OutFile, ErlFile, HTML) -> {error, {file, ErlFile, Reason}} end. -print_lines(Module, InFd, OutFd, L, HTML) -> - case io:get_line(InFd, '') of + +print_lines(Module, CovLines, InFd, OutFd, L, HTML) -> + case file:read_line(InFd) of eof -> ignore; - "%"++_=Line -> %Comment line - not executed. - io:put_chars(OutFd, [tab(),escape_lt_and_gt(Line, HTML)]), - print_lines(Module, InFd, OutFd, L+1, HTML); - RawLine -> + {ok,"%"++_=Line} -> %Comment line - not executed. + file:write(OutFd, [tab(),escape_lt_and_gt(Line, HTML)]), + print_lines(Module, CovLines, InFd, OutFd, L+1, HTML); + {ok,RawLine} -> Line = escape_lt_and_gt(RawLine,HTML), - Pattern = {#bump{module=Module,line=L},'$1'}, - case ets:match(?COLLECTION_TABLE, Pattern) of - [] -> - io:put_chars(OutFd, [tab(),Line]); - Ns -> - N = lists:foldl(fun([Ni], Nacc) -> Nacc+Ni end, 0, Ns), + case CovLines of + [{L,N}|CovLines1] -> + %% N = lists:foldl(fun([Ni], Nacc) -> Nacc+Ni end, 0, Ns), if N=:=0, HTML=:=true -> LineNoNL = Line -- "\n", @@ -2232,19 +2480,22 @@ print_lines(Module, InFd, OutFd, L, HTML) -> %%Str = string:right("0", 6, 32), RedLine = ["<font color=red>",Str,fill1(), LineNoNL,"</font>\n"], - io:put_chars(OutFd, RedLine); + file:write(OutFd, RedLine); N<1000000 -> Str = string:right(integer_to_list(N), 6, 32), - io:put_chars(OutFd, [Str,fill1(),Line]); + file:write(OutFd, [Str,fill1(),Line]); N<10000000 -> Str = integer_to_list(N), - io:put_chars(OutFd, [Str,fill2(),Line]); + file:write(OutFd, [Str,fill2(),Line]); true -> Str = integer_to_list(N), - io:put_chars(OutFd, [Str,fill3(),Line]) - end - end, - print_lines(Module, InFd, OutFd, L+1, HTML) + file:write(OutFd, [Str,fill3(),Line]) + end, + print_lines(Module, CovLines1, InFd, OutFd, L+1, HTML); + _ -> + file:write(OutFd, [tab(),Line]), + print_lines(Module, CovLines, InFd, OutFd, L+1, HTML) + end end. tab() -> " | ". @@ -2254,7 +2505,7 @@ fill3() -> "| ". %%%--Export-------------------------------------------------------------- do_export(Module, OutFile, From, State) -> - case file:open(OutFile,[write,binary,raw]) of + case file:open(OutFile,[write,binary,raw,delayed_write]) of {ok,Fd} -> Reply = case Module of @@ -2393,21 +2644,21 @@ do_reset_collection_table(Module) -> ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}). %% do_reset(Module) -> ok -%% The reset is done on a per-clause basis to avoid building +%% The reset is done on ?CHUNK_SIZE number of bumps to avoid building %% long lists in the case of very large modules do_reset(Module) -> - [{Module,Clauses}] = ets:lookup(?COVER_CLAUSE_TABLE, Module), - do_reset2(Clauses). + Pattern = {#bump{module=Module, _='_'}, '$1'}, + MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}], + Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE), + do_reset2(Match). -do_reset2([{M,F,A,C,_L}|Clauses]) -> - Pattern = {#bump{module=M, function=F, arity=A, clause=C}, '_'}, - Bumps = ets:match_object(?COVER_TABLE, Pattern), +do_reset2({Bumps,Continuation}) -> lists:foreach(fun({Bump,_N}) -> ets:insert(?COVER_TABLE, {Bump,0}) end, Bumps), - do_reset2(Clauses); -do_reset2([]) -> + do_reset2(ets:select(Continuation)); +do_reset2('$end_of_table') -> ok. do_clear(Module) -> @@ -2434,14 +2685,6 @@ not_loaded(_Module,_Else, State) -> %%%--Div----------------------------------------------------------------- -reverse(List) -> - reverse(List,[]). -reverse([H|T],Acc) -> - reverse(T,[H|Acc]); -reverse([],Acc) -> - Acc. - - escape_lt_and_gt(Rawline,HTML) when HTML =/= true -> Rawline; escape_lt_and_gt(Rawline,_HTML) -> @@ -2458,31 +2701,43 @@ escape_lt_and_gt1([],Acc) -> escape_lt_and_gt1([H|T],Acc) -> escape_lt_and_gt1(T,[H|Acc]). -pmap(Fun, List) -> - pmap(Fun, List, 20). -pmap(Fun, List, Limit) -> - pmap(Fun, List, [], Limit, 0, []). -pmap(Fun, [E | Rest], Pids, Limit, Cnt, Acc) when Cnt < Limit -> - Collector = self(), - Pid = spawn_link(fun() -> - ?SPAWN_DBG(pmap,E), - Collector ! {res,self(),Fun(E)} - end), - erlang:monitor(process, Pid), - pmap(Fun, Rest, Pids ++ [Pid], Limit, Cnt + 1, Acc); -pmap(Fun, List, [Pid | Pids], Limit, Cnt, Acc) -> - receive - {'DOWN', _Ref, process, X, _} when is_pid(X) -> - pmap(Fun, List, [Pid | Pids], Limit, Cnt - 1, Acc); - {res, Pid, Res} -> - pmap(Fun, List, Pids, Limit, Cnt, [Res | Acc]) - end; -pmap(_Fun, [], [], _Limit, 0, Acc) -> - lists:reverse(Acc); -pmap(Fun, [], [], Limit, Cnt, Acc) -> +%%%--Internal functions for parallelization------------------------------ +pmap(Fun,List) -> + NTot = length(List), + NProcs = erlang:system_info(schedulers) * 2, + NPerProc = (NTot div NProcs) + 1, + Mons = pmap_spawn(Fun,NPerProc,List,[]), + pmap_collect(Mons,[]). + +pmap_spawn(_,_,[],Mons) -> + Mons; +pmap_spawn(Fun,NPerProc,List,Mons) -> + {L1,L2} = if length(List)>=NPerProc -> lists:split(NPerProc,List); + true -> {List,[]} % last chunk + end, + Mon = + spawn_monitor( + fun() -> + exit({pmap_done,lists:map(Fun,L1)}) + end), + pmap_spawn(Fun,NPerProc,L2,[Mon|Mons]). + +pmap_collect([],Acc) -> + lists:append(Acc); +pmap_collect(Mons,Acc) -> receive - {'DOWN', _Ref, process, X, _} when is_pid(X) -> - pmap(Fun, [], [], Limit, Cnt - 1, Acc) + {'DOWN', Ref, process, Pid, {pmap_done,Result}} -> + pmap_collect(lists:delete({Pid,Ref},Mons),[Result|Acc]); + {'DOWN', Ref, process, Pid, Reason} = Down -> + case lists:member({Pid,Ref},Mons) of + true -> + %% Something went really wrong - don't hang! + exit(Reason); + false -> + %% This should be handled somewhere else + self() ! Down, + pmap_collect(Mons,Acc) + end end. %%%----------------------------------------------------------------- diff --git a/lib/tools/src/cover_web.erl b/lib/tools/src/cover_web.erl index 69f2f3b1aa..ae8b3f25cf 100644 --- a/lib/tools/src/cover_web.erl +++ b/lib/tools/src/cover_web.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2001-2009. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -734,7 +735,7 @@ generate_filename(Prefix) -> filename:join(Cwd,Prefix ++ "_" ++ ts() ++ ".coverdata"). ts() -> - {{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(now()), + {{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(erlang:timestamp()), io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,M,D,H,Min,S]). diff --git a/lib/tools/src/cprof.erl b/lib/tools/src/cprof.erl index b0c3341efa..0240f876bc 100644 --- a/lib/tools/src/cprof.erl +++ b/lib/tools/src/cprof.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2002-2009. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl index bfbbefb473..0357e46a50 100644 --- a/lib/tools/src/eprof.erl +++ b/lib/tools/src/eprof.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 1996-2013. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -187,7 +188,7 @@ handle_call({profile_start, Rootset, Pattern, {M,F,A}, Opts}, From, #state{fd = case set_process_trace(true, [Pid|Rootset], Topts) of true -> ok = set_pattern_trace(true, Pattern), - T0 = now(), + T0 = erlang:timestamp(), ok = execute_profiling(Pid), {noreply, #state{ profiling = true, @@ -211,7 +212,7 @@ handle_call({profile_start, Rootset, Pattern, undefined, Opts}, From, #state{ fd case set_process_trace(true, Rootset, Topts) of true -> - T0 = now(), + T0 = erlang:timestamp(), ok = set_pattern_trace(true, Pattern), {reply, profiling, #state{ profiling = true, @@ -485,20 +486,22 @@ string_bp_mfa([{Mfa, {Count, Time}}|Mfas], Tus, {MfaW, CountW, PercW, TimeW, TpC erlang:max(TpCW, length(Stpc)) }, [[Smfa, Scount, Sperc, Stime, Stpc] | Strings]). -print_bp_mfa(Mfas, {_Tn, Tus}, Fd, Opts) -> +print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts) -> Fmfas = filter_mfa(sort_mfa(Mfas, proplists:get_value(sort, Opts)), proplists:get_value(filter, Opts)), {{MfaW, CountW, PercW, TimeW, TpCW}, Strs} = string_bp_mfa(Fmfas, Tus), - Ws = { - erlang:max(length("FUNCTION"), MfaW), - erlang:max(length("CALLS"), CountW), - erlang:max(length(" %"), PercW), - erlang:max(length("TIME"), TimeW), - erlang:max(length("uS / CALLS"), TpCW) - }, - format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]), - format(Fd, Ws, ["--------", "-----", "---", "----", "----------"]), - + TnStr = s(Tn), + TusStr = s(Tus), + TuspcStr = s("~.2f", [divide(Tus,Tn)]), + Ws = {erlang:max(length("FUNCTION"), MfaW), + lists:max([length("CALLS"), CountW, length(TnStr)]), + erlang:max(length(" %"), PercW), + lists:max([length("TIME"), TimeW, length(TusStr)]), + lists:max([length("uS / CALLS"), TpCW, length(TuspcStr)])}, + format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]), + format(Fd, Ws, ["--------", "-----", "-------", "----", "----------"]), lists:foreach(fun (String) -> format(Fd, Ws, String) end, Strs), + format(Fd, Ws, [lists:duplicate(N,$-)||N <- tuple_to_list(Ws)]), + format(Fd, Ws, ["Total:", TnStr, "100.00%", TusStr, TuspcStr]), ok. s({M,F,A}) -> s("~w:~w/~w",[M,F,A]); diff --git a/lib/tools/src/fprof.erl b/lib/tools/src/fprof.erl index 75840e54ff..7c6fab0b75 100644 --- a/lib/tools/src/fprof.erl +++ b/lib/tools/src/fprof.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2001-2013. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/instrument.erl b/lib/tools/src/instrument.erl index fa8a4a4867..34c5ba04cc 100644 --- a/lib/tools/src/instrument.erl +++ b/lib/tools/src/instrument.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 1998-2009. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl index f13a297ecf..e8b3d242e4 100644 --- a/lib/tools/src/lcnt.erl +++ b/lib/tools/src/lcnt.erl @@ -1,19 +1,19 @@ -%% -*- coding: utf-8 -*- %% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -62,6 +62,8 @@ locations/1, inspect/1, inspect/2, + histogram/1, + histogram/2, information/0, swap_pid_keys/0, % set options @@ -90,14 +92,14 @@ duration = 0 }). - -record(stats, { - file, - line, - tries, - colls, - time, % us - nt % #timings collected + file :: atom(), + line :: non_neg_integer(), + tries :: non_neg_integer(), + colls :: non_neg_integer(), + time :: non_neg_integer(), % us + nt :: non_neg_integer(), % #timings collected + hist :: tuple() % histogram }). -record(lock, { @@ -116,7 +118,9 @@ colls, cr, % collision ratio time, - dtr % time duration ratio + dtr, % time duration ratio + %% new + hist % log2 histogram of lock wait_time }). @@ -128,7 +132,7 @@ %% -------------------------------------------------------------------- %% start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). -stop() -> gen_server:cast(?MODULE, stop). +stop() -> gen_server:call(?MODULE, stop, infinity). init([]) -> {ok, #state{ locks = [], duration = 0 } }. %% -------------------------------------------------------------------- %% @@ -172,6 +176,8 @@ conflicts() -> call({conflicts, []}). conflicts(Opts) -> call({conflicts, Opts}). inspect(Lock) -> call({inspect, Lock, []}). inspect(Lock, Opts) -> call({inspect, Lock, Opts}). +histogram(Lock) -> call({histogram, Lock, []}). +histogram(Lock, Opts)-> call({histogram, Lock, Opts}). information() -> call(information). swap_pid_keys() -> call(swap_pid_keys). raw() -> call(raw). @@ -284,14 +290,14 @@ handle_call({locations, InOpts}, _From, #state{ locks = Locks } = State) when is {reply, ok, State}; -handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, locks = Locks } = State) when is_list(InOpts) -> +handle_call({inspect, Lockname, InOpts}, _From, #state{ duration=Duration, locks=Locks } = State) when is_list(InOpts) -> Default = [ {sort, time}, {reverse, false}, - {print, [name,id,tries,colls,ratio,time,duration]}, + {print, [name,id,tries,colls,ratio,time,duration,histogram]}, {max_locks, 20}, {combine, false}, - {thresholds, [] }, + {thresholds, []}, {locations, false}], Opts = options(InOpts, Default), @@ -300,7 +306,7 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, loc {true, true} -> locks_ids(Filtered); _ -> [] end, - Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)), + Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)), case proplists:get_value(locations, Opts) of true -> lists:foreach(fun @@ -314,25 +320,48 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, loc [] -> ok; _ -> - %io:format("Combined ~p~n", [Combined]), print("lock: " ++ term2string(Name)), print("id: " ++ IdString), print("type: " ++ term2string(Type)), Ps = stats2print(Combined, Duration), - Opts1 = options([{print, [entry, tries,colls,ratio,time,duration]}, + Opts1 = options([{print, [entry, tries,colls,ratio,time,duration,histogram]}, {thresholds, [{tries, -1}, {colls, -1}, {time, -1}]}], Opts), print_lock_information(filter_print(Ps, Opts1), proplists:get_value(print, Opts1)) end - % (#lock{ name = Name, id = Id}) -> - % io:format("Empty lock ~p ~p~n", [Name, Id]) end, Combos); _ -> - Print1 = locks2print(Combos, Duration), - Print2 = filter_print(Print1, Opts), - print_lock_information(Print2, proplists:get_value(print, Opts)) + Print = filter_print(locks2print(Combos, Duration), Opts), + print_lock_information(Print, proplists:get_value(print, Opts)) end, {reply, ok, State}; +%% histogram + +handle_call({histogram, Lockname, InOpts}, _From, #state{ duration=Duration, locks=Locks} = State)-> + Default = [ + {sort, time}, + {reverse, false}, + {print, [name,id,tries,colls,ratio,time,duration,histogram]}, + {max_locks, 20}, + {combine, true}, + {thresholds, []}, + {locations, false}], + + Opts = options(InOpts, Default), + Filtered = filter_locks(Locks, Lockname), + Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)), + lists:foreach(fun + (#lock{ stats = Stats }=L) -> + SumStats = summate_stats(Stats), + Opts1 = options([{print, [name,id,tries,colls,ratio,time,duration]}, + {thresholds, [{tries, -1}, {colls, -1}, {time, -1}]}], Opts), + Prints = locks2print([L], Duration), + print_lock_information(Prints, proplists:get_value(print, Opts1)), + print_full_histogram(SumStats#stats.hist) + end, Combos), + + {reply, ok, State}; + handle_call(raw, _From, #state{ locks = Locks} = State)-> {reply, Locks, State}; @@ -348,7 +377,6 @@ handle_call(swap_pid_keys, _From, #state{ locks = Locks } = State)-> (L) -> L end, Locks), - {reply, ok, State#state{ locks = SwappedLocks}}; % settings @@ -381,6 +409,8 @@ handle_call({save, Filename}, _From, State) -> {reply, {error, Error}, State} end; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; handle_call(Command, _From, State) -> {reply, {error, {undefined, Command}}, State}. @@ -391,8 +421,6 @@ handle_call(Command, _From, State) -> %% %% -------------------------------------------------------------------- %% -handle_cast(stop, State) -> - {stop, normal, State}; handle_cast(_, State) -> {noreply, State}. @@ -433,15 +461,32 @@ code_change(_OldVsn, State, _Extra) -> summate_locks(Locks) -> summate_locks(Locks, #stats{ tries = 0, colls = 0, time = 0, nt = 0}). summate_locks([], Stats) -> Stats; -summate_locks([L|Ls], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt}) -> +summate_locks([L|Ls], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt, hist = Hist}) -> S = summate_stats(L#lock.stats), - summate_locks(Ls, #stats{ tries = Tries + S#stats.tries, colls = Colls + S#stats.colls, time = Time + S#stats.time, nt = Nt + S#stats.nt}). + summate_locks(Ls, #stats{ + tries = Tries + S#stats.tries, + colls = Colls + S#stats.colls, + time = Time + S#stats.time, + nt = Nt + S#stats.nt, + hist = summate_histogram(Hist, S#stats.hist) + }). summate_stats(Stats) -> summate_stats(Stats, #stats{ tries = 0, colls = 0, time = 0, nt = 0}). summate_stats([], Stats) -> Stats; -summate_stats([S|Ss], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt}) -> - summate_stats(Ss, #stats{ tries = Tries + S#stats.tries, colls = Colls + S#stats.colls, time = Time + S#stats.time, nt = Nt + S#stats.nt}). - +summate_stats([S|Ss], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt, hist = Hist}) -> + summate_stats(Ss, #stats{ + tries = Tries + S#stats.tries, + colls = Colls + S#stats.colls, + time = Time + S#stats.time, + nt = Nt + S#stats.nt, + hist = summate_histogram(Hist, S#stats.hist) + }). + +%% first call is undefined +summate_histogram(Tup,undefined) when is_tuple(Tup) -> Tup; +summate_histogram(undefined,Tup) when is_tuple(Tup) -> Tup; +summate_histogram(Hs1,Hs2) -> + list_to_tuple([ A + B || {A,B} <- lists:zip(tuple_to_list(Hs1),tuple_to_list(Hs2))]). %% manipulators filter_locks_type(Locks, undefined) -> Locks; @@ -463,20 +508,22 @@ filter_locks(Locks, Lockname) -> % 4. max length of locks filter_print(PLs, Opts) -> - TLs = threshold_locks(PLs, proplists:get_value(thresholds, Opts, [])), - SLs = sort_locks(TLs, proplists:get_value(sort, Opts, time)), - CLs = cut_locks(SLs, proplists:get_value(max_locks, Opts, none)), - reverse_locks(CLs, proplists:get_value(reverse, Opts, false)). - -sort_locks(Locks, Type) -> lists:reverse(sort_locks0(Locks, Type)). -sort_locks0(Locks, name) -> lists:keysort(#print.name, Locks); -sort_locks0(Locks, id) -> lists:keysort(#print.id, Locks); -sort_locks0(Locks, type) -> lists:keysort(#print.type, Locks); -sort_locks0(Locks, tries) -> lists:keysort(#print.tries, Locks); -sort_locks0(Locks, colls) -> lists:keysort(#print.colls, Locks); -sort_locks0(Locks, ratio) -> lists:keysort(#print.cr, Locks); -sort_locks0(Locks, time) -> lists:keysort(#print.time, Locks); -sort_locks0(Locks, _) -> sort_locks0(Locks, time). + TLs = threshold_locks(PLs, proplists:get_value(thresholds, Opts, [])), + SLs = sort_locks(TLs, proplists:get_value(sort, Opts, time)), + CLs = cut_locks(SLs, proplists:get_value(max_locks, Opts, none)), + reverse_locks(CLs, proplists:get_value(reverse, Opts, false)). + +sort_locks(Locks, name) -> reverse_sort_locks(#print.name, Locks); +sort_locks(Locks, id) -> reverse_sort_locks(#print.id, Locks); +sort_locks(Locks, type) -> reverse_sort_locks(#print.type, Locks); +sort_locks(Locks, tries) -> reverse_sort_locks(#print.tries, Locks); +sort_locks(Locks, colls) -> reverse_sort_locks(#print.colls, Locks); +sort_locks(Locks, ratio) -> reverse_sort_locks(#print.cr, Locks); +sort_locks(Locks, time) -> reverse_sort_locks(#print.time, Locks); +sort_locks(Locks, _) -> sort_locks(Locks, time). + +reverse_sort_locks(Ix, Locks) -> + lists:reverse(lists:keysort(Ix, Locks)). % cut locks not above certain thresholds threshold_locks(Locks, Thresholds) -> @@ -557,45 +604,65 @@ locks_ids(Locks) -> locks_ids(Locks, []). locks_ids([], Out) -> Out; locks_ids([#lock{ name = Key } = L|Ls], Out) -> case proplists:get_value(Key, Out) of - undefined -> - locks_ids(Ls, [{Key, [L#lock.id] } | Out]); - Ids -> - locks_ids(Ls, [{Key, [L#lock.id | Ids] } | proplists:delete(Key,Out)]) + undefined -> locks_ids(Ls, [{Key, [L#lock.id]}|Out]); + Ids -> locks_ids(Ls, [{Key, [L#lock.id|Ids]}|proplists:delete(Key,Out)]) end. stats2print(Stats, Duration) -> lists:map(fun (S) -> - #print{ - entry = term2string("~tp:~p", [S#stats.file, S#stats.line]), - colls = S#stats.colls, - tries = S#stats.tries, - cr = percent(S#stats.colls, S#stats.tries), - time = S#stats.time, - dtr = percent(S#stats.time, Duration) - } + #print{entry = term2string("~tp:~p", [S#stats.file, S#stats.line]), + colls = S#stats.colls, + tries = S#stats.tries, + cr = percent(S#stats.colls, S#stats.tries), + time = S#stats.time, + dtr = percent(S#stats.time, Duration), + hist = format_histogram(S#stats.hist)} end, Stats). locks2print(Locks, Duration) -> lists:map( fun (L) -> - Tries = lists:sum([T || #stats{ tries = T} <- L#lock.stats]), - Colls = lists:sum([C || #stats{ colls = C} <- L#lock.stats]), - Time = lists:sum([T || #stats{ time = T} <- L#lock.stats]), - Cr = percent(Colls, Tries), - Dtr = percent(Time, Duration), - #print{ - name = L#lock.name, - id = L#lock.id, - type = L#lock.type, - tries = Tries, - colls = Colls, - cr = Cr, - time = Time, - dtr = Dtr - } + #stats{tries = Tries, + colls = Colls, + time = Time, + hist = Hist} = summate_stats(L#lock.stats), + Cr = percent(Colls, Tries), + Dtr = percent(Time, Duration), + #print{name = L#lock.name, + id = L#lock.id, + type = L#lock.type, + tries = Tries, + colls = Colls, + hist = format_histogram(Hist), + cr = Cr, + time = Time, + dtr = Dtr} end, Locks). + +format_histogram(Tup) when is_tuple(Tup) -> + Vs = tuple_to_list(Tup), + Max = lists:max(Vs), + case Max of + 0 -> string_histogram(Vs); + _ -> string_histogram([case V of 0 -> 0; _ -> V/Max end || V <- Vs]) + end. + +string_histogram(Vs) -> + [$||histogram_values_to_string(Vs,$|)]. + +histogram_values_to_string([0|Vs],End) -> + [$\s|histogram_values_to_string(Vs,End)]; +histogram_values_to_string([V|Vs],End) when V > 0.66 -> + [$X|histogram_values_to_string(Vs,End)]; +histogram_values_to_string([V|Vs],End) when V > 0.33 -> + [$x|histogram_values_to_string(Vs,End)]; +histogram_values_to_string([_|Vs],End) -> + [$.|histogram_values_to_string(Vs,End)]; +histogram_values_to_string([],End) -> + [End]. + %% state making data2state(Data, State) -> @@ -607,22 +674,32 @@ data2state(Data, State) -> locks = Locks }. -locks2records(Locks) -> locks2records(Locks, []). -locks2records([], Out) -> Out; -locks2records([{Name, Id, Type, Stats}|Locks], Out) -> - Lock = #lock{ - name = Name, - id = clean_id_creation(Id), - type = Type, - stats = [ #stats{ - file = File, - line = Line, - tries = Tries, - colls = Colls, - time = time2us({S, Ns}), - nt = N - } || {{File, Line}, {Tries, Colls, {S, Ns, N}}} <- Stats] }, - locks2records(Locks, [Lock|Out]). +locks2records([{Name, Id, Type, Stats}|Locks]) -> + [#lock{name = Name, + id = clean_id_creation(Id), + type = Type, + stats = stats2record(Stats)}|locks2records(Locks)]; +locks2records([]) -> []. + +%% new stats with histogram +stats2record([{{File,Line},{Tries,Colls,{S,Ns,N}},Hist}|Stats]) -> + [#stats{file = File, + line = Line, + hist = Hist, + tries = Tries, + colls = Colls, + time = time2us({S, Ns}), + nt = N} | stats2record(Stats)]; +%% old stats without histogram +stats2record([{{File,Line},{Tries,Colls,{S,Ns,N}}}|Stats]) -> + [#stats{file = File, + line = Line, + hist = {}, + tries = Tries, + colls = Colls, + time = time2us({S, Ns}), + nt = N} | stats2record(Stats)]; +stats2record([]) -> []. clean_id_creation(Id) when is_pid(Id) -> Bin = term_to_binary(Id), @@ -648,22 +725,45 @@ state2list(State) -> (X, Y) -> {X,Y} end, record_info(fields, state), Values). -list2state(List) -> list2state(record_info(fields, state), List, [state]). -list2state([], _, Out) -> list_to_tuple(lists:reverse(Out)); -list2state([locks|Fs], List, Out) -> - Locks = [ list2lock(Lock) || Lock <- proplists:get_value(locks, List, [])], - list2state(Fs, List, [Locks|Out]); -list2state([F|Fs], List, Out) -> list2state(Fs, List, [proplists:get_value(F, List, state_default(F))|Out]). - lock_default(Field) -> proplists:get_value(Field, lock2list(#lock{})). lock2list(Lock) -> [_|Values] = tuple_to_list(Lock), lists:zip(record_info(fields, lock), Values). -list2lock(List) -> list2lock(record_info(fields, lock), List, [lock]). -list2lock([], _, Out) -> list_to_tuple(lists:reverse(Out)); -list2lock([F|Fs], List, Out) -> list2lock(Fs, List, [proplists:get_value(F, List, lock_default(F))|Out]). + +list2state(List) -> + list_to_tuple([state|list2state(record_info(fields, state), List)]). +list2state([], _) -> []; +list2state([locks|Fs], List) -> + Locks = [list2lock(Lock) || Lock <- proplists:get_value(locks, List, [])], + [Locks|list2state(Fs,List)]; +list2state([F|Fs], List) -> + [proplists:get_value(F, List, state_default(F))|list2state(Fs, List)]. + +list2lock(Ls) -> + list_to_tuple([lock|list2lock(record_info(fields, lock), Ls)]). + +list2lock([],_) -> []; +list2lock([stats=F|Fs], Ls) -> + Stats = stats2stats(proplists:get_value(F, Ls, lock_default(F))), + [Stats|list2lock(Fs, Ls)]; +list2lock([F|Fs], Ls) -> + [proplists:get_value(F, Ls, lock_default(F))|list2lock(Fs, Ls)]. + +%% process old stats (hack) +%% old stats had no histograms +%% in future versions stats should be serialized as a list, not a record + +stats2stats([]) -> []; +stats2stats([Stat|Stats]) -> + Sz = tuple_size(#stats{}), + [stat2stat(Stat,Sz)|stats2stats(Stats)]. + +stat2stat(Stat,Sz) when tuple_size(Stat) =:= Sz -> Stat; +stat2stat(Stat,_) -> + %% assume no histogram at the end + list_to_tuple(tuple_to_list(Stat) ++ [{0}]). %% printing @@ -684,7 +784,7 @@ auto_print_width(Locks, Print) -> ({print,print}, Out) -> [print|Out]; ({Str, Len}, Out) -> [erlang:min(erlang:max(length(s(Str))+1,Len),80)|Out] end, [], lists:zip(tuple_to_list(L), tuple_to_list(Max))))) - end, #print{ id = 4, type = 5, entry = 5, name = 6, tries = 8, colls = 13, cr = 16, time = 11, dtr = 14 }, + end, #print{ id=4, type=5, entry=5, name=6, tries=8, colls=13, cr=16, time=11, dtr=14, hist=20 }, Locks), % Setup the offsets for later pruning Offsets = [ @@ -696,7 +796,9 @@ auto_print_width(Locks, Print) -> {colls, R#print.colls}, {ratio, R#print.cr}, {time, R#print.time}, - {duration, R#print.dtr}], + {duration, R#print.dtr}, + {histogram, R#print.hist} + ], % Prune offsets to only allow specified print options lists:foldr(fun ({Type, W}, Out) -> [{Type, W}|Out]; @@ -706,9 +808,7 @@ auto_print_width(Locks, Print) -> print_lock_information(Locks, Print) -> % remake Print to autosize entries AutoPrint = auto_print_width(Locks, Print), - print_header(AutoPrint), - lists:foreach(fun (L) -> print_lock(L, AutoPrint) @@ -725,7 +825,8 @@ print_header(Opts) -> colls = "#collisions", cr = "collisions [%]", time = "time [us]", - dtr = "duration [%]" + dtr = "duration [%]", + hist = "histogram [log2(us)]" }, Divider = #print{ name = lists:duplicate(1 + length(Header#print.name), 45), @@ -736,39 +837,44 @@ print_header(Opts) -> colls = lists:duplicate(1 + length(Header#print.colls), 45), cr = lists:duplicate(1 + length(Header#print.cr), 45), time = lists:duplicate(1 + length(Header#print.time), 45), - dtr = lists:duplicate(1 + length(Header#print.dtr), 45) + dtr = lists:duplicate(1 + length(Header#print.dtr), 45), + hist = lists:duplicate(1 + length(Header#print.hist), 45) }, print_lock(Header, Opts), print_lock(Divider, Opts), ok. -print_lock(L, Opts) -> print_lock(L, Opts, []). -print_lock(_, [], Formats) -> print(strings(lists:reverse(Formats))); -print_lock(L, [Opt|Opts], Formats) -> +print_lock(L, Opts) -> + print(strings(format_lock(L, Opts))). + +format_lock(_, []) -> []; +format_lock(L, [Opt|Opts]) -> case Opt of - id -> print_lock(L, Opts, [{space, 25, s(L#print.id) } | Formats]); - {id, W} -> print_lock(L, Opts, [{space, W, s(L#print.id) } | Formats]); - type -> print_lock(L, Opts, [{space, 18, s(L#print.type) } | Formats]); - {type, W} -> print_lock(L, Opts, [{space, W, s(L#print.type) } | Formats]); - entry -> print_lock(L, Opts, [{space, 30, s(L#print.entry)} | Formats]); - {entry, W} -> print_lock(L, Opts, [{space, W, s(L#print.entry)} | Formats]); - name -> print_lock(L, Opts, [{space, 22, s(L#print.name) } | Formats]); - {name, W} -> print_lock(L, Opts, [{space, W, s(L#print.name) } | Formats]); - tries -> print_lock(L, Opts, [{space, 12, s(L#print.tries)} | Formats]); - {tries, W} -> print_lock(L, Opts, [{space, W, s(L#print.tries)} | Formats]); - colls -> print_lock(L, Opts, [{space, 14, s(L#print.colls)} | Formats]); - {colls, W} -> print_lock(L, Opts, [{space, W, s(L#print.colls)} | Formats]); - ratio -> print_lock(L, Opts, [{space, 20, s(L#print.cr) } | Formats]); - {ratio, W} -> print_lock(L, Opts, [{space, W, s(L#print.cr) } | Formats]); - time -> print_lock(L, Opts, [{space, 15, s(L#print.time) } | Formats]); - {time, W} -> print_lock(L, Opts, [{space, W, s(L#print.time) } | Formats]); - duration -> print_lock(L, Opts, [{space, 20, s(L#print.dtr) } | Formats]); - {duration, W} -> print_lock(L, Opts, [{space, W, s(L#print.dtr) } | Formats]); - _ -> print_lock(L, Opts, Formats) + id -> [{space, 25, s(L#print.id) } | format_lock(L, Opts)]; + {id, W} -> [{space, W, s(L#print.id) } | format_lock(L, Opts)]; + type -> [{space, 18, s(L#print.type) } | format_lock(L, Opts)]; + {type, W} -> [{space, W, s(L#print.type) } | format_lock(L, Opts)]; + entry -> [{space, 30, s(L#print.entry)} | format_lock(L, Opts)]; + {entry, W} -> [{space, W, s(L#print.entry)} | format_lock(L, Opts)]; + name -> [{space, 22, s(L#print.name) } | format_lock(L, Opts)]; + {name, W} -> [{space, W, s(L#print.name) } | format_lock(L, Opts)]; + tries -> [{space, 12, s(L#print.tries)} | format_lock(L, Opts)]; + {tries, W} -> [{space, W, s(L#print.tries)} | format_lock(L, Opts)]; + colls -> [{space, 14, s(L#print.colls)} | format_lock(L, Opts)]; + {colls, W} -> [{space, W, s(L#print.colls)} | format_lock(L, Opts)]; + ratio -> [{space, 20, s(L#print.cr) } | format_lock(L, Opts)]; + {ratio, W} -> [{space, W, s(L#print.cr) } | format_lock(L, Opts)]; + time -> [{space, 15, s(L#print.time) } | format_lock(L, Opts)]; + {time, W} -> [{space, W, s(L#print.time) } | format_lock(L, Opts)]; + duration -> [{space, 20, s(L#print.dtr) } | format_lock(L, Opts)]; + {duration, W} -> [{space, W, s(L#print.dtr) } | format_lock(L, Opts)]; + histogram -> [{space, 20, s(L#print.hist) } | format_lock(L, Opts)]; + {histogram, W} -> [{left, W - length(s(L#print.hist)) - 1, s(L#print.hist)} | format_lock(L, Opts)]; + _ -> format_lock(L, Opts) end. -print_state_information(#state{ locks = Locks} = State) -> +print_state_information(#state{locks = Locks} = State) -> Stats = summate_locks(Locks), print("information:"), print(kv("#locks", s(length(Locks)))), @@ -780,9 +886,25 @@ print_state_information(#state{ locks = Locks} = State) -> print(kv("percent of duration", s(Stats#stats.time/State#state.duration*100) ++ " %")), ok. + +print_full_histogram(T) when is_tuple(T) -> + Vs = tuple_to_list(T), + Max = lists:max(Vs), + W = 60, + print_full_histogram(0,Vs,Max,W). + +print_full_histogram(_,[],_,_) -> ok; +print_full_histogram(Ix,[V|Vs],0,W) -> + io:format("~2w = log2 : ~8w |~n", [Ix,V]), + print_full_histogram(Ix+1,Vs,0,W); +print_full_histogram(Ix,[V|Vs],Max,W) -> + io:format("~2w = log2 : ~8w | ~s~n", [Ix,V,lists:duplicate(trunc(W*(V/Max)), $#)]), + print_full_histogram(Ix+1,Vs,Max,W). + + %% AUX -time2us({S, Ns}) -> round(S*1000000 + Ns/1000). +time2us({S, Ns}) -> S*1000000 + (Ns div 1000). percent(_,0) -> 0.0; percent(T,N) -> T/N*100. @@ -809,7 +931,8 @@ s(T) -> term2string(T). strings(Strings) -> strings(Strings, []). strings([], Out) -> Out; -strings([{space, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string("~~~ps", [N]), [S])); +strings([{space, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string("~~~ws", [N]), [S])); +strings([{left, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string(" ~~s~~~ws", [N]), [S,""])); strings([{format, Format, S} | Ss], Out) -> strings(Ss, Out ++ term2string(Format, [S])); strings([S|Ss], Out) -> strings(Ss, Out ++ term2string("~ts", [S])). @@ -826,7 +949,7 @@ term2string(Term) when is_pid(Term) -> term2string(Term) -> term2string("~w", [Term]). term2string(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). -%%% AUD id binary +%%% AUX id binary bytes16(Value) -> B0 = Value band 255, diff --git a/lib/tools/src/make.erl b/lib/tools/src/make.erl index c8ef0a04a5..96c3e0e506 100644 --- a/lib/tools/src/make.erl +++ b/lib/tools/src/make.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 1996-2013. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/tags.erl b/lib/tools/src/tags.erl index e3cc51cdb2..2bc1865503 100644 --- a/lib/tools/src/tags.erl +++ b/lib/tools/src/tags.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -297,15 +298,16 @@ word_char(_) -> false. %% Check the options `outfile' and `outdir'. open_out(Options) -> + Opts = [write, {encoding, unicode}], case lists:keysearch(outfile, 1, Options) of {value, {outfile, File}} -> - file:open(File, [write]); + file:open(File, Opts); _ -> case lists:keysearch(outdir, 1, Options) of {value, {outdir, Dir}} -> - file:open(filename:join(Dir, "TAGS"), [write]); + file:open(filename:join(Dir, "TAGS"), Opts); _ -> - file:open("TAGS", [write]) + file:open("TAGS", Opts) end end. diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src index 553c5eb96b..978b54719c 100644 --- a/lib/tools/src/tools.app.src +++ b/lib/tools/src/tools.app.src @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2012. All Rights Reserved. +%% Copyright Ericsson AB 1996-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -39,23 +40,9 @@ {applications, [kernel, stdlib]}, {env, [{file_util_search_methods,[{"", ""}, {"ebin", "esrc"}, {"ebin", "src"}]} ] - } + }, + {runtime_dependencies, ["webtool-0.8.10","stdlib-2.5","runtime_tools-1.8.14", + "kernel-3.0","inets-5.10","erts-7.0", + "compiler-5.0"]} ] }. - - - - - - - - - - - - - - - - - diff --git a/lib/tools/src/tools.appup.src b/lib/tools/src/tools.appup.src index 8de1ec76c9..9eee8df184 100644 --- a/lib/tools/src/tools.appup.src +++ b/lib/tools/src/tools.appup.src @@ -1,19 +1,22 @@ -# -# %CopyrightBegin% -# -# Copyright Ericsson AB 2001-2009. All Rights Reserved. -# -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. -# -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. -# -# %CopyrightEnd% -# -{"%VSN%",[],[]}. +%% -*- erlang -*- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2014. All Rights Reserved. +%% +%% 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. +%% +%% %CopyrightEnd% +{"%VSN%", + [{<<".*">>,[{restart_application, tools}]}], + [{<<".*">>,[{restart_application, tools}]}] +}. diff --git a/lib/tools/src/xref.erl b/lib/tools/src/xref.erl index abc184c84d..64add7afbb 100644 --- a/lib/tools/src/xref.erl +++ b/lib/tools/src/xref.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2000-2013. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/xref.hrl b/lib/tools/src/xref.hrl index fa8c5c746d..a83271cbdb 100644 --- a/lib/tools/src/xref.hrl +++ b/lib/tools/src/xref.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. +%% Copyright Ericsson AB 2000-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -22,6 +23,8 @@ -define(VAR_EXPR, '$F_EXPR'). -define(MOD_EXPR, '$M_EXPR'). +-define(XREF_END_LINE, (1 bsl 23)). + %%% Filenames are stored as directory and basename. A lot of heap can %%% be saved by keeping only one (or few) copy of the directory name. diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl index 30c5f3d12d..88031f6e77 100644 --- a/lib/tools/src/xref_base.erl +++ b/lib/tools/src/xref_base.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2000-2013. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/xref_compiler.erl b/lib/tools/src/xref_compiler.erl index f0fed502a5..8c87e47c4c 100644 --- a/lib/tools/src/xref_compiler.erl +++ b/lib/tools/src/xref_compiler.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2013. All Rights Reserved. +%% Copyright Ericsson AB 2000-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -885,7 +886,7 @@ evaluate([pop | P], T, [_ | S]) -> evaluate([], T, [R]) -> {T, R}. -%% (PossibleGraph, 1 | -1, dict()) -> dict() +%% (PossibleGraph, 1 | -1, dict:dict()) -> dict:dict() %% Use the same table for everything... Here: Reference counters for digraphs. update_graph_counter(Value, Inc, T) -> case catch digraph:info(Value) of @@ -924,7 +925,7 @@ format_parse_error(["invalid_operator", Op], Line) -> format_parse_error(Error, Line) -> io_lib:format("Parse error~s: ~ts~n", [Line, lists:flatten(Error)]). -format_line(-1) -> +format_line(?XREF_END_LINE) -> " at end of string"; format_line(0) -> ""; diff --git a/lib/tools/src/xref_parser.yrl b/lib/tools/src/xref_parser.yrl index 1279ece061..48602bb120 100644 --- a/lib/tools/src/xref_parser.yrl +++ b/lib/tools/src/xref_parser.yrl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2000-2010. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl index d3601c6ea0..41b93caaeb 100644 --- a/lib/tools/src/xref_reader.erl +++ b/lib/tools/src/xref_reader.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2013. All Rights Reserved. +%% Copyright Ericsson AB 2000-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -77,17 +78,18 @@ forms([], S) -> form({attribute, Line, xref, Calls}, S) -> % experimental #xrefr{module = M, function = Fun, lattrs = L, xattrs = X, battrs = B} = S, - attr(Calls, Line, M, Fun, L, X, B, S); + attr(Calls, erl_anno:line(Line), M, Fun, L, X, B, S); form({attribute, _Line, _Attr, _Val}, S) -> S; -form({function, 0, module_info, 0, _Clauses}, S) -> +form({function, _, module_info, 0, _Clauses}, S) -> S; -form({function, 0, module_info, 1, _Clauses}, S) -> +form({function, _, module_info, 1, _Clauses}, S) -> S; -form({function, Line, Name, Arity, Clauses}, S) -> +form({function, Anno, Name, Arity, Clauses}, S) -> MFA0 = {S#xrefr.module, Name, Arity}, MFA = adjust_arity(S, MFA0), S1 = S#xrefr{function = MFA}, + Line = erl_anno:line(Anno), S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]}, S3 = clauses(Clauses, S2), S3#xrefr{function = []}. @@ -171,6 +173,11 @@ expr({'fun', Line, {function, Name, Arity}, _Extra}, S) -> handle_call(local, S#xrefr.module, Name, Arity, Line, S); expr({'fun', _Line, {clauses, Cs}, _Extra}, S) -> clauses(Cs, S); +expr({named_fun, _Line, '_', Cs, _Extra}, S) -> + clauses(Cs, S); +expr({named_fun, _Line, Name, Cs, _Extra}, S) -> + S1 = S#xrefr{funvars = [Name | S#xrefr.funvars]}, + clauses(Cs, S1); expr({call, Line, {atom, _, Name}, As}, S) -> S1 = handle_call(local, S#xrefr.module, Name, length(As), Line, S), expr(As, S1); @@ -186,6 +193,9 @@ expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}, _Extra}}, S) -> %% that are passed around by the "expansion" of list comprehension. S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]}, clauses(Cs, S1); +expr({match, _Line, {var,_,Var}, {named_fun, _, _, _, _} = Fun}, S) -> + S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]}, + expr(Fun, S1); expr({match, _Line, {var,_,Var}, E}, S) -> %% Used for resolving code like %% Args = [A,B], apply(m, f, Args) @@ -288,6 +298,8 @@ funarg({'fun', _, _Clauses, _Extra}, _S) -> true; funarg({'fun', _, {function,_,_,_}}, _S) -> %% New abstract format for fun M:F/A in R15. true; +funarg({named_fun, _, _, _, _}, _S) -> + true; funarg({var, _, Var}, S) -> member(Var, S#xrefr.funvars); funarg(_, _S) -> false. @@ -295,10 +307,14 @@ fun_args(apply2, [FunArg, Args]) -> {FunArg, Args}; fun_args(1, [FunArg | Args]) -> {FunArg, Args}; fun_args(2, [_Node, FunArg | Args]) -> {FunArg, Args}. -list2term([A | As]) -> - {cons, 0, A, list2term(As)}; -list2term([]) -> - {nil, 0}. +list2term(L) -> + A = erl_anno:new(0), + list2term(L, A). + +list2term([A | As], Anno) -> + {cons, Anno, A, list2term(As)}; +list2term([], Anno) -> + {nil, Anno}. term2list({cons, _Line, H, T}, L, S) -> term2list(T, [H | L], S); @@ -325,10 +341,11 @@ handle_call(Locality, Module, Name, Arity, Line, S) -> handle_call(Locality, To, Line, S, false) end. -handle_call(Locality, To0, Line, S, IsUnres) -> +handle_call(Locality, To0, Anno, S, IsUnres) -> From = S#xrefr.function, To = adjust_arity(S, To0), Call = {From, To}, + Line = erl_anno:line(Anno), CallAt = {Call, Line}, S1 = if IsUnres -> diff --git a/lib/tools/src/xref_scanner.erl b/lib/tools/src/xref_scanner.erl index 990f8aa87b..0b14159fbe 100644 --- a/lib/tools/src/xref_scanner.erl +++ b/lib/tools/src/xref_scanner.erl @@ -1,24 +1,27 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. +%% Copyright Ericsson AB 2000-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% -module(xref_scanner). +-include("xref.hrl"). + -export([scan/1]). scan(Chars) -> @@ -77,7 +80,7 @@ lex([V={var,N,Var} | L]) -> lex([T | Ts]) -> [T | lex(Ts)]; lex([]) -> - [{'$end', -1}]. + [{'$end', erl_anno:new(?XREF_END_LINE)}]. is_type('Rel') -> true; is_type('App') -> true; diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl index 7b72165e6f..438ec93962 100644 --- a/lib/tools/src/xref_utils.erl +++ b/lib/tools/src/xref_utils.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2013. All Rights Reserved. +%% Copyright Ericsson AB 2000-2014. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. %% %% %CopyrightEnd% %% @@ -437,7 +438,7 @@ regexpr({ModExpr, FunExpr, ArityExpr}, Var) -> V2 end. -%% -> digraph() +%% -> digraph:graph() relation_to_graph(S) -> G = digraph:new(), Fun = fun({From, To}) -> |