From e3c82f04a315824e03c57c8395b95ec132ca5ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Thu, 26 Nov 2009 20:08:35 +0100 Subject: Add lock profiling tool The Lock profiling tool, lcnt, can make use of the internal lock statistics when the runtime system is built with this feature enabled. This provides a mechanism to examine potential lock bottlenecks within the runtime itself. --- lib/tools/src/Makefile | 4 +- lib/tools/src/lcnt.erl | 693 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 696 insertions(+), 1 deletion(-) create mode 100644 lib/tools/src/lcnt.erl (limited to 'lib') diff --git a/lib/tools/src/Makefile b/lib/tools/src/Makefile index 81933cda14..2105f51d5f 100644 --- a/lib/tools/src/Makefile +++ b/lib/tools/src/Makefile @@ -34,11 +34,13 @@ RELSYSDIR = $(RELEASE_PATH)/lib/tools-$(VSN) # Common Macros # ---------------------------------------------------- -MODULES= cover \ +MODULES= \ + cover \ cover_web \ eprof \ fprof \ cprof \ + lcnt \ instrument \ make \ tags \ diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl new file mode 100644 index 0000000000..c9a217c83a --- /dev/null +++ b/lib/tools/src/lcnt.erl @@ -0,0 +1,693 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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/. +%% +%% 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% +%% + +-module(lcnt). +-behaviour(gen_server). +-author("Björn-Egil Dahlberg"). + +%% gen_server callbacks +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + +%% start/stop +-export([ + start/0, + stop/0 + ]). + +%% gen_server call api +-export([ + raw/0, + collect/0, + collect/1, + clear/0, + clear/1, + conflicts/0, + conflicts/1, + locations/0, + locations/1, + inspect/1, + inspect/2, + information/0, + swap_pid_keys/0, + % set options + set/1, + set/2, + + load/1, + save/1 + ]). + +%% convenience +-export([ + apply/3, + apply/2, + help/0 + ]). + +-define(version, "1.0"). + +-record(state, { + locks = [], + duration = 0 + }). + + +-record(stats, { + file, + line, + tries, + colls, + time, % us + nt % #timings collected + }). + +-record(lock, { + name, + id, + type, + stats = [] + + }). + +-record(print, { + name, + id, + type, + entry, + tries, + colls, + cr, % collision ratio + time, + dtr % time duration ratio + }). + +%% -------------------------------------------------------------------- %% +%% +%% start/stop/init +%% +%% -------------------------------------------------------------------- %% + +start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). +stop() -> gen_server:cast(?MODULE, stop). +init([]) -> {ok, #state{ locks = [], duration = 0 } }. + +%% -------------------------------------------------------------------- %% +%% +%% API implementation +%% +%% -------------------------------------------------------------------- %% + +clear() -> erts_debug:lock_counters(clear). +clear(Node) -> rpc:call(Node, erts_debug, lock_counters, [clear]). +collect() -> gen_server:call(?MODULE, {collect, erts_debug:lock_counters(info)}). +collect(Node) -> gen_server:call(?MODULE, {collect, rpc:call(Node, erts_debug, lock_counters, [info])}). +locations() -> gen_server:call(?MODULE, {locations,[]}). +locations(Opts) -> gen_server:call(?MODULE, {locations, Opts}). +conflicts() -> gen_server:call(?MODULE, {conflicts, []}). +conflicts(Opts) -> gen_server:call(?MODULE, {conflicts, Opts}). +inspect(Lock) -> gen_server:call(?MODULE, {inspect, Lock, []}). +inspect(Lock, Opts) -> gen_server:call(?MODULE, {inspect, Lock, Opts}). +information() -> gen_server:call(?MODULE, information). +swap_pid_keys() -> gen_server:call(?MODULE, swap_pid_keys). +raw() -> gen_server:call(?MODULE, raw). +set(Option, Value) -> gen_server:call(?MODULE, {set, Option, Value}). +set({Option, Value}) -> gen_server:call(?MODULE, {set, Option, Value}). +save(Filename) -> gen_server:call(?MODULE, {save, Filename}, infinity). +load(Filename) -> start(), gen_server:call(?MODULE, {load, Filename}, infinity). + +%% -------------------------------------------------------------------- %% +%% +%% convenience implementation +%% +%% -------------------------------------------------------------------- %% + +apply(M,F,As) when is_atom(M), is_atom(F), is_list(As) -> + lcnt:start(), + lcnt:clear(), + Res = erlang:apply(M,F,As), + lcnt:collect(), + Res. + +apply(Fun, As) when is_function(Fun) -> + lcnt:start(), + lcnt:clear(), + Res = erlang:apply(Fun, As), + lcnt:collect(), + Res. + +help() -> + Help = + "lcnt:conflicts() -> ok\n" + "lcnt:conflicts(Opts) -> ok\n" + " Returns a list of internal lock counters.\n" + " Opts = [Opt]\n" + " Opt = {sort, Sort} | {threshold, Threshold} | {print, PrintOpts} | {max_locks, MaxLocks} {combine, bool()}, {location, bool()}\n" + " Sort = name | id | type | tries | colls | ratio | time | entry\n" + " Threshold = {tries, integer()} | {colls, integer()} | {time, integer()}\n" + " PrintOpts = [PrintOpt | {PrintOpt, Width}]\n" + " PrintOpt = name | id | type | entry | tries | colls | ratio | time | duration\n" + " Width = integer()\n" + " MaxLocks = integer()\n", + io:format("~s", [Help]), + ok. + +%% -------------------------------------------------------------------- %% +%% +%% handle_call +%% +%% -------------------------------------------------------------------- %% + +% printing + +handle_call({conflicts, InOpts}, _From, #state{ locks = Locks } = State) when is_list(InOpts) -> + Default = [ + {sort, time}, + {reverse, false}, + {print, [name,id,tries,colls,ratio,time,duration]}, + {max_locks, 20}, + {combine, true}, + {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, + {locations, false}], + Opts = options(InOpts, Default), + Combos = combine_classes(Locks, proplists:get_value(combine, Opts)), + Printables = locks2print(Combos, State#state.duration), + Filtered = filter_print(Printables, Opts), + + print_lock_information(Filtered, proplists:get_value(print, Opts)), + + {reply, ok, State}; + +handle_call(information, _From, State) -> + print_state_information(State), + {reply, ok, State}; + +handle_call({locations, InOpts}, _From, #state{ locks = Locks } = State) when is_list(InOpts) -> + Default = [ + {sort, time}, + {reverse, false}, + {print, [name,id,tries,colls,ratio,time,duration]}, + {max_locks, 20}, + {combine, true}, + {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, + {locations, false}], + + Opts = options(InOpts, Default), + Printables = filter_print([#print{ + name = term2string("~w", [Names]), + entry = term2string("~p:~p", [Stats#stats.file, Stats#stats.line]), + colls = Stats#stats.colls, + tries = Stats#stats.tries, + cr = percent(Stats#stats.colls, Stats#stats.tries), + time = Stats#stats.time, + dtr = percent(Stats#stats.time, State#state.duration) + } || {Stats, Names} <- combine_locations(Locks) ], Opts), + + print_lock_information(Printables, proplists:get_value(print, Opts)), + + {reply, ok, State}; + +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]}, + {max_locks, 20}, + {combine, false}, + {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, + {locations, false}], + + Opts = options(InOpts, Default), + Filtered = filter_locks(Lockname, Locks), + IDs = case {proplists:get_value(full_id, Opts), proplists:get_value(combine, Opts)} of + {true, true} -> locks_ids(Filtered); + _ -> [] + end, + Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)), + case proplists:get_value(locations, Opts) of + true -> + lists:foreach(fun + (L) -> + IdString = case proplists:get_value(full_id, Opts) of + true -> term2string(proplists:get_value(L#lock.name, IDs, L#lock.id)); + _ -> term2string(L#lock.id) + end, + print("lock: " ++ term2string(L#lock.name)), + print("id: " ++ IdString), + print("type: " ++ term2string(L#lock.type)), + Combined = [Stats || {Stats,_} <- combine_locations(L#lock.stats)], + Ps = stats2print(Combined, Duration), + Opts1 = options([{{print, [entry, name,id,tries,colls,ratio,time,duration]}, + print_lock_information(filter_print(Ps, Opts), proplists:get_value(print, Opts)) + end, Combos); + _ -> + print_lock_information(locks2print(Combos, Duration), proplists:get_value(print, Opts)) + end, + {reply, ok, State}; + +handle_call(raw, _From, #state{ locks = Locks} = State)-> + {reply, Locks, State}; + +% collecting +handle_call({collect, Data}, _From, State)-> + {reply, ok, data2state(Data, State)}; + +% manipulate +handle_call(swap_pid_keys, _From, #state{ locks = Locks } = State)-> + SwappedLocks = lists:map(fun + (L) when L#lock.name =:= port_lock; L#lock.type =:= proclock -> + L#lock{ id = L#lock.name, name = L#lock.id }; + (L) -> + L + end, Locks), + + {reply, ok, State#state{ locks = SwappedLocks}}; + +% settings +handle_call({set, data, Data}, _From, State)-> + {reply, ok, data2state(Data, State)}; + +% file operations +handle_call({load, Filename}, _From, State) -> + case file:read_file(Filename) of + {ok, Binary} -> + case binary_to_term(Binary) of + {?version, Statelist} -> + {reply, ok, list2state(Statelist)}; + {Version, _} -> + {reply, {error, {mismatch, Version, ?version}}, State} + end; + Error -> + {reply, {error, Error}, State} + end; + +handle_call({save, Filename}, _From, State) -> + Binary = term_to_binary({?version, state2list(State)}), + case file:write_file(Filename, Binary) of + ok -> + {reply, ok, State}; + Error -> + {reply, {error, Error}, State} + end; + + +handle_call(Command, _From, State) -> + {reply, {error, {undefined, Command}}, State}. + +%% -------------------------------------------------------------------- %% +%% +%% handle_cast +%% +%% -------------------------------------------------------------------- %% + +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- %% +%% +%% handle_info +%% +%% -------------------------------------------------------------------- %% + +handle_info(_Info, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- %% +%% +%% termination +%% +%% -------------------------------------------------------------------- %% + +terminate(_Reason, _State) -> + ok. + +%% -------------------------------------------------------------------- %% +%% +%% code_change +%% +%% -------------------------------------------------------------------- %% + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% -------------------------------------------------------------------- %% +%% +%% AUX +%% +%% -------------------------------------------------------------------- %% + +% summate + +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}) -> + 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_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}). + + +%% manipulators + +filter_locks({Lockname, Ids}, Locks) when is_list(Ids) -> + [ L || L <- Locks, L#lock.name == Lockname, lists:member(L#lock.id, Ids)]; +filter_locks({Lockname, Id}, Locks) -> + [ L || L <- Locks, L#lock.name == Lockname, L#lock.id == Id ]; +filter_locks(Lockname, Locks) -> + [ L || L <- Locks, L#lock.name == Lockname ]. +% order of processing +% 2. cut thresholds +% 3. sort locks +% 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). + +% cut locks not above certain thresholds +threshold_locks(Locks, Thresholds) -> + Tries = proplists:get_value(tries, Thresholds, -1), + Colls = proplists:get_value(colls, Thresholds, -1), + Time = proplists:get_value(time, Thresholds, -1), + [ L || L <- Locks, L#print.tries > Tries, L#print.colls > Colls, L#print.time > Time]. + +cut_locks(Locks, N) when is_integer(N), N > 0 -> lists:sublist(Locks, N); +cut_locks(Locks, _) -> Locks. + +%% reversal +reverse_locks(Locks, true) -> lists:reverse(Locks); +reverse_locks(Locks, _) -> Locks. + +%% combine_locations +%% In: +%% Locations :: [#lock{}] | [#stats{}] +%% Out: +%% [{{File,Line}, #stats{}, [Lockname]}] + + +combine_locations(Locations) -> gb_trees:values(combine_locations(Locations, gb_trees:empty())). +combine_locations([], Tree) -> Tree; +combine_locations([S|_] = Stats, Tree) when is_record(S, stats) -> + combine_locations(Stats, undefined, Tree); +combine_locations([#lock{ stats = Stats, name = Name}|Ls], Tree) -> + combine_locations(Ls, combine_locations(Stats, Name, Tree)). + +combine_locations([], _, Tree) -> Tree; +combine_locations([S|Ss], Name, Tree) when is_record(S, stats)-> + Key = {S#stats.file, S#stats.line}, + Tree1 = case gb_trees:lookup(Key, Tree) of + none -> + gb_trees:insert(Key, {S, [Name]}, Tree); + {value, {C, Names}} -> + NewNames = case lists:member(Name, Names) of + true -> Names; + _ -> [Name | Names] + end, + gb_trees:update(Key, { + C#stats{ + tries = C#stats.tries + S#stats.tries, + colls = C#stats.colls + S#stats.colls, + time = C#stats.time + S#stats.time, + nt = C#stats.nt + S#stats.nt + }, NewNames}, Tree) + end, + combine_locations(Ss, Name, Tree1). + +%% combines all statistics for a class (name) lock +%% id's are translated to #id's. + +combine_classes(Locks, true) -> combine_classes1(Locks, gb_trees:empty()); +combine_classes(Locks, _) -> Locks. + +combine_classes1([], Tree) -> gb_trees:values(Tree); +combine_classes1([L|Ls], Tree) -> + Key = L#lock.name, + case gb_trees:lookup(Key, Tree) of + none -> + combine_classes1(Ls, gb_trees:insert(Key, L#lock{ id = 1 }, Tree)); + {value, C} -> + combine_classes1(Ls, gb_trees:update(Key, C#lock{ + id = C#lock.id + 1, + stats = L#lock.stats ++ C#lock.stats + }, Tree)) + end. + +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)]) + end. + +stats2print(Stats, Duration) -> + lists:map(fun + (S) -> + #print{ + entry = term2string("~p:~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) + } + 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 + } + end, Locks). + +%% state making + +data2state(Data, State) -> + Duration = time2us(proplists:get_value(duration, Data)), + Rawlocks = proplists:get_value(locks, Data), + Locks = locks2records(Rawlocks), + State#state{ + duration = Duration, + locks = Locks + }. + +% [{name, id, type, [{{file, line}, {tries, colls, {s, ns, n}}}] +locks2records(Locks) -> + [ #lock{ + name = Name, + id = 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] + } || {Name, Id, Type, Stats} <- Locks + ]. + + +%% serializer + +state_default(Field) -> proplists:get_value(Field, state2list(#state{})). + +state2list(State) -> + [_|Values] = tuple_to_list(State), + lists:zipwith(fun + (locks, Locks) -> {locks, [lock2list(Lock) || Lock <- Locks]}; + (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]). + +%% printing + +%% print_lock_information +%% In: +%% Locks :: [#lock{}] +%% Print :: [Type | {Type, integer()}] +%% +%% Out: +%% ok + +print_lock_information(Locks, Print) -> + print_header(Print), + lists:foreach(fun + (L) -> + print_lock(L, Print) + end, Locks), + ok. + +print_header(Opts) -> + Header = #print{ + name = "lock", + id = "id", + type = "type", + entry = "location", + tries = "#tries", + colls = "#collisions", + cr = "collisions [%]", + time = "time [us]", + dtr = "duration [%]" + }, + Divider = #print{ + name = lists:duplicate(1 + length(Header#print.name), 45), + id = lists:duplicate(1 + length(Header#print.id), 45), + type = lists:duplicate(1 + length(Header#print.type), 45), + entry = lists:duplicate(1 + length(Header#print.entry), 45), + tries = lists:duplicate(1 + length(Header#print.tries), 45), + 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) + }, + 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) -> + case Opt of + id -> print_lock(L, Opts, [{space, 18, 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, 25, 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) + end. + +print_state_information(#state{ locks = Locks} = State) -> + Stats = summate_locks(Locks), + print("information:"), + print(kv("#locks", s(length(Locks)))), + print(kv("duration", s(State#state.duration) ++ " us" ++ " (" ++ s(State#state.duration/1000000) ++ " s)")), + print("\nsummated stats:"), + print(kv("#tries", s(Stats#stats.tries))), + print(kv("#colls", s(Stats#stats.colls))), + print(kv("wait time", s(Stats#stats.time) ++ " us" ++ " ( " ++ s(Stats#stats.time/1000000) ++ " s)")), + print(kv("percent of duration", s(Stats#stats.time/State#state.duration*100) ++ " %")), + ok. + +%% AUX + +time2us({S, Ns}) -> round(S*1000000 + Ns/1000). + +percent(_,0) -> 0.0; +percent(T,N) -> T/N*100. + +options(Opts, Default) when is_list(Default) -> + options1(proplists:unfold(Opts), Default). +options1([], Defaults) -> Defaults; +options1([{Key, Value}|Opts], Defaults) -> + case proplists:get_value(Key, Defaults) of + undefined -> options1(Opts, [{Key, Value} | Defaults]); + _ -> options1(Opts, [{Key, Value} | proplists:delete(Key, Defaults)]) + end. + +%%% AUX STRING FORMATTING + +print(String) -> io:format("~s~n", [String]). + +kv(Key, Value) -> kv(Key, Value, 20). +kv(Key, Value, Offset) -> term2string(term2string("~~~ps : ~~s", [Offset]),[Key, Value]). + +s(T) when is_float(T) -> term2string("~.4f", [T]); +s(T) when is_list(T) -> term2string("~s", [T]); +s(T) -> term2string("~p", [T]). + +strings(Strings) -> strings(Strings, []). +strings([], Out) -> Out; +strings([{space, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string("~~~ps", [N]), [S])); +strings([{format, Format, S} | Ss], Out) -> strings(Ss, Out ++ term2string(Format, [S])); +strings([S|Ss], Out) -> strings(Ss, Out ++ term2string("~s", [S])). + + +term2string({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> term2string("~p:~p/~p", [M,F,A]); +term2string(Term) -> term2string("~w", [Term]). +term2string(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). -- cgit v1.2.3 From eec56b21f02368b085b0b1076233448459b4c555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Mon, 1 Feb 2010 11:51:06 +0100 Subject: Add lcnt documentation Documentation for the lock profiling tool --- lib/tools/doc/src/Makefile | 2 + lib/tools/doc/src/lcnt.xml | 465 +++++++++++++++++++++++++++++++++++++ lib/tools/doc/src/lcnt_chapter.xml | 301 ++++++++++++++++++++++++ lib/tools/doc/src/part.xml | 3 + lib/tools/doc/src/ref_man.xml | 6 + 5 files changed, 777 insertions(+) create mode 100644 lib/tools/doc/src/lcnt.xml create mode 100644 lib/tools/doc/src/lcnt_chapter.xml (limited to 'lib') diff --git a/lib/tools/doc/src/Makefile b/lib/tools/doc/src/Makefile index bab607c4bd..5ac8718b61 100644 --- a/lib/tools/doc/src/Makefile +++ b/lib/tools/doc/src/Makefile @@ -41,6 +41,7 @@ XML_REF3_FILES = \ eprof.xml \ fprof.xml \ cprof.xml \ + lcnt.xml \ instrument.xml \ make.xml \ tags.xml \ @@ -53,6 +54,7 @@ XML_CHAPTER_FILES = \ cover_chapter.xml \ fprof_chapter.xml \ cprof_chapter.xml \ + lcnt_chapter.xml \ erlang_mode_chapter.xml \ xref_chapter.xml \ notes.xml \ diff --git a/lib/tools/doc/src/lcnt.xml b/lib/tools/doc/src/lcnt.xml new file mode 100644 index 0000000000..3c55e4e422 --- /dev/null +++ b/lib/tools/doc/src/lcnt.xml @@ -0,0 +1,465 @@ + + + + +
+ + 2009 + 2010 + Ericsson AB, 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. + + The Initial Developer of the Original Code is Ericsson AB. + + + lcnt + Björn-Egil Dahlberg + nobody + + nobody + + 2009-11-26 + PA1 + lcnt.xml +
+ lcnt + A runtime system Lock Profiling tool. + +

The lcnt module is used to profile the internal ethread locks in the + Erlang Runtime System. With lcnt enabled, Internal counters in the + runtime system are updated each time a lock is taken. The counters stores + information about the number of acquisition tries and the number of collisions + that has occurred during the acquisition tries. The counters also record the + waiting time a lock has caused for a blocked thread when a collision has occurred. +

+

+ The data produced by the lock counters will give an estimate on how well + the runtime system will behave from a parallelizable view point for the + scenarios tested. This tool was mainly developed to help erlang runtime + developers iron out potential and generic bottlenecks. +

+

Locks in the emulator are named after what type of resource they protect and where + in the emulator they are initialized, those are lock 'classes'. Most of those + locks are also instantiated several times, and given unique identifiers, to increase + locking granularity. Typically an instantiated lock protects a disjunct set of + the resource, i.e ets-tables, processes or ports. In other cases it protects a + specific range of a resource, e.g. pix_lock which protects index to process + mappings, and is given a unique number within the class. A unique lock in lcnt + is referenced by a name (class) and an identifier, {Name, Id}. +

+

Some locks in the system are static and protects global resources, for example + bif_timers and the run_queue locks. Other locks are dynamic and not + necessarily long lived, for example process locks and ets-table locks. The + statistics data from short lived locks can be stored separately when the locks + are deleted. This behavior is by default turned off to save memory but can be + turned on via lcnt:rt_opt({copy_save, true}). The lcnt:apply/1,2,3 + functions enables this behavior during profiling. +

+
+ + + + start() -> {ok, Pid} | {error, {already_started, Pid}} + Starts the lock profiler server. + + Pid = pid() + + +

Starts the lock profiler server. The server only act as a medium for the + user and performs filtering and printing of data collected by lcnt:collect/1. +

+
+
+ + + stop() -> ok + Stops the lock profiler server. + +

Stops the lock profiler server.

+
+
+ + + collect() -> ok + Same as collect(node()). +

Same as collect(node()).

+
+ + + collect(Node) -> ok + Collects lock statistics from the runtime system. + + Node = node() + + +

Collects lock statistics from the runtime system. The function starts a + server if it is not already started. It then populates the server with lock + statistics. If the server held any lock statistics data before the collect then + that data is lost. +

+ +

+ When collection occurs the runtime system transitions to a single thread, + blocking all other threads. No other tasks will be scheduled during this + operation. Depending on the size of the data this might take a long time + (several seconds) and cause timeouts in the system. +

+
+
+
+ + + clear() -> ok + Same as clear(node()). +

Same as clear(node()).

+
+ + + clear(Node) -> ok + Clears the internal lock statistics from runtime system. + + Node = node() + + +

Clears the internal lock statistics from the runtime system. This does not clear the + data on the server only on runtime system. All counters for static locks are zeroed, + all dynamic locks currently alive are zeroed and all saved locks now destroyed are removed. + It also resets the duration timer. +

+
+
+ + conflicts() -> ok + Same as conflicts([]). +

Same as conflicts([]).

+
+ + conflicts([Option]) -> ok + Prints a list of internal lock counters. + + Option = {sort, Sort} | {reverse, bool()} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()} + Sort = name | id | type | tries | colls | ratio | time | entry + Thresholds = {tries, integer()} | {colls, integer()} | {time, integer()} + Print = name | id | type | entry | tries | colls | ratio | time | duration + MaxLocks = integer() | none + + +

Prints a list of internal locks and its statistics.

+

For option description, see lcnt:inspect/2.

+
+
+ + + locations() -> ok + Same as locations([]). + +

Same as locations([]).

+
+
+ + locations([Option]) -> ok + Prints a list of internal lock counters by source code locations. + + Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()} + Sort = name | id | type | tries | colls | ratio | time | entry + Thresholds = {tries, integer()} | {colls, integer()} | {time, integer()} + Print = name | id | type | entry | tries | colls | ratio | time | duration + MaxLocks = integer() | none + + +

Prints a list of internal lock counters by source code locations.

+

For option description, see lcnt:inspect/2.

+
+
+ + + inspect(Lock) -> ok + Same as inspect(Lock, []). +

Same as inspect(Lock, []).

+
+ + inspect(Lock, [Option]) -> ok + Prints a list of internal lock counters for a specific lock. + + Lock = Name | {Name, Id | [Id]} + Name = atom() | pid() | port() + Id = atom() | integer() | pid() | port() + Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()} | {locations, bool()} + Sort = name | id | type | tries | colls | ratio | time + Thresholds = {tries, integer()} | {colls, integer()} | {time, integer()} + Print = name | id | type | entry | tries | colls | ratio | time | duration + MaxLocks = integer() | none + + +

Prints a list of internal lock counters for a specific lock.

+

Lock Name and Id for ports and processes are interchangeable with the use of lcnt:swap_pid_keys/0 and is the reason why pid() and port() options can be used in both Name and Id space. Both pids and ports are special identifiers with stripped creation and can be recreated with lcnt:pid/2,3 and lcnt:port/1,2.

+

Option description:

+ + {combine, bool()} + Combine the statistics from different instances of a lock class. +
Default: true +
+ + {locations, bool()} + Print the statistics by source file and line numbers. +
Default: false +
+ + {max_locks, MaxLocks} + Maximum number of locks printed or no limit with none. +
Default: 20 +
+ + {print, PrintOptions} + Printing options: + + name + Named lock or named set of locks (classes). The same name used for initializing the lock in the VM. + + id + Internal id for set of locks, not always unique. This could be table name for ets tables (db_tab), port id for ports, integer identifiers for allocators, etc. + + type + Type of lock: rw_mutex, mutex, spinlock, rw_spinlock or proclock. + + entry + In combination with {locations, true} this option prints the lock operations source file and line number entry-points along with statistics for each entry. + + tries + Number of acquisitions of this lock. + + colls + Number of collisions when a thread tried to acquire this lock. This is when a trylock is EBUSY, a write try on read held rw_lock, a try read on write held rw_lock, a thread tries to lock an already locked lock. (Internal states supervises this). + + ratio + The ratio between the number of collisions and the number of tries (acquisitions) in percentage. + + time + Accumulated waiting time for this lock. This could be greater than actual wall clock time, it is accumulated for all threads. Trylock conflicts does not accumulate time. + + duration + Percentage of accumulated waiting time of wall clock time. This percentage can be higher than 100% since accumulated time is from all threads. + +
Default: [name,id,tries,colls,ratio,time,duration] +
+ + {reverse, bool()} + Reverses the order of sorting. +
Default: false +
+ + {sort, Sort} + Column sorting orders. +
Default: time +
+ + {thresholds, Thresholds} + Filtering thresholds. Anything values above the threshold value are passed through. +
Default: [{tries, 0}, {colls, 0}, {time, 0}] +
+ +
+ +
+
+ + + information() -> ok + Prints lcnt server state and generic information about collected lock statistics. + +

Prints lcnt server state and generic information about collected lock statistics.

+
+
+ + + swap_pid_keys() -> ok + Swaps places on Name and Id space for ports and processes. + +

Swaps places on Name and Id space for ports and processes.

+
+
+ + + load(Filename) -> ok + Restores previously saved data to the server. + + Filename = filename() + + +

Restores previously saved data to the server.

+
+
+ + + save(Filename) -> ok + Saves the collected data to file. + + Filename = filename() + + +

Saves the collected data to file.

+
+
+
+ +
+ Convenience functions +

The following functions are used for convenience.

+
+ + + apply(Fun) -> term() + Same as apply(Fun, []). + +

Same as apply(Fun, []).

+
+
+ + apply(Fun, Args) -> term() + Clears counters, applies function and collects the profiling results. + + Fun = fun() + Args = [term()] + + +

Clears the lock counters and then setups the instrumentation to save all destroyed locks. + After setup the fun is called, passing the elements in Args as arguments. + When the fun returns the statistics are immediately collected to the server. After the + collection the instrumentation is returned to its previous behavior. + The result of the applied fun is returned. +

+
+
+ + apply(Module, Function, Args) -> term() + Clears counters, applies function and collects the profiling results. + + Module = atom() + Function = atom() + Args = [term()] + + +

Clears the lock counters and then setups the instrumentation to save all destroyed locks. + After setup the function is called, passing the elements in Args as arguments. + When the function returns the statistics are immediately collected to the server. After the + collection the instrumentation is returned to its previous behavior. + The result of the applied function is returned. +

+
+
+ + + pid(Id, Serial) -> pid() + Same as pid(node(), Id, Serial). +

Same as pid(node(), Id, Serial).

+
+ + pid(Node, Id, Serial) -> pid() + Creates a process id with creation 0. + + Node = node() + Id = integer() + Serial = integer() + + +

Creates a process id with creation 0. Example:

+
+
+ + + port(Id) -> port() + Same as port(node(), Id). +

Same as port(node(), Id).

+
+ + port(Node, Id) -> port() + Creates a port id with creation 0. + + Node = node() + Id = integer() + +

Creates a port id with creation 0.

+
+ +
+ +
+ Internal runtime lock counter controllers +

The following functions control the behavior of the internal counters.

+
+ + + + rt_collect() -> [lock_counter_data()] + Same as rt_collect(node()). +

Same as rt_collect(node()).

+
+ + rt_collect(Node) -> [lock_counter_data()] + Returns a list of raw lock counter data. + + Node = node() + +

Returns a list of raw lock counter data.

+
+ + + rt_clear() -> ok + Same as rt_clear(node()). +

Same as rt_clear(node()).

+
+ + rt_clear(Node) -> ok + Clears the internal counters. + + Node = node() + +

Clear the internal counters. Same as lcnt:clear(Node).

+
+ + + rt_opt({Type, bool()}) -> bool() + Same as rt_opt(node(), {Type, Opt}). +

Same as rt_opt(node(), {Type, Opt}).

+
+ + rt_opt(Node, {Type, bool()}) -> bool() + Changes the lock counter behavior and returns the previous behaviour. + + Node = node() + Type = copy_save | process_locks + + +

Changes the lock counter behavior and returns the previous behaviour.

+

Option description:

+ + {copy_save, bool()} + Enable statistics saving from destroyed locks by copying. This might consume a lot of memory. +
Default: false +
+ + {process_locks, bool()} + Profile process locks. +
Default: true +
+
+
+
+
+ +
+ See Also +

LCNT User's Guide

+
+
diff --git a/lib/tools/doc/src/lcnt_chapter.xml b/lib/tools/doc/src/lcnt_chapter.xml new file mode 100644 index 0000000000..8f44b23f59 --- /dev/null +++ b/lib/tools/doc/src/lcnt_chapter.xml @@ -0,0 +1,301 @@ + + + + +
+ + 20092010 + Ericsson AB. 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. + + + + lcnt - The Lock Profiler + Björn-Egil Dahlberg + nobody + + nobody + no + 2009-11-26 + PA1 + lcnt_chapter.xml +
+

+ Internally in the Erlang runtime system locks are used to protect resources from being updated from multiple threads in a fatal way. Locks are necessary + to ensure that the runtime system works properly but it also introduces a couple of limitations. Lock contention and locking overhead. +

+

+ With lock contention we mean when one thread locks a resource and another thread, or threads, tries to acquire the same resource at the same time. The lock will deny + the other thread access to the resource and the thread will be blocked from continuing its execution. The second thread has to wait until the first thread has + completed its access to the resource and unlocked it. The lcnt tool measures these lock conflicts. +

+

+ Locks has an inherent cost in execution time and memory space. It takes time initialize, destroy, aquiring or releasing locks. To decrease lock contention it + some times necessary to use finer grained locking strategies. This will usually also increase the locking overhead and hence there is a tradeoff + between lock contention and overhead. In general, lock contention increases with the number of threads running concurrently. The lcnt tool does not measure locking overhead. +

+
+ Enabling lock-counting +

For investigation of locks in the emulator we use an internal tool called lcnt (short for lock-count). The VM needs to be compiled with this option enabled. To enable this, use:

+ +
+cd $ERL_TOP
+./configure --enable-lock-counter
+	
+ +

+ Another way to enable this alongside a normal VM is to compile it at emulator directory level, much like a debug build. To compile it this way do the following, +

+
+cd $ERL_TOP/erts/emulator
+make lcnt FLAVOR=smp
+	
+

and then starting Erlang with,

+
+$ERL_TOP/bin/cerl -lcnt
+	
+

To verify that you lock-counting enabled check that [lock-counting] appears in the status text when the VM is started.

+
+Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe]
+ [kernel-poll:false] [lock-counting]
+	
+
+
+ Getting started +

Once you have a lock counting enabled VM the module lcnt can be used. The module is intended to be used from the current running nodes shell. To access remote nodes use lcnt:clear(Node) and lcnt:collect(Node).

+ +

All locks are continuously monitored and its statistics updated. Use lcnt:clear/0 to initially clear all counters before running any specific tests. This command will also reset the duration timer internally.

+

To retrieve lock statistics information use, lcnt:collect/0,1. The collect operation will start a lcnt server if it not already started. All collected data will be built into an erlang term and uploaded to the server and a duration time will also be uploaded. This duration is the time between lcnt:clear/0,1 and lcnt:collect/0,1.

+ +

Once the data is collected to the server it can be filtered, sorted and printed in many different ways.

+

See the reference manual for a description of each function.

+
+
+ Example of usage +

From the Erlang shell:

+
+Erlang R13B03 (erts-5.7.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe]
+ [kernel-poll:false] [lock-counting]
+1> lcnt:rt_opt({copy_save, true}).
+false
+2> lcnt:clear(), big:bang(1000), lcnt:collect().
+ok
+3> lcnt:conflicts().
+                   lock   id  #tries  #collisions  collisions [%]  time [us]  duration [%]
+                  -----  --- ------- ------------ --------------- ---------- -------------
+         alcu_allocator   50 4113692       158921          3.8632     215464        4.4962
+               pix_lock  256 4007140         4882          0.1218      12221        0.2550
+              run_queue    8 2287246         6949          0.3038       9825        0.2050
+              proc_main 1029 3115778        25755          0.8266       1199        0.0250
+              proc_msgq 1029 2467022         1910          0.0774       1048        0.0219
+            proc_status 1029 5708439         2435          0.0427        706        0.0147
+ message_pre_alloc_lock    8 2008569          134          0.0067         90        0.0019
+              timeofday    1   54065            8          0.0148         22        0.0005
+                gc_info    1    7071            7          0.0990          5        0.0001
+ok
+
+

+ Another way to to profile a specific function is to use lcnt:apply/3 or lcnt:apply/1 which does lcnt:clear/0 before the function and lcnt:collect/0 after its invocation. + It also sets copy_save to true for the duration of the function call +

+
+Erlang R13B03 (erts-5.7.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe]
+ [kernel-poll:false] [lock-counting]
+1> lcnt:apply(fun() -> big:bang(1000) end).
+4384.338
+2> lcnt:conflicts().
+                   lock   id  #tries  #collisions  collisions [%]  time [us]  duration [%]
+                  -----  --- ------- ------------ --------------- ---------- -------------
+         alcu_allocator   50 4117913       183091          4.4462     234232        5.1490
+              run_queue    8 2050398         3801          0.1854       6700        0.1473
+               pix_lock  256 4007080         4943          0.1234       2847        0.0626
+              proc_main 1028 3000178        28247          0.9415       1022        0.0225
+              proc_msgq 1028 2293677         1352          0.0589        545        0.0120
+            proc_status 1028 5258029         1744          0.0332        442        0.0097
+ message_pre_alloc_lock    8 2009322          147          0.0073         82        0.0018
+              timeofday    1   48616            9          0.0185         13        0.0003
+                gc_info    1    7455           12          0.1610          9        0.0002
+ok
+
+

The process locks are sorted after its class like all other locks. It is convenient to look at specific processes and ports as classes. We can do this by swapping class and class identifiers with lcnt:swap_pid_keys/0.

+
+3> lcnt:swap_pid_keys().
+ok
+4> lcnt:conflicts([{print, [name, tries, ratio, time]}]).
+                   lock  #tries  collisions [%]  time [us]
+                  ----- ------- --------------- ----------
+         alcu_allocator 4117913          4.4462     234232
+              run_queue 2050398          0.1854       6700
+               pix_lock 4007080          0.1234       2847
+ message_pre_alloc_lock 2009322          0.0073         82
+  <nonode@nohost.660.0>   13493          1.4452         41
+  <nonode@nohost.724.0>   13504          1.1404         36
+  <nonode@nohost.803.0>   13181          1.6235         35
+  <nonode@nohost.791.0>   13534          0.8202         22
+   <nonode@nohost.37.0>    8744          5.8326         22
+  <nonode@nohost.876.0>   13335          1.1174         19
+  <nonode@nohost.637.0>   13452          1.3678         19
+  <nonode@nohost.799.0>   13497          1.8745         18
+  <nonode@nohost.469.0>   11009          2.5343         18
+  <nonode@nohost.862.0>   13131          1.2566         16
+  <nonode@nohost.642.0>   13216          1.7327         15
+  <nonode@nohost.582.0>   13156          1.1098         15
+  <nonode@nohost.622.0>   13420          0.7303         14
+  <nonode@nohost.596.0>   13141          1.6437         14
+  <nonode@nohost.592.0>   13346          1.2064         13
+  <nonode@nohost.526.0>   13076          1.1701         13
+ok
+
+
+
+ Example with Mnesia Transaction Benchmark +

From the Erlang shell:

+
+Erlang R13B03 (erts-5.7.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe]
+ [kernel-poll:false] [lock-counting]
+
+Eshell V5.7.4  (abort with ^G)
+1> Conf=[{db_nodes, [node()]}, {driver_nodes, [node()]}, {replica_nodes, [node()]},
+ {n_drivers_per_node, 10}, {n_branches, 1000}, {n_accounts_per_branch, 10},
+ {replica_type, ram_copies}, {stop_after, 60000}, {reuse_history_id, true}].
+[{db_nodes,[nonode@nohost]},
+ {driver_nodes,[nonode@nohost]},
+ {replica_nodes,[nonode@nohost]},
+ {n_drivers_per_node,10},
+ {n_branches,1000},
+ {n_accounts_per_branch,10},
+ {replica_type,ram_copies},
+ {stop_after,60000},
+ {reuse_history_id,true}]
+2> mnesia_tpcb:init([{use_running_mnesia, false}|Conf]).
+ignore
+
+

Initial configuring of the benchmark is done. It is time to profile the actual benchmark and Mnesia

+
+3> lcnt:apply(fun() -> {ok,{time, Tps,_,_,_,_}} = mnesia_tpcb:run([{use_running_mnesia,
+ true}|Conf]), Tps/60 end).
+12037.483333333334
+ok
+4> lcnt:swap_pid_keys().
+ok
+
+

The id header represents the number of unique identifiers under a class when the option {combine, true} is used (which is on by default). It will otherwise show the specific identifier. +The db_tab listing shows 722287 unique locks, it is one for each ets-table created and Mnesia creates one for each transaction. +

+
+5> lcnt:conflicts().
+                   lock     id   #tries  #collisions  collisions [%]  time [us]  duration [%]
+                  -----    ---  ------- ------------ --------------- ---------- -------------
+         alcu_allocator     50 56355118       732662          1.3001    2934747        4.8862
+                 db_tab 722287 94513441        63203          0.0669    1958797        3.2613
+              timeofday      1  2701048       175854          6.5106    1746079        2.9071
+               pix_lock    256 24306168       163214          0.6715     918309        1.5289
+              run_queue      8 11813811       152637          1.2920     357040        0.5945
+ message_pre_alloc_lock      8 17671449        57203          0.3237     263043        0.4380
+          mnesia_locker      4 17477633      1618548          9.2607      97092        0.1617
+              mnesia_tm      4  9891408       463788          4.6888      86353        0.1438
+                gc_info      1   823460          628          0.0763      24826        0.0413
+     meta_main_tab_slot     16 41393400         7193          0.0174      11393        0.0190
+ <nonode@nohost.1108.0>      4  4331412          333          0.0077       7148        0.0119
+            timer_wheel      1   203185           30          0.0148       3108        0.0052
+ <nonode@nohost.1110.0>      4  4291098          210          0.0049        885        0.0015
+ <nonode@nohost.1114.0>      4  4294702          288          0.0067        442        0.0007
+ <nonode@nohost.1113.0>      4  4346066          235          0.0054        390        0.0006
+ <nonode@nohost.1106.0>      4  4348159          287          0.0066        379        0.0006
+ <nonode@nohost.1111.0>      4  4279309          290          0.0068        325        0.0005
+ <nonode@nohost.1107.0>      4  4292190          302          0.0070        315        0.0005
+ <nonode@nohost.1112.0>      4  4208858          265          0.0063        276        0.0005
+ <nonode@nohost.1109.0>      4  4377502          267          0.0061        276        0.0005
+ok
+
+

The listing shows mnesia_locker, a process, has highly contended locks.

+
+6> lcnt:inspect(mnesia_locker).
+          lock          id  #tries  #collisions  collisions [%]  time [us]  duration [%]
+         -----         --- ------- ------------ --------------- ---------- -------------
+ mnesia_locker   proc_msgq 5449930        59374          1.0894      69781        0.1162
+ mnesia_locker   proc_main 4462782      1487374         33.3284      14398        0.0240
+ mnesia_locker proc_status 7564921        71800          0.9491      12913        0.0215
+ mnesia_locker   proc_link       0            0          0.0000          0        0.0000
+ok
+
+

Listing without class combiner.

+ +
+7> lcnt:conflicts([{combine, false}, {print, [name, id, tries, ratio, time]}]).
+                   lock                        id   #tries  collisions [%]  time [us]
+                  -----                       ---  ------- --------------- ----------
+                 db_tab mnesia_transient_decision   722250          3.9463    1856852
+              timeofday                 undefined  2701048          6.5106    1746079
+         alcu_allocator                 ets_alloc  7490696          2.2737     692655
+         alcu_allocator                 ets_alloc  7081771          2.3294     664522
+         alcu_allocator                 ets_alloc  7047750          2.2520     658495
+         alcu_allocator                 ets_alloc  5883537          2.3177     610869
+               pix_lock                        58 11011355          1.1924     564808
+               pix_lock                        60  4426484          0.7120     262490
+         alcu_allocator                 ets_alloc  1897004          2.4248     219543
+ message_pre_alloc_lock                 undefined  4211267          0.3242     128299
+              run_queue                         3  2801555          1.3003     116792
+              run_queue                         2  2799988          1.2700     100091
+              run_queue                         1  2966183          1.2712      78834
+          mnesia_locker                 proc_msgq  5449930          1.0894      69781
+ message_pre_alloc_lock                 undefined  3495672          0.3262      65773
+ message_pre_alloc_lock                 undefined  4189752          0.3174      58607
+              mnesia_tm                 proc_msgq  2094144          1.7184      56361
+              run_queue                         4  2343585          1.3115      44300
+                 db_tab                    branch  1446529          0.5229      38244
+                gc_info                 undefined   823460          0.0763      24826
+ok
+
+

+In this scenario the lock that protects ets-table mnesia_transient_decision has spent most of its waiting for. That is 1.8 seconds in a test that run for 60 seconds. The time is also spread on eight different scheduler threads. +

+
+8> lcnt:inspect(db_tab, [{print, [name, id, tries, colls, ratio, duration]}]).
+   lock                        id  #tries  #collisions  collisions [%]  duration [%]
+  -----                       --- ------- ------------ --------------- -------------
+ db_tab mnesia_transient_decision  722250        28502          3.9463        3.0916
+ db_tab                    branch 1446529         7564          0.5229        0.0637
+ db_tab                   account 1464500         8203          0.5601        0.0357
+ db_tab                    teller 1464529         8110          0.5538        0.0291
+ db_tab                   history  722250         3767          0.5216        0.0232
+ db_tab              mnesia_stats  750332         7057          0.9405        0.0180
+ db_tab        mnesia_trans_store      61            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      61            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ db_tab        mnesia_trans_store      53            0          0.0000        0.0000
+ok
+
+ +
+
+ Deciphering the output +

Typically high time values are bad and this is often the thing to look for. However, one should also look for high lock acquisition frequencies (#tries) since locks generate overhead and because high frequency could become problematic if they begin to have conflicts even if it is not shown in a particular test.

+
+ +
+ See Also +

LCNT Reference Manual

+
+
diff --git a/lib/tools/doc/src/part.xml b/lib/tools/doc/src/part.xml index 3e02086b80..19f0763410 100644 --- a/lib/tools/doc/src/part.xml +++ b/lib/tools/doc/src/part.xml @@ -55,6 +55,8 @@ instrument Utility functions for obtaining and analysing resource usage in an instrumented Erlang runtime system. + lcnt + A lock profiling tool for the Erlang runtime system. make A make utility for Erlang similar to UNIX make. tags @@ -69,6 +71,7 @@ + diff --git a/lib/tools/doc/src/ref_man.xml b/lib/tools/doc/src/ref_man.xml index aea74e3746..6cd4975075 100644 --- a/lib/tools/doc/src/ref_man.xml +++ b/lib/tools/doc/src/ref_man.xml @@ -51,9 +51,14 @@ Erlang programs. Uses trace to file to minimize runtime performance impact, and displays time for calling and called functions. + instrument Utility functions for obtaining and analysing resource usage in an instrumented Erlang runtime system. + + lcnt + A lock profiling tool for the Erlang runtime system. + make A make utility for Erlang similar to UNIX make. tags @@ -70,6 +75,7 @@ + -- cgit v1.2.3 From cfff4e99860a4d21a42645b20f76188dde704e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Tue, 2 Feb 2010 15:33:50 +0100 Subject: Add auto width on string output --- lib/tools/src/lcnt.erl | 167 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 53 deletions(-) (limited to 'lib') diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl index c9a217c83a..e1772537d3 100644 --- a/lib/tools/src/lcnt.erl +++ b/lib/tools/src/lcnt.erl @@ -64,7 +64,11 @@ -export([ apply/3, apply/2, - help/0 + apply/1, + all_conflicts/0, + all_conflicts/1, + pid/2, pid/3, + port/1, port/2 ]). -define(version, "1.0"). @@ -122,21 +126,23 @@ init([]) -> {ok, #state{ locks = [], duration = 0 } }. clear() -> erts_debug:lock_counters(clear). clear(Node) -> rpc:call(Node, erts_debug, lock_counters, [clear]). -collect() -> gen_server:call(?MODULE, {collect, erts_debug:lock_counters(info)}). -collect(Node) -> gen_server:call(?MODULE, {collect, rpc:call(Node, erts_debug, lock_counters, [info])}). -locations() -> gen_server:call(?MODULE, {locations,[]}). -locations(Opts) -> gen_server:call(?MODULE, {locations, Opts}). -conflicts() -> gen_server:call(?MODULE, {conflicts, []}). -conflicts(Opts) -> gen_server:call(?MODULE, {conflicts, Opts}). -inspect(Lock) -> gen_server:call(?MODULE, {inspect, Lock, []}). -inspect(Lock, Opts) -> gen_server:call(?MODULE, {inspect, Lock, Opts}). -information() -> gen_server:call(?MODULE, information). -swap_pid_keys() -> gen_server:call(?MODULE, swap_pid_keys). -raw() -> gen_server:call(?MODULE, raw). -set(Option, Value) -> gen_server:call(?MODULE, {set, Option, Value}). -set({Option, Value}) -> gen_server:call(?MODULE, {set, Option, Value}). -save(Filename) -> gen_server:call(?MODULE, {save, Filename}, infinity). -load(Filename) -> start(), gen_server:call(?MODULE, {load, Filename}, infinity). +collect() -> call({collect, erts_debug:lock_counters(info)}). +collect(Node) -> call({collect, rpc:call(Node, erts_debug, lock_counters, [info])}). +locations() -> call({locations,[]}). +locations(Opts) -> call({locations, Opts}). +conflicts() -> call({conflicts, []}). +conflicts(Opts) -> call({conflicts, Opts}). +inspect(Lock) -> call({inspect, Lock, []}). +inspect(Lock, Opts) -> call({inspect, Lock, Opts}). +information() -> call(information). +swap_pid_keys() -> call(swap_pid_keys). +raw() -> call(raw). +set(Option, Value) -> call({set, Option, Value}). +set({Option, Value}) -> call({set, Option, Value}). +save(Filename) -> call({save, Filename}). +load(Filename) -> start(), call({load, Filename}). + +call(Msg) -> gen_server:call(?MODULE, Msg, infinity). %% -------------------------------------------------------------------- %% %% @@ -151,6 +157,9 @@ apply(M,F,As) when is_atom(M), is_atom(F), is_list(As) -> lcnt:collect(), Res. +apply(Fun) when is_function(Fun) -> + lcnt:apply(Fun, []). + apply(Fun, As) when is_function(Fun) -> lcnt:start(), lcnt:clear(), @@ -158,21 +167,23 @@ apply(Fun, As) when is_function(Fun) -> lcnt:collect(), Res. -help() -> - Help = - "lcnt:conflicts() -> ok\n" - "lcnt:conflicts(Opts) -> ok\n" - " Returns a list of internal lock counters.\n" - " Opts = [Opt]\n" - " Opt = {sort, Sort} | {threshold, Threshold} | {print, PrintOpts} | {max_locks, MaxLocks} {combine, bool()}, {location, bool()}\n" - " Sort = name | id | type | tries | colls | ratio | time | entry\n" - " Threshold = {tries, integer()} | {colls, integer()} | {time, integer()}\n" - " PrintOpts = [PrintOpt | {PrintOpt, Width}]\n" - " PrintOpt = name | id | type | entry | tries | colls | ratio | time | duration\n" - " Width = integer()\n" - " MaxLocks = integer()\n", - io:format("~s", [Help]), - ok. +all_conflicts() -> all_conflicts(time). +all_conflicts(Sort) -> + conflicts([{max_locks, none}, {thresholds, []},{combine,false}, {sort, Sort}, {reverse, true}]). + +pid(Id, Serial) -> pid(node(), Id, Serial). +pid(Node, Id, Serial) when is_atom(Node) -> + Header = <<131,103,100>>, + String = atom_to_list(Node), + L = length(String), + binary_to_term(list_to_binary([Header, bytes16(L), String, bytes32(Id), bytes32(Serial),0])). + +port(Id) -> port(node(), Id). +port(Node, Id ) when is_atom(Node) -> + Header = <<131,102,100>>, + String = atom_to_list(Node), + L = length(String), + binary_to_term(list_to_binary([Header, bytes16(L), String, bytes32(Id), 0])). %% -------------------------------------------------------------------- %% %% @@ -208,15 +219,15 @@ handle_call({locations, InOpts}, _From, #state{ locks = Locks } = State) when is Default = [ {sort, time}, {reverse, false}, - {print, [name,id,tries,colls,ratio,time,duration]}, + {print, [name,entry,tries,colls,ratio,time,duration]}, {max_locks, 20}, {combine, true}, {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, - {locations, false}], + {locations, true}], Opts = options(InOpts, Default), Printables = filter_print([#print{ - name = term2string("~w", [Names]), + name = string_names(Names), entry = term2string("~p:~p", [Stats#stats.file, Stats#stats.line]), colls = Stats#stats.colls, tries = Stats#stats.tries, @@ -235,7 +246,7 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, loc {reverse, false}, {print, [name,id,tries,colls,ratio,time,duration]}, {max_locks, 20}, - {combine, false}, + {combine, true}, {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, {locations, false}], @@ -249,21 +260,32 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, loc case proplists:get_value(locations, Opts) of true -> lists:foreach(fun - (L) -> - IdString = case proplists:get_value(full_id, Opts) of - true -> term2string(proplists:get_value(L#lock.name, IDs, L#lock.id)); - _ -> term2string(L#lock.id) - end, - print("lock: " ++ term2string(L#lock.name)), - print("id: " ++ IdString), - print("type: " ++ term2string(L#lock.type)), - Combined = [Stats || {Stats,_} <- combine_locations(L#lock.stats)], - Ps = stats2print(Combined, Duration), - Opts1 = options([{{print, [entry, name,id,tries,colls,ratio,time,duration]}, - print_lock_information(filter_print(Ps, Opts), proplists:get_value(print, Opts)) + (#lock{ name = Name, id = Id, type = Type, stats = Stats }) -> + IdString = case proplists:get_value(full_id, Opts) of + true -> term2string(proplists:get_value(Name, IDs, Id)); + _ -> term2string(Id) + end, + Combined = [CStats || {CStats,_} <- combine_locations(Stats)], + case Combined of + [] -> + 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]}, + {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); _ -> - print_lock_information(locks2print(Combos, Duration), proplists:get_value(print, Opts)) + Print1 = locks2print(Combos, Duration), + Print2 = filter_print(Print1, Opts), + print_lock_information(Print2, proplists:get_value(print, Opts)) end, {reply, ok, State}; @@ -417,6 +439,13 @@ cut_locks(Locks, _) -> Locks. reverse_locks(Locks, true) -> lists:reverse(Locks); reverse_locks(Locks, _) -> Locks. + +%% +string_names([]) -> ""; +string_names(Names) -> string_names(Names, []). +string_names([Name], Strings) -> strings(lists:reverse([term2string(Name) | Strings])); +string_names([Name|Names],Strings) -> string_names(Names, [term2string(Name) ++ ","|Strings]). + %% combine_locations %% In: %% Locations :: [#lock{}] | [#stats{}] @@ -581,11 +610,43 @@ list2lock([F|Fs], List, Out) -> list2lock(Fs, List, [proplists:get_value(F, List %% Out: %% ok +auto_print_width(Locks, Print) -> + % iterate all lock entries to save all max length values + % these are records, so we do a little tuple <-> list smashing + R = lists:foldl(fun + (L, Max) -> + list_to_tuple(lists:reverse(lists:foldl(fun + ({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 = 3, type = 5, entry = 5, name = 5, tries = 7, colls = 13, cr = 16, time = 11, dtr = 14 }, + Locks), + % Setup the offsets for later pruning + Offsets = [ + {id, R#print.id}, + {name, R#print.name}, + {type, R#print.type}, + {entry, R#print.entry}, + {tries, R#print.tries}, + {colls, R#print.colls}, + {ratio, R#print.cr}, + {time, R#print.time}, + {duration, R#print.dtr}], + % Prune offsets to only allow specified print options + lists:foldr(fun + ({Type, W}, Out) -> [{Type, W}|Out]; + (Type, Out) -> [proplists:lookup(Type, Offsets)|Out] + end, [], Print). + print_lock_information(Locks, Print) -> - print_header(Print), + % remake Print to autosize entries + AutoPrint = auto_print_width(Locks, Print), + + print_header(AutoPrint), + lists:foreach(fun (L) -> - print_lock(L, Print) + print_lock(L, AutoPrint) end, Locks), ok. @@ -621,13 +682,13 @@ print_lock(L, Opts) -> print_lock(L, Opts, []). print_lock(_, [], Formats) -> print(strings(lists:reverse(Formats))); print_lock(L, [Opt|Opts], Formats) -> case Opt of - id -> print_lock(L, Opts, [{space, 18, s(L#print.id) } | Formats]); + 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, 25, s(L#print.name) } | 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]); @@ -639,7 +700,7 @@ print_lock(L, [Opt|Opts], 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) + _ -> print_lock(L, Opts, Formats) end. print_state_information(#state{ locks = Locks} = State) -> -- cgit v1.2.3 From 98b2678497e1d5eed9d343d7dc6f4ed3ac4a6094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Mon, 25 Jan 2010 14:52:48 +0100 Subject: Add lcnt:rt_opt/1 bindings to erts_debug Runtime options for copy_save. --- lib/tools/src/lcnt.erl | 138 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 112 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl index e1772537d3..53894b933c 100644 --- a/lib/tools/src/lcnt.erl +++ b/lib/tools/src/lcnt.erl @@ -37,6 +37,17 @@ stop/0 ]). +%% erts_debug:lock_counters api +-export([ + rt_collect/0, + rt_collect/1, + rt_clear/0, + rt_clear/1, + rt_opt/1, + rt_opt/2 + ]). + + %% gen_server call api -export([ raw/0, @@ -93,7 +104,6 @@ id, type, stats = [] - }). -record(print, { @@ -108,6 +118,8 @@ dtr % time duration ratio }). + + %% -------------------------------------------------------------------- %% %% %% start/stop/init @@ -118,16 +130,41 @@ start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). stop() -> gen_server:cast(?MODULE, stop). init([]) -> {ok, #state{ locks = [], duration = 0 } }. +%% -------------------------------------------------------------------- %% +%% +%% API erts_debug:lock_counters +%% +%% -------------------------------------------------------------------- %% + +rt_collect() -> + erts_debug:lock_counters(info). + +rt_collect(Node) -> + rpc:call(Node, erts_debug, lock_counters, [info]). + +rt_clear() -> + erts_debug:lock_counters(clear). + +rt_clear(Node) -> + rpc:call(Node, erts_debug, lock_counters, [clear]). + +rt_opt({Type, Opt}) -> + erts_debug:lock_counters({Type, Opt}). + +rt_opt(Node, {Type, Opt}) -> + rpc:call(Node, erts_debug, lock_counters, [{Type, Opt}]). + %% -------------------------------------------------------------------- %% %% %% API implementation %% %% -------------------------------------------------------------------- %% -clear() -> erts_debug:lock_counters(clear). -clear(Node) -> rpc:call(Node, erts_debug, lock_counters, [clear]). -collect() -> call({collect, erts_debug:lock_counters(info)}). -collect(Node) -> call({collect, rpc:call(Node, erts_debug, lock_counters, [info])}). +clear() -> rt_clear(). +clear(Node) -> rt_clear(Node). +collect() -> call({collect, rt_collect()}). +collect(Node) -> call({collect, rt_collect(Node)}). + locations() -> call({locations,[]}). locations(Opts) -> call({locations, Opts}). conflicts() -> call({conflicts, []}). @@ -152,9 +189,11 @@ call(Msg) -> gen_server:call(?MODULE, Msg, infinity). apply(M,F,As) when is_atom(M), is_atom(F), is_list(As) -> lcnt:start(), + Opt = lcnt:rt_opt({copy_save, true}), lcnt:clear(), Res = erlang:apply(M,F,As), lcnt:collect(), + lcnt:rt_opt({copy_save, Opt}), Res. apply(Fun) when is_function(Fun) -> @@ -162,9 +201,11 @@ apply(Fun) when is_function(Fun) -> apply(Fun, As) when is_function(Fun) -> lcnt:start(), + Opt = lcnt:rt_opt({copy_save, true}), lcnt:clear(), Res = erlang:apply(Fun, As), lcnt:collect(), + lcnt:rt_opt({copy_save, Opt}), Res. all_conflicts() -> all_conflicts(time). @@ -202,8 +243,10 @@ handle_call({conflicts, InOpts}, _From, #state{ locks = Locks } = State) when is {combine, true}, {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, {locations, false}], + Opts = options(InOpts, Default), - Combos = combine_classes(Locks, proplists:get_value(combine, Opts)), + Flocks = filter_locks_type(Locks, proplists:get_value(type, Opts)), + Combos = combine_classes(Flocks, proplists:get_value(combine, Opts)), Printables = locks2print(Combos, State#state.duration), Filtered = filter_print(Printables, Opts), @@ -246,12 +289,12 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration = Duration, loc {reverse, false}, {print, [name,id,tries,colls,ratio,time,duration]}, {max_locks, 20}, - {combine, true}, - {thresholds, [{tries, 0}, {colls, 0}, {time, 0}] }, + {combine, false}, + {thresholds, [] }, {locations, false}], Opts = options(InOpts, Default), - Filtered = filter_locks(Lockname, Locks), + Filtered = filter_locks(Locks, Lockname), IDs = case {proplists:get_value(full_id, Opts), proplists:get_value(combine, Opts)} of {true, true} -> locks_ids(Filtered); _ -> [] @@ -311,6 +354,9 @@ handle_call(swap_pid_keys, _From, #state{ locks = Locks } = State)-> handle_call({set, data, Data}, _From, State)-> {reply, ok, data2state(Data, State)}; +handle_call({set, duration, Duration}, _From, State)-> + {reply, ok, State#state{ duration = Duration}}; + % file operations handle_call({load, Filename}, _From, State) -> case file:read_file(Filename) of @@ -397,13 +443,19 @@ summate_stats([S|Ss], #stats{ tries = Tries, colls = Colls, time = Time, nt = Nt %% manipulators - -filter_locks({Lockname, Ids}, Locks) when is_list(Ids) -> - [ L || L <- Locks, L#lock.name == Lockname, lists:member(L#lock.id, Ids)]; -filter_locks({Lockname, Id}, Locks) -> - [ L || L <- Locks, L#lock.name == Lockname, L#lock.id == Id ]; -filter_locks(Lockname, Locks) -> - [ L || L <- Locks, L#lock.name == Lockname ]. +filter_locks_type(Locks, undefined) -> Locks; +filter_locks_type(Locks, all) -> Locks; +filter_locks_type(Locks, Types) when is_list(Types) -> + [ L || L <- Locks, lists:member(L#lock.type, Types)]; +filter_locks_type(Locks, Type) -> + [ L || L <- Locks, L#lock.type =:= Type]. + +filter_locks(Locks, {Lockname, Ids}) when is_list(Ids) -> + [ L || L <- Locks, L#lock.name =:= Lockname, lists:member(L#lock.id, Ids)]; +filter_locks(Locks, {Lockname, Id}) -> + [ L || L <- Locks, L#lock.name =:= Lockname, L#lock.id =:= Id ]; +filter_locks(Locks, Lockname) -> + [ L || L <- Locks, L#lock.name =:= Lockname ]. % order of processing % 2. cut thresholds % 3. sort locks @@ -554,11 +606,12 @@ data2state(Data, State) -> locks = Locks }. -% [{name, id, type, [{{file, line}, {tries, colls, {s, ns, n}}}] -locks2records(Locks) -> - [ #lock{ +locks2records(Locks) -> locks2records(Locks, []). +locks2records([], Out) -> Out; +locks2records([{Name, Id, Type, Stats}|Locks], Out) -> + Lock = #lock{ name = Name, - id = Id, + id = clean_id_creation(Id), type = Type, stats = [ #stats{ file = File, @@ -567,10 +620,21 @@ locks2records(Locks) -> colls = Colls, time = time2us({S, Ns}), nt = N - } || {{File, Line}, {Tries, Colls, {S, Ns, N}}} <- Stats] - } || {Name, Id, Type, Stats} <- Locks - ]. - + } || {{File, Line}, {Tries, Colls, {S, Ns, N}}} <- Stats] }, + locks2records(Locks, [Lock|Out]). + +clean_id_creation(Id) when is_pid(Id) -> + Bin = term_to_binary(Id), + <> = Bin, + Bin2 = list_to_binary([H, bytes16(L), Node, Ids, 0]), + binary_to_term(Bin2); +clean_id_creation(Id) when is_port(Id) -> + Bin = term_to_binary(Id), + <> = Bin, + Bin2 = list_to_binary([H, bytes16(L), Node, Ids, 0]), + binary_to_term(Bin2); +clean_id_creation(Id) -> + Id. %% serializer @@ -619,7 +683,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 = 3, type = 5, entry = 5, name = 5, tries = 7, 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 }, Locks), % Setup the offsets for later pruning Offsets = [ @@ -740,7 +804,7 @@ kv(Key, Value, Offset) -> term2string(term2string("~~~ps : ~~s", [Offset]),[Key, s(T) when is_float(T) -> term2string("~.4f", [T]); s(T) when is_list(T) -> term2string("~s", [T]); -s(T) -> term2string("~p", [T]). +s(T) -> term2string(T). strings(Strings) -> strings(Strings, []). strings([], Out) -> Out; @@ -750,5 +814,27 @@ strings([S|Ss], Out) -> strings(Ss, Out ++ term2string("~s", [S])). term2string({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> term2string("~p:~p/~p", [M,F,A]); +term2string(Term) when is_port(Term) -> + % ex #Port<6442.816> + <<_:3/binary, L:16, Node:L/binary, Ids:32, _/binary>> = term_to_binary(Term), + term2string("#Port<~s.~w>", [Node, Ids]); +term2string(Term) when is_pid(Term) -> + % ex <0.80.0> + <<_:3/binary, L:16, Node:L/binary, Ids:32, Serial:32, _/binary>> = term_to_binary(Term), + term2string("<~s.~w.~w>", [Node, Ids, Serial]); term2string(Term) -> term2string("~w", [Term]). term2string(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). + +%%% AUD id binary + +bytes16(Value) -> + B0 = Value band 255, + B1 = (Value bsr 8) band 255, + <>. + +bytes32(Value) -> + B0 = Value band 255, + B1 = (Value bsr 8) band 255, + B2 = (Value bsr 16) band 255, + B3 = (Value bsr 24) band 255, + <>. -- cgit v1.2.3 From 9a22cca549f88f955163e165b8849a6129925e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Mon, 1 Feb 2010 11:48:57 +0100 Subject: Add test suite for lcnt in tools --- lib/tools/test/Makefile | 7 +- lib/tools/test/lcnt_SUITE.erl | 154 ++++++++++++++++++++++++ lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt | Bin 0 -> 226100 bytes 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 lib/tools/test/lcnt_SUITE.erl create mode 100644 lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt (limited to 'lib') diff --git a/lib/tools/test/Makefile b/lib/tools/test/Makefile index a846a3a6f4..3a59be758a 100644 --- a/lib/tools/test/Makefile +++ b/lib/tools/test/Makefile @@ -26,6 +26,7 @@ MODULES = \ fprof_SUITE \ cprof_SUITE \ instrument_SUITE \ + lcnt_SUITE \ make_SUITE \ tools_SUITE \ xref_SUITE \ @@ -49,7 +50,8 @@ RELSYSDIR = $(RELEASE_PATH)/tools_test # FLAGS # ---------------------------------------------------- ERL_MAKE_FLAGS += -ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include +ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include \ + -I$(ERL_TOP)/lib/percept/include EBIN = . @@ -68,10 +70,11 @@ tests debug opt: make_emakefile clean: rm -f $(EMAKEFILE) rm -f $(TARGET_FILES) - rm -f core + rm -f core *~ docs: + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- diff --git a/lib/tools/test/lcnt_SUITE.erl b/lib/tools/test/lcnt_SUITE.erl new file mode 100644 index 0000000000..cb01d233f8 --- /dev/null +++ b/lib/tools/test/lcnt_SUITE.erl @@ -0,0 +1,154 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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% +%% + +-module(lcnt_SUITE). +-include("test_server.hrl"). + +%% Test server specific exports +-export([all/1]). +-export([init_per_suite/1, end_per_suite/1]). +-export([init_per_testcase/2, end_per_testcase/2]). + +%% Test cases +-export([ + load_v1/1, + conflicts/1, + locations/1, + swap_keys/1 + ]). + +%% Default timetrap timeout (set in init_per_testcase) +-define(default_timeout, ?t:minutes(2)). + +init_per_suite(Config) when is_list(Config) -> + Config. + +end_per_suite(Config) when is_list(Config) -> + Config. + +init_per_testcase(_Case, Config) -> + Dog = ?t:timetrap(?default_timeout), + [{watchdog,Dog} | Config]. + +end_per_testcase(_Case, Config) -> + Dog = ?config(watchdog, Config), + ?t:timetrap_cancel(Dog), + ok. + +all(suite) -> + % Test cases + [load_v1, conflicts, locations, swap_keys]. + +%%---------------------------------------------------------------------- +%% Tests +%%---------------------------------------------------------------------- + +load_v1(suite) -> + []; +load_v1(doc) -> + ["Load data from file."]; +load_v1(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:stop(), + ok. + +conflicts(suite) -> + []; +conflicts(doc) -> + ["API: conflicts"]; +conflicts(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:conflicts(), + THs = [-1, 0, 100, 1000], + Print = [name , id , type , entry , tries , colls , ratio , time , duration], + Opts = [ + [{sort, Sort}, {reverse, Rev}, {max_locks, ML}, {combine, Combine}, {thresholds, [TH]}, {print, [Print]}] || + Sort <- [name , id , type , tries , colls , ratio , time , entry], + ML <- [none, 1 , 32, 4096], + Combine <- [true, false], + TH <- [{tries, Tries} || Tries <- THs] ++ [{colls, Colls} || Colls <- THs] ++ [{time, Time} || Time <- THs], + Rev <- [true, false] + ], + ?line ok = test_conflicts_opts(Opts), + ?line ok = lcnt:stop(), + ok. + +test_conflicts_opts([]) -> ok; +test_conflicts_opts([Opt|Opts]) -> + ?line ok = lcnt:conflicts(Opt), + test_conflicts_opts(Opts). + +locations(suite) -> + []; +locations(doc) -> + ["API: locations"]; +locations(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:locations(), + THs = [-1, 0, 100, 1000], + Print = [name , id , type , entry , tries , colls , ratio , time , duration], + Opts = [ + [{full_id, Id}, {sort, Sort}, {max_locks, ML}, {combine, Combine}, {thresholds, [TH]}, {print, Print}] || + Sort <- [name , id , type , tries , colls , ratio , time , entry], + ML <- [none, 1 , 64], + Combine <- [true, false], + TH <- [{tries, Tries} || Tries <- THs] ++ [{colls, Colls} || Colls <- THs] ++ [{time, Time} || Time <- THs], + Id <- [true, false] + ], + ?line ok = test_locations_opts(Opts), + ?line ok = lcnt:stop(), + ok. + +test_locations_opts([]) -> ok; +test_locations_opts([Opt|Opts]) -> + ?line ok = lcnt:locations(Opt), + test_locations_opts(Opts). + +swap_keys(suite) -> + []; +swap_keys(doc) -> + ["Test interchanging port/process id with class"]; +swap_keys(Config) when is_list(Config) -> + ?line {ok, _} = lcnt:start(), + ?line Path = ?config(data_dir, Config), + ?line File = filename:join([Path,"big_bang_40.lcnt"]), + ?line ok = lcnt:load(File), + ?line ok = lcnt:conflicts(), + ?line ok = lcnt:swap_pid_keys(), + ?line ok = lcnt:conflicts(), + ?line ok = lcnt:stop(), + ok. + + +%%---------------------------------------------------------------------- +%% Auxiliary tests +%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Auxiliary +%%---------------------------------------------------------------------- diff --git a/lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt b/lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt new file mode 100644 index 0000000000..6087f6f37e Binary files /dev/null and b/lib/tools/test/lcnt_SUITE_data/big_bang_40.lcnt differ -- cgit v1.2.3