1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2017-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21-module(lcnt_SUITE). 22 23-include_lib("common_test/include/ct.hrl"). 24 25-export( 26 [all/0, suite/0, 27 init_per_suite/1, end_per_suite/1, 28 init_per_testcase/2, end_per_testcase/2]). 29 30-export( 31 [toggle_lock_counting/1, error_on_invalid_category/1, preserve_locks/1, 32 registered_processes/1, registered_db_tables/1]). 33 34suite() -> 35 [{ct_hooks,[ts_install_cth]}, 36 {timetrap, {seconds, 10}}]. 37 38all() -> 39 [toggle_lock_counting, error_on_invalid_category, preserve_locks, 40 registered_processes, registered_db_tables]. 41 42init_per_suite(Config) -> 43 case erlang:system_info(lock_counting) of 44 true -> 45 %% The tests will run straight over these properties, so we have to 46 %% preserve them to avoid tainting the other tests. 47 OldCopySave = erts_debug:lcnt_control(copy_save), 48 OldMask = erts_debug:lcnt_control(mask), 49 [{lcnt_SUITE, {OldCopySave, OldMask}} | Config]; 50 _ -> 51 {skip, "Lock counting is not enabled"} 52 end. 53 54end_per_suite(Config) -> 55 {OldCopySave, OldMask} = proplists:get_value(lcnt_SUITE, Config), 56 57 erts_debug:lcnt_control(copy_save, OldCopySave), 58 OldCopySave = erts_debug:lcnt_control(copy_save), 59 60 erts_debug:lcnt_control(mask, OldMask), 61 OldMask = erts_debug:lcnt_control(mask), 62 63 erts_debug:lcnt_clear(), 64 ok. 65 66init_per_testcase(_Case, Config) -> 67 disable_lock_counting(), 68 Config. 69 70end_per_testcase(_Case, _Config) -> 71 ok. 72 73disable_lock_counting() -> 74 ok = erts_debug:lcnt_control(copy_save, false), 75 ok = erts_debug:lcnt_control(mask, []), 76 ok = erts_debug:lcnt_clear(), 77 78 %% Sanity check. 79 false = erts_debug:lcnt_control(copy_save), 80 [] = erts_debug:lcnt_control(mask), 81 82 %% The above commands rely on some lazy operations, so we'll have to wait 83 %% for the list to clear. 84 ok = wait_for_empty_lock_list(). 85 86wait_for_empty_lock_list() -> 87 wait_for_empty_lock_list(10). 88wait_for_empty_lock_list(Tries) when Tries > 0 -> 89 try_flush_cleanup_ops(), 90 [{duration, _}, {locks, Locks}] = erts_debug:lcnt_collect(), 91 case remove_untoggleable_locks(Locks) of 92 [] -> 93 ok; 94 _ -> 95 timer:sleep(50), 96 wait_for_empty_lock_list(Tries - 1) 97 end; 98wait_for_empty_lock_list(0) -> 99 ct:fail("Lock list failed to clear after disabling lock counting."). 100 101%% Queue up a lot of thread progress cleanup ops in a vain attempt to 102%% flush the lock list. 103try_flush_cleanup_ops() -> 104 false = lists:member(process, erts_debug:lcnt_control(mask)), 105 [spawn(fun() -> ok end) || _ <- lists:seq(1, 1000)]. 106 107%% 108%% Test cases 109%% 110 111toggle_lock_counting(Config) when is_list(Config) -> 112 Categories = 113 [allocator, db, debug, distribution, generic, io, process, scheduler], 114 lists:foreach( 115 fun(Category) -> 116 Locks = get_lock_info_for(Category), 117 if 118 Locks =/= [] -> 119 disable_lock_counting(); 120 Locks =:= [] -> 121 ct:fail("Failed to toggle ~p locks.", [Category]) 122 end 123 end, Categories). 124 125get_lock_info_for(Categories) when is_list(Categories) -> 126 ok = erts_debug:lcnt_control(mask, Categories), 127 [{duration, _}, {locks, Locks}] = erts_debug:lcnt_collect(), 128 remove_untoggleable_locks(Locks); 129 130get_lock_info_for(Category) when is_atom(Category) -> 131 get_lock_info_for([Category]). 132 133preserve_locks(Config) when is_list(Config) -> 134 erts_debug:lcnt_control(mask, [process]), 135 136 erts_debug:lcnt_control(copy_save, true), 137 [spawn(fun() -> ok end) || _ <- lists:seq(1, 1000)], 138 139 %% Wait for the processes to be fully destroyed before disabling copy_save, 140 %% then remove all active locks from the list. (There's no foolproof method 141 %% to do this; sleeping before/after is the best way we have) 142 timer:sleep(500), 143 144 erts_debug:lcnt_control(copy_save, false), 145 erts_debug:lcnt_control(mask, []), 146 147 try_flush_cleanup_ops(), 148 timer:sleep(500), 149 150 case erts_debug:lcnt_collect() of 151 [{duration, _}, {locks, Locks}] when length(Locks) > 0 -> 152 ct:pal("Preserved ~p locks.", [length(Locks)]); 153 [{duration, _}, {locks, []}] -> 154 ct:fail("copy_save didn't preserve any locks.") 155 end. 156 157error_on_invalid_category(Config) when is_list(Config) -> 158 {error, badarg, q_invalid} = erts_debug:lcnt_control(mask, [q_invalid]), 159 ok. 160 161registered_processes(Config) when is_list(Config) -> 162 %% There ought to be at least one registered process (init/code_server) 163 erts_debug:lcnt_control(mask, [process]), 164 [_, {locks, ProcLocks}] = erts_debug:lcnt_collect(), 165 true = lists:any( 166 fun 167 ({proc_main, RegName, _, _}) when is_atom(RegName) -> true; 168 (_Lock) -> false 169 end, ProcLocks), 170 ok. 171 172registered_db_tables(Config) when is_list(Config) -> 173 %% There ought to be at least one registered table (code) 174 erts_debug:lcnt_control(mask, [db]), 175 [_, {locks, DbLocks}] = erts_debug:lcnt_collect(), 176 true = lists:any( 177 fun 178 ({db_tab, RegName, _, _}) when is_atom(RegName) -> true; 179 (_Lock) -> false 180 end, DbLocks), 181 ok. 182 183%% Not all locks can be toggled on or off due to technical limitations, so we 184%% need to filter them out when checking whether we successfully disabled lock 185%% counting. 186remove_untoggleable_locks([]) -> 187 []; 188remove_untoggleable_locks([{resource_monitors, _, _, _} | T]) -> 189 remove_untoggleable_locks(T); 190remove_untoggleable_locks([H | T]) -> 191 [H | remove_untoggleable_locks(T)]. 192