From e46015a3f7d5360f08a242e7a9a4ad74b884296b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 2 Nov 2023 06:33:41 +0100 Subject: [PATCH] WIP: cover: Use native coverage if enabled This is a crude modification to use the native coverage feature when it is enabled in the runtime system when running the compiler test suite. Some rough measurements when running the compiler test suite on my Intel iMac: * Without coverage: 4 min 22 sec * With -JPcover line_coverage: 5 min 42 sec * With -JPcover line_counters: 6 min 5 sec * With traditional coverage: 8 min 15 sec Note that when coverage is enabled for the compiler test suite, each test module will be recompiled, which does not happen when running without coverage. Therefore, these measurements don't say anything about the overhead for native coverage compared to uninstrumented code. To get a better idea of the overhead, I compiled the entire compiler with the `line_coverage` option, and then run the test case `compilation_SUITE:self_compile/1` with different configurations. I ran each test case several times for each configuration and noted the shortest time for each: * -JPcover false: 11.5 sec * -JPcover line_coverage: 11.5 sec * -JPcover line_counters: 12.4 sec I repeated the measurments on my M1 MacBook Pro and got the following times: * -JPcover false: 7.2 sec * -JPcover line_coverage: 7.2 sec * -JPcover line_counters: 8.0 sec At least according to this benchmark, the overhead for the line_coverage mode is negligible. --- lib/tools/src/cover.erl | 117 +++++++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 26 deletions(-) diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 6ed24c833316..8e8592a3f69c 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1373,11 +1373,16 @@ get_downs_r(Mons) -> get_data_for_remote_loading({Module,File}) -> [{Module,Code}] = ets:lookup(?BINARY_TABLE, Module), %%! The InitialTable list will be long if the module is big - what to do?? - Mapping = counters_mapping_table(Module), - InitialClauses = ets:lookup(?COVER_CLAUSE_TABLE,Module), + case has_native_coverage() of + true -> + #remote_data{module=Module,file=File,code=Code}; + false -> + Mapping = counters_mapping_table(Module), + InitialClauses = ets:lookup(?COVER_CLAUSE_TABLE,Module), - #remote_data{module=Module,file=File,code=Code, - mapping=Mapping,clauses=InitialClauses}. + #remote_data{module=Module,file=File,code=Code, + mapping=Mapping,clauses=InitialClauses} + end. %% Unload modules on remote nodes remote_unload(Nodes,UnloadedModules) -> @@ -1756,7 +1761,8 @@ do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile,LocalOnly) -> %% Compile and load the result. %% It's necessary to check the result of loading since it may %% fail, for example if Module resides in a sticky directory. - Options = SourceInfo ++ UserOptions, + Options = [report_errors|SourceInfo ++ UserOptions], + {ok, Module, Binary} = compile:forms(Forms, Options), case code:load_binary(Module, ?TAG, Binary) of @@ -1847,13 +1853,25 @@ create_counters(Mod) -> Cref. patch_code(Mod, Forms, false) -> - A = erl_anno:new(0), - AbstrKey = {tuple,A,[{atom,A,?MODULE},{atom,A,Mod}]}, - patch_code1(Forms, {distributed,AbstrKey}); + case has_native_coverage() of + true -> + _ = catch erlang:reset_coverage(Mod), + patch_code1(Forms, native_coverage); + false -> + A = erl_anno:new(0), + AbstrKey = {tuple,A,[{atom,A,?MODULE},{atom,A,Mod}]}, + patch_code1(Forms, {distributed,AbstrKey}) + end; patch_code(Mod, Forms, true) -> - Cref = create_counters(Mod), - AbstrCref = cid_to_abstract(Cref), - patch_code1(Forms, {local_only,AbstrCref}). + case has_native_coverage() of + true -> + _ = catch erlang:reset_coverage(Mod), + patch_code1(Forms, native_coverage); + false -> + Cref = create_counters(Mod), + AbstrCref = cid_to_abstract(Cref), + patch_code1(Forms, {local_only,AbstrCref}) + end. %% Go through the abstract code and replace 'executable_line' forms %% with the actual code to increment the counters. @@ -1871,6 +1889,8 @@ patch_code1({executable_line,_Anno,Index}, {local_only,AbstrCref}) -> A = element(2, AbstrCref), {call,A,{remote,A,{atom,A,counters},{atom,A,add}}, [AbstrCref,{integer,A,Index},{integer,A,1}]}; +patch_code1({executable_line,_Anno,_Index}=Line, native_coverage) -> + Line; patch_code1({clauses,Cs}, Key) -> {clauses,[patch_code1(El, Key) || El <- Cs]}; patch_code1({attribute, _, _, _} = Attribute, _Key) -> @@ -1902,28 +1922,39 @@ cid_to_abstract(Cref0) -> %% the main node. Also zero the counters. send_counters(Mod, CollectorPid) -> Process = fun(Chunk) -> send_chunk(CollectorPid, Chunk) end, - move_counters(Mod, Process). + Move = standard_move(Mod), + move_counters(Mod, Move, Process). %% Called on the main node. Collect the counters and consolidate %% them into the collection table. Also zero the counters. move_counters(Mod) -> - move_counters(Mod, fun insert_in_collection_table/1). + Process = fun insert_in_collection_table/1, + Move = case has_native_coverage() of + true -> + native_move(Mod); + false -> + standard_move(Mod) + end, + move_counters(Mod, Move, Process), + case has_native_coverage() of + true -> + _ = catch erlang:reset_coverage(Mod), + ok; + false -> + ok + end. -move_counters(Mod, Process) -> +move_counters(Mod, Move, Process) -> Pattern = {#bump{module=Mod,_='_'},'_'}, Matches = ets:match_object(?COVER_MAPPING_TABLE, Pattern, ?CHUNK_SIZE), - Cref = get_counters_ref(Mod), - move_counters1(Matches, Cref, Process). - -move_counters1({Mappings,Continuation}, Cref, Process) -> - Move = fun({Key,Index}) -> - Count = counters:get(Cref, Index), - ok = counters:sub(Cref, Index, Count), - {Key,Count} - end, - Process(lists:map(Move, Mappings)), - move_counters1(ets:match_object(Continuation), Cref, Process); -move_counters1('$end_of_table', _Cref, _Process) -> + move_counters1(Matches, Move, Process). + +move_counters1({Mappings,Continuation}, Move, Process) -> + Moved = [Move(Item) || Item <- Mappings], + %% io:format("~p\n", [Moved]), + Process(Moved), + move_counters1(ets:match_object(Continuation), Move, Process); +move_counters1('$end_of_table', _Move, _Process) -> ok. counters_mapping_table(Mod) -> @@ -1946,8 +1977,39 @@ clear_counters(Mod) -> _ = ets:match_delete(?COVER_MAPPING_TABLE, Pattern), ok. +standard_move(Mod) -> + Cref = get_counters_ref(Mod), + fun({Key,Index}) -> + Count = counters:get(Cref, Index), + ok = counters:sub(Cref, Index, Count), + {Key,Count} + end. + +native_move(Mod) -> + Coverage0 = erlang:get_line_coverage(Mod), + Coverage = maps:from_list(lists:sort(Coverage0)), + fun({#bump{line=Line}=Key,_Index}) -> + case Coverage of + #{Line := false} -> + {Key,0}; + #{Line := true} -> + {Key,1}; + #{Line := N} when is_integer(N), N >= 0 -> + {Key,N}; + #{} -> + {Key,0} + end + end. + %% Reset counters (set counters to 0). reset_counters(Mod) -> + case has_native_coverage() of + true -> + _ = catch erlang:reset_coverage(Mod), + ok; + false -> + ok + end, Pattern = {#bump{module=Mod,_='_'},'$1'}, MatchSpec = [{Pattern,[],['$1']}], Matches = ets:select(?COVER_MAPPING_TABLE, @@ -2665,3 +2727,6 @@ html_encoding(latin1) -> "iso-8859-1"; html_encoding(utf8) -> "utf-8". + +has_native_coverage() -> + erlang:system_info(line_coverage).