Przeglądaj źródła

ft: 修改为二进制匹配

master
SisMaker 4 lat temu
rodzic
commit
ed0e7a3059
10 zmienionych plików z 1555 dodań i 1471 usunięć
  1. +4
    -0
      include/eFmt.hrl
  2. +126
    -54
      src/eFmt.erl
  3. +68
    -60
      src/eFmtPretty.erl
  4. +283
    -283
      src/test/recon-2.5.1/recon.erl
  5. +354
    -354
      src/test/recon-2.5.1/recon_alloc.erl
  6. +160
    -160
      src/test/recon-2.5.1/recon_lib.erl
  7. +98
    -98
      src/test/recon-2.5.1/recon_map.erl
  8. +134
    -134
      src/test/recon-2.5.1/recon_rec.erl
  9. +312
    -312
      src/test/recon-2.5.1/recon_trace.erl
  10. +16
    -16
      src/test/recon-2.5.1/test.erl

+ 4
- 0
include/eFmt.hrl Wyświetl plik

@ -1,3 +1,6 @@
%% pretty
-define(LineCCnt, 120).
-define(base(Precision), case Precision of none -> 10; _ -> Precision end).
%%
-define(IIF(Cond, Ret1, Ret2), (case Cond of true -> Ret1; _ -> Ret2 end)).
@ -13,3 +16,4 @@
, strings :: boolean() %% l string设置为false
}).

+ 126
- 54
src/eFmt.erl Wyświetl plik

@ -105,7 +105,7 @@ write(Term, Depth) ->
write(Term, Depth, IsPretty) ->
case IsPretty of
true ->
eFmtPretty:pPrint(Term, 1, 120, Depth);
eFmtPretty:pPrint(Term, 1, ?LineCCnt, Depth);
_ ->
writeTerm(Term, Depth, latin1)
end.
@ -118,8 +118,33 @@ write(Term, Depth, Encoding, CharsLimit) ->
CharsLimit < 0 ->
writeTerm(Term, Depth, Encoding);
true ->
If = eFmtPretty:pIntermediate(Term, Depth, CharsLimit, no_fun, Encoding, _Str = false),
eFmtPretty:pWrite(If)
BinTerm = writeTerm(Term, Depth, ?LineCCnt, Encoding, false),
BinTermSize = erlang:byte_size(BinTerm),
if
CharsLimit < 0 ->
BinTerm;
BinTermSize > CharsLimit ->
<<(binary:part(BinTerm, 0, CharsLimit))/binary, "...">>;
true ->
BinTerm
end
end.
write(Term, Depth, Width, CharsLimit, Encoding, Strings) ->
if
Depth =:= 0 orelse CharsLimit =:= 0 ->
<<"...">>;
true ->
BinTerm = writeTerm(Term, Depth, Width, Encoding, Strings),
BinTermSize = erlang:byte_size(BinTerm),
if
CharsLimit < 0 ->
BinTerm;
BinTermSize > CharsLimit ->
<<(binary:part(BinTerm, 0, CharsLimit))/binary, "...">>;
true ->
BinTerm
end
end.
-define(writeInt(Int), integer_to_binary(Term)).
@ -143,21 +168,51 @@ writeList([One | List], D, E, BinAcc) ->
writeList(Other, D, E, BinAcc) ->
<<BinAcc/binary, "|", (writeTerm(Other, D, E))/binary, "]">>.
writeTuple(Tuple, D, E, Index, TupleSize, BinAcc) ->
writeList(List, Depth, Width, Encoding, Strings) ->
case Strings andalso visableList(List) of
true ->
list_to_binary(List);
_ ->
writeList([], Depth, Widt, Encoding, Strings, SumLC, <<"[">>)
end.
writeList([], Depth, Width, Encoding, Strings, SumLC, BinAcc) ->
<<BinAcc/binary, "]">>;
writeList([One], Depth, Width, Encoding, Strings, SumLC, BinAcc) ->
TermBin = writeTerm(One, Depth, Width, Encoding, Strings),
TermBinBinSize = erlang:byte_size(TermBin),
NewSumLC = SumLC + TermBinBinSize,
case NewSumLC >= Width of
true ->
<<BinAcc/binary, TermBin/binary, "]\n">>;
_ ->
<<BinAcc/binary, TermBin/binary, "]">>
end;
writeList([One | List], Depth, Width, Encoding, Strings, SumLC, BinAcc) ->
if
D =:= 1 -> <<BinAcc/binary, "...}">>;
Depth =:= 1 -> <<BinAcc, "|...]">>;
true ->
writeList(List, Depth - 1, E, <<BinAcc/binary, (writeTerm(One, Depth, E))/binary, ",">>)
end;
writeList(Term, Depth, Width, Encoding, Strings, <<"[">>) ->
<<BinAcc/binary, (writeTerm(One, D, E))/binary, "]">>.
writeTuple(Tuple, Depth, Width, CharsLimit, Encoding, Strings, Index, TupleSize, BinAcc) ->
if
Depth =:= 1 -> <<BinAcc/binary, "...}">>;
true ->
if
Index < TupleSize ->
writeTuple(Tuple, D - 1, E, Index + 1, TupleSize, <<BinAcc/binary, (writeTerm(element(Index, Tuple), D - 1, E))/binary, ",">>);
writeTuple(Tuple, Depth, Width, CharsLimit, Encoding, Strings, Index + 1, TupleSize, <<BinAcc/binary, (writeTerm(element(Index, Tuple), D - 1, E))/binary, ",">>);
Index == TupleSize ->
<<BinAcc/binary, (writeTerm(element(Index, Tuple), D - 1, E))/binary, "}">>;
<<BinAcc/binary, (writeTerm(element(Index, Tuple), Depth, Width, CharsLimit, Encoding, Strings))/binary, "}">>;
true ->
<<BinAcc/binary, "}">>
end
end.
writeMap(Map, D, E, BinAcc) when is_integer(D) ->
writeMap(Map, D, E, BinAcc) ->
if
D =:= 1 ->
<<BinAcc/binary, "...}">>;
@ -199,18 +254,42 @@ writeBinary(Bin, D, BinAcc) ->
end
end.
writeTerm(_Term, 0, _E) -> <<"...">>;
writeTerm(Term, _D, _E) when is_integer(Term) -> ?writeInt(Term);
writeTerm(Atom, _D, E) when is_atom(Atom) -> ?writeAtom(Atom, E);
writeTerm(Term, D, E) when is_list(Term) -> writeList(Term, D, E, <<"[">>);
writeTerm(Term, D, E) when is_map(Term) -> writeMap(Term, D, E, <<"#{">>);
writeTerm(Term, D, E) when is_tuple(Term) -> writeTuple(Term, D, E, 1, tuple_size(Term), <<"{">>);
writeTerm(Term, D, _E) when is_bitstring(Term) -> writeBinary(Term, D, <<"<<">>);
writeTerm(Term, _D, _E) when is_pid(Term) -> ?writePid(Term);
writeTerm(Term, _D, _E) when is_float(Term) -> ?writeFloat(Term);
writeTerm(Term, _D, _E) when is_port(Term) -> ?writePort(Term);
writeTerm(Term, _D, _E) when is_reference(Term) -> ?writeRef(Term);
writeTerm(Term, _D, _E) when is_function(Term) -> ?writeFun(Term).
writeBinary(Bin, Depth, Width, Encoding, Strings) ->
case Strings andalso visableBin(Bin) of
true ->
<<"<<", Bin/binary, ">>">>;
_ ->
writeBinary([], Depth, Width, Encoding, Strings, <<"<<">>)
end.
writeTerm(_Term, Depth, _E) when Depth =< 0 -> <<"...">>;
writeTerm(Term, _Depth, _E) when is_integer(Term) -> ?writeInt(Term);
writeTerm(Term, _Depth, E) when is_atom(Term) -> ?writeAtom(Term, E);
writeTerm(Term, Depth, E) when is_list(Term) -> writeList(Term, Depth, E, <<"[">>);
writeTerm(Term, Depth, E) when is_map(Term) -> writeMap(Term, Depth, E, <<"#{">>);
writeTerm(Term, Depth, E) when is_tuple(Term) -> writeTuple(Term, Depth, E, 1, tuple_size(Term), <<"{">>);
writeTerm(Term, Depth, _E) when is_bitstring(Term) -> writeBinary(Term, Depth, <<"<<">>);
writeTerm(Term, _Depth, _E) when is_pid(Term) -> ?writePid(Term);
writeTerm(Term, _Depth, _E) when is_float(Term) -> ?writeFloat(Term);
writeTerm(Term, _Depth, _E) when is_port(Term) -> ?writePort(Term);
writeTerm(Term, _Depth, _E) when is_reference(Term) -> ?writeRef(Term);
writeTerm(Term, _Depth, _E) when is_function(Term) -> ?writeFun(Term).
writeTerm(_Term, Depth, _Width, _Encoding, _Strings) when Depth =< 0 -> <<"...">>;
writeTerm(Term, _Depth, _Width, _Encoding, _Strings) when is_integer(Term) -> ?writeInt(Term);
writeTerm(Term, _Depth, _Width, Encoding, _Strings) when is_atom(Term) -> ?writeAtom(Term, Encoding);
writeTerm(Term, Depth, Width, Encoding, Strings) when is_list(Term) -> writeList(Term, Depth, Width, Encoding, Strings);
writeTerm(Term, Depth, Width, Encoding, Strings) when is_map(Term) ->
writeMap(Term, Depth, Width, Encoding, Strings, <<"#{">>);
writeTerm(Term, Depth, Width, Encoding, Strings) when is_tuple(Term) ->
writeTuple(Term, Depth, Width, Encoding, Strings, 1, tuple_size(Term), <<"{">>);
writeTerm(Term, Depth, Width, Encoding, Strings) when is_bitstring(Term) ->
writeBinary(Term, Depth, Width, Encoding, Strings, <<"<<">>);
writeTerm(Term, _Depth, _Width, _Encoding, _Strings) when is_pid(Term) -> ?writePid(Term);
writeTerm(Term, _Depth, _Width, _Encoding, _Strings) when is_float(Term) -> ?writeFloat(Term);
writeTerm(Term, _Depth, _Width, _Encoding, _Strings) when is_port(Term) -> ?writePort(Term);
writeTerm(Term, _Depth, _Width, _Encoding, _Strings) when is_reference(Term) -> ?writeRef(Term);
writeTerm(Term, _Depth, _Width, _Encoding, _Strings) when is_function(Term) -> ?writeFun(Term).
%% ********************************************** eFmt end *************************************************************
@ -223,19 +302,19 @@ fWrite(Format, Args) ->
fWrite(Format, Args, Options) ->
fBuild(fScan(Format, Args), Options).
%% ~F.P.PadModC
%% Parse all control sequences in the format string.
-spec fScan(Format :: io:format(), Data :: [term()]) -> FormatList :: [char() | fmtSpec()].
%% ~F.P.PadModC
fScan(Format, Args) ->
if
is_atom(Format) ->
doCollect(atom_to_binary(Format, utf8), Args, []);
is_binary(Format) ->
doCollect(Format, Args, []);
is_list(Format) ->
doCollect(list_to_binary(Format), Args, []);
is_atom(Format) ->
doCollect(atom_to_binary(Format, utf8), Args, []);
true ->
doCollect(Format, Args, [])
throw(bad_format)
end.
doCollect(FmtBinStr, Args, Acc) ->
@ -465,23 +544,19 @@ buildLimited([OneCA | Cs], NumOfPs, Count, MaxLen, I, Acc) ->
#fmtSpec{ctlChar = CtlChar, args = Args, width = Width, adjust = Adjust, precision = Precision, padChar = PadChar, encoding = Encoding, strings = Strings} ->
MaxChars = if MaxLen < 0 -> MaxLen; true -> MaxLen div Count end,
IoListStr = ctlLimited(CtlChar, Args, Width, Adjust, Precision, PadChar, Encoding, Strings, MaxChars, I),
NewNumOfPs = decrPc(CtlChar, NumOfPs),
NewNumOfPs = ?IIF(CtlChar == $p orelse CtlChar == $P, NumOfPs - 1, NumOfPs),
NewCount = Count - 1,
MaxLen = ?IIF(MaxLen < 0, MaxLen, remainChars(MaxLen, charsLen(IoListStr))),
NewMaxLen = ?IIF(MaxLen < 0, MaxLen, remainChars(MaxLen, charsLen(IoListStr))),
if
NewNumOfPs > 0 ->
buildLimited(Cs, NewNumOfPs, NewCount, MaxLen, I, [IoListStr | Acc]);
buildLimited(Cs, NewNumOfPs, NewCount, NewMaxLen, I, [IoListStr | Acc]);
true ->
buildLimited(Cs, NewNumOfPs, NewCount, MaxLen, I, [IoListStr | Acc])
buildLimited(Cs, NewNumOfPs, NewCount, NewMaxLen, I, [IoListStr | Acc])
end;
_ ->
buildLimited(Cs, NumOfPs, Count, MaxLen, I + 1, [OneCA | Acc])
end.
decrPc($p, Pc) -> Pc - 1;
decrPc($P, Pc) -> Pc - 1;
decrPc(_, Pc) -> Pc.
%% (CtlChar, Args, Width, Adjust, Precision, PadChar, Encoding, Strings, MaxChars, I)
ctlLimited($s, Args, Width, Adjust, Precision, PadChar, Encoding, _Strings, CharsLimit, _I) ->
case Encoding of
@ -499,13 +574,13 @@ ctlLimited($s, Args, Width, Adjust, Precision, PadChar, Encoding, _Strings, Char
ctlLimited($w, Args, Width, Adjust, Precision, PadChar, Encoding, _Strings, CharsLimit, _I) ->
Chars = write(Args, -1, Encoding, CharsLimit),
term(Chars, Width, Adjust, Precision, PadChar);
ctlLimited($p, Args, Width, Adjust, Precision, PadChar, Encoding, Strings, CharsLimit, I) ->
print(Args, -1, Width, Adjust, Precision, PadChar, Encoding, Strings, CharsLimit, I);
ctlLimited($p, Args, Width, _Adjust, _Precision, _PadChar, Encoding, Strings, CharsLimit, _I) ->
write(Args, -1, ?IIF(Width == none, ?LineCCnt, Width), CharsLimit, Encoding, Strings);
ctlLimited($W, [Args, Depth], Width, Adjust, Precision, PadChar, Encoding, _Strings, CharsLimit, _I) ->
Chars = write(Args, Depth, Encoding, CharsLimit),
term(Chars, Width, Adjust, Precision, PadChar);
ctlLimited($P, [Args, Depth], Width, Adjust, Precision, PadChar, Encoding, Strings, CharsLimit, I) ->
print(Args, Depth, Width, Adjust, Precision, PadChar, Encoding, Strings, CharsLimit, I).
ctlLimited($P, [Args, Depth], Width, _Adjust, _Precision, _PadChar, Encoding, Strings, CharsLimit, _I) ->
write(Args, Depth, ?IIF(Width == none, ?LineCCnt, Width), CharsLimit, Encoding, Strings).
term(BinStrOrIoList, Width, Adjust, Precision, PadChar) ->
if
@ -536,26 +611,22 @@ print(Term, Depth, Width, _Adjust, Precision, _PadChar, Encoding, Strings, Chars
Width == none ->
if
Precision == none ->
eFmtPretty:print(Term, I + 1, 120, Depth, -1, CharsLimit, no_fun, Encoding, Strings);
print(Term, I + 1, ?LineCCnt, Depth, -1, CharsLimit, no_fun, Encoding, Strings);
true ->
eFmtPretty:print(Term, Precision, 120, Depth, -1, CharsLimit, no_fun, Encoding, Strings)
print(Term, Precision, ?LineCCnt, Depth, -1, CharsLimit, no_fun, Encoding, Strings)
end;
true ->
if
Precision == none ->
eFmtPretty:print(Term, I + 1, Width, Depth, -1, CharsLimit, no_fun, Encoding, Strings);
print(Term, I + 1, Width, Depth, -1, CharsLimit, no_fun, Encoding, Strings);
true ->
eFmtPretty:print(Term, Precision, Width, Depth, -1, CharsLimit, no_fun, Encoding, Strings)
print(Term, Precision, Width, Depth, -1, CharsLimit, no_fun, Encoding, Strings)
end
end.
floatE(Float, Width, Adjust, Precision, PadChar) ->
case Precision of
none ->
NewPrecision = 6;
_ ->
NewPrecision = Precision
end,
NewPrecision = ?IIF(Precision == none, 6, Precision),
case Width of
none ->
float_to_binary(Float, [{scientific, NewPrecision}]);
@ -564,12 +635,8 @@ floatE(Float, Width, Adjust, Precision, PadChar) ->
end.
floatF(Float, Width, Adjust, Precision, PadChar) ->
case Precision of
none ->
NewPrecision = 6;
_ ->
NewPrecision = Precision
end,
NewPrecision = ?IIF(Precision == none, 6, Precision),
case Width of
none ->
float_to_binary(Float, [{decimals, NewPrecision}]);
@ -729,6 +796,11 @@ remainChars(T, E) ->
end.
%% ********************************************** eFmtFormat end *****************************************************
%% ********************************************** eFmtPretty start *****************************************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%dfsdfsdfsdfsdff start
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%dfsdfsdfsdfsdff start
%% ********************************************** eFmtPretty end *****************************************************
%% ********************************************** utils start **********************************************************
toLowerStr(BinStr) ->

+ 68
- 60
src/eFmtPretty.erl Wyświetl plik

@ -1,5 +1,7 @@
-module(eFmtPretty).
-include("eFmt.hrl").
-export([
pPrint/1
, pPrint/2
@ -24,7 +26,7 @@
-spec pPrint(term()) -> io_lib:chars().
pPrint(Term) ->
print(Term, 1, 120, -1, -1, -1, no_fun, latin1, true).
print(Term, 1, ?LineCCnt, -1, -1, -1, no_fun, latin1, true).
%% print(Term, RecDefFun) -> [Chars]
%% print(Term, Depth, RecDefFun) -> [Chars]
@ -55,7 +57,7 @@ pPrint(Term) ->
pPrint(Term, Options) when is_list(Options) ->
Col = get_option(column, Options, 1),
Ll = get_option(line_length, Options, 120),
Ll = get_option(line_length, Options, ?LineCCnt),
D = get_option(depth, Options, -1),
M = get_option(line_max_chars, Options, -1),
T = get_option(chars_limit, Options, -1),
@ -69,7 +71,7 @@ pPrint(Term, RecDefFun) ->
-spec pPrint(term(), depth(), rec_print_fun()) -> chars().
pPrint(Term, Depth, RecDefFun) ->
pPrint(Term, 1, 120, Depth, RecDefFun).
pPrint(Term, 1, ?LineCCnt, Depth, RecDefFun).
-spec pPrint(term(), column(), line_length(), depth()) -> chars().
pPrint(Term, Col, Ll, D) ->
@ -100,13 +102,11 @@ print(_, _, _, _D, _M, 0, _RF, _Enc, _Str) -> "...";
print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str) when Col =< 0 ->
%% ensure Col is at least 1
print(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str);
print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str) when is_atom(Atom) ->
write_atom(Atom, Enc);
print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str) when is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
%% preprocess and compute total number of chars
{_, Len, _Dots, _} = If =
case T < 0 of
true -> print_length(Term, D, T, RecDefFun, Enc, Str);
true -> printTerm(Term, D, T, RecDefFun, Enc, Str);
false -> pIntermediate(Term, D, T, RecDefFun, Enc, Str)
end,
%% use Len as CHAR_MAX if M0 = -1
@ -126,7 +126,49 @@ print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str) when is_tuple(Term); is_list
end;
print(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str) ->
%% atomic data types (bignums, atoms, ...) are never truncated
io_lib:write(Term).
eFmt:write(Term, _D, _Enc).
print(_, _, _, 0, _M, _T, _RF, _Enc, _Str) -> "...";
print(_, _, _, _D, _M, 0, _RF, _Enc, _Str) -> "...";
print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str) when Col =< 0 ->
%% ensure Col is at least 1
print(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str);
%% print(Term, Precision, Width, Depth, -1, CharsLimit, no_fun, Encoding, Strings)
print(Term, Precision, Width, Depth, M0_1, CharsLimit, RecDefFun, Encoding, Strings) when is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
if
Depth == 0 orelse CharsLimit == 0 ->
<<"...">>;
true ->
NewPrecision == ?IIF(Precision =< 0, 1, Precision),
if
is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
%% preprocess and compute total number of chars
{_, Len, _Dots, _} = If =
case CharsLimit < 0 of
true -> printTerm(Term, Depth, CharsLimit, RecDefFun, Encoding, Strings);
_ -> pIntermediate(Term, Depth, CharsLimit, RecDefFun, Encoding, Strings)
end,
%% use Len as CHAR_MAX if M0 = -1
M = max_cs(M0_1, Len),
if
Width =:= 0 ->
pWrite(If);
Len < Width - NewPrecision, Len =< M ->
%% write the whole thing on a single line when there is room
pWrite(If);
true ->
%% compute the indentation TInd for tagged tuples and records
TInd = while_fail([-1, 4],
fun(I) -> cind(If, NewPrecision, Width, M, I, 0, 0) end,
1),
pp(If, NewPrecision, Width, M, TInd, indent(NewPrecision), 0, 0)
end;
true ->
eFmt:write(Term, Depth, Encoding, CharsLimit)
end
end.
%%%
%%% Local functions
@ -425,7 +467,7 @@ write_tail(E, S) ->
pIntermediate(Term, Depth, CharsLimit, RF, Enc, Str) ->
D0 = 1,
If = print_length(Term, D0, CharsLimit, RF, Enc, Str),
If = printTerm(Term, D0, CharsLimit, RF, Enc, Str),
case If of
{_, Len, Dots, _} when Dots =:= 0; Len > CharsLimit; Depth =:= 1 ->
If;
@ -476,24 +518,10 @@ search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str) ->
search_depth(If, Upper, Term, T, D1, Du, RF, Enc, Str)
end.
%% The depth (D) is used for extracting and counting the characters to
%% print. The structure is kept so that the returned intermediate
%% format can be formatted. The separators (list, tuple, record, map) are
%% counted but need to be added later.
%% D =/= 0
print_length([], _D, _T, _RF, _Enc, _Str) ->
{"[]", 2, 0, no_more};
print_length({}, _D, _T, _RF, _Enc, _Str) ->
{"{}", 2, 0, no_more};
print_length(#{} = M, _D, _T, _RF, _Enc, _Str) when map_size(M) =:= 0 ->
{"#{}", 3, 0, no_more};
print_length(Atom, _D, _T, _RF, Enc, _Str) when is_atom(Atom) ->
S = write_atom(Atom, Enc),
{S, io_lib:chars_length(S), 0, no_more};
print_length(List, D, T, RF, Enc, Str) when is_list(List) ->
printTerm(List, D, T, RF, Enc, Str) when is_list(List) ->
%% only flat lists are "printable"
case Str andalso printable_list(List, D, T, Enc) of
case Str andalso visualList(List, D, T, Enc) of
true ->
%% print as string, escaping double-quotes in the list
S = write_string(List, Enc),
@ -515,27 +543,11 @@ print_length(List, D, T, RF, Enc, Str) when is_list(List) ->
If
end
end;
print_length(Fun, _D, _T, _RF, _Enc, _Str) when is_function(Fun) ->
S = io_lib:write(Fun),
{S, iolist_size(S), 0, no_more};
print_length(R, D, T, RF, Enc, Str) when is_atom(element(1, R)),
is_function(RF) ->
case RF(element(1, R), tuple_size(R) - 1) of
no ->
print_length_tuple(R, D, T, RF, Enc, Str);
RDefs ->
print_length_record(R, D, T, RF, RDefs, Enc, Str)
end;
print_length(Tuple, D, T, RF, Enc, Str) when is_tuple(Tuple) ->
printTerm(Tuple, D, T, RF, Enc, Str) when is_tuple(Tuple) ->
print_length_tuple(Tuple, D, T, RF, Enc, Str);
print_length(Map, D, T, RF, Enc, Str) when is_map(Map) ->
printTerm(Map, D, T, RF, Enc, Str) when is_map(Map) ->
print_length_map(Map, D, T, RF, Enc, Str);
print_length(<<>>, _D, _T, _RF, _Enc, _Str) ->
{"<<>>", 4, 0, no_more};
print_length(<<_/bitstring>> = Bin, 1, _T, RF, Enc, Str) ->
More = fun(T1, Dd) -> ?FUNCTION_NAME(Bin, 1 + Dd, T1, RF, Enc, Str) end,
{"<<...>>", 7, 3, More};
print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
printTerm(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
D1 = D - 1,
case
Str andalso (bit_size(Bin) rem 8) =:= 0 andalso printable_bin0(Bin, D1, tsub(T, 6), Enc) of
@ -567,11 +579,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
end,
{{bin, S}, iolist_size(S), 3, More}
end
end;
print_length(Term, _D, _T, _RF, _Enc, _Str) ->
S = io_lib:write(Term),
%% S can contain unicode, so iolist_size(S) cannot be used here
{S, io_lib:chars_length(S), 0, no_more}.
end.
print_length_map(Map, 1, _T, RF, Enc, Str) ->
More = fun(T1, Dd) -> ?FUNCTION_NAME(Map, 1 + Dd, T1, RF, Enc, Str) end,
@ -597,9 +605,9 @@ print_length_map_pairs({K, V, Iter}, D, D0, T, RF, Enc, Str) ->
print_length_map_pairs(Next, D - 1, D0, tsub(T, Len1 + 1), RF, Enc, Str)].
print_length_map_pair(K, V, D, T, RF, Enc, Str) ->
{_, KL, KD, _} = P1 = print_length(K, D, T, RF, Enc, Str),
{_, KL, KD, _} = P1 = printTerm(K, D, T, RF, Enc, Str),
KL1 = KL + 4,
{_, VL, VD, _} = P2 = print_length(V, D, tsub(T, KL1), RF, Enc, Str),
{_, VL, VD, _} = P2 = printTerm(V, D, tsub(T, KL1), RF, Enc, Str),
{{map_pair, P1, P2}, KL1 + VL, KD + VD, no_more}.
print_length_tuple(Tuple, 1, _T, RF, Enc, Str) ->
@ -620,7 +628,7 @@ print_length_tuple1(Tuple, I, D, T, RF, Enc, Str) when D =:= 1; T =:= 0 ->
print_length_tuple1(Tuple, I, D, T, RF, Enc, Str) ->
E = element(I, Tuple),
T1 = tsub(T, 1),
{_, Len1, _, _} = Elem1 = print_length(E, D - 1, T1, RF, Enc, Str),
{_, Len1, _, _} = Elem1 = printTerm(E, D - 1, T1, RF, Enc, Str),
T2 = tsub(T1, Len1),
[Elem1 | print_length_tuple1(Tuple, I + 1, D - 1, T2, RF, Enc, Str)].
@ -659,7 +667,7 @@ print_length_field(Def, D, T, E, RF, Enc, Str) ->
Name = write_atom(Def, Enc),
NameL = io_lib:chars_length(Name) + 3,
{_, Len, Dots, _} =
Field = print_length(E, D, tsub(T, NameL), RF, Enc, Str),
Field = printTerm(E, D, tsub(T, NameL), RF, Enc, Str),
{{field, Name, NameL, Field}, NameL + Len, Dots, no_more}.
print_length_list(List, D, T, RF, Enc, Str) ->
@ -673,10 +681,10 @@ print_length_list1(Term, D, T, RF, Enc, Str) when D =:= 1; T =:= 0 ->
More = fun(T1, Dd) -> ?FUNCTION_NAME(Term, D + Dd, T1, RF, Enc, Str) end,
{dots, 3, 3, More};
print_length_list1([E | Es], D, T, RF, Enc, Str) ->
{_, Len1, _, _} = Elem1 = print_length(E, D - 1, tsub(T, 1), RF, Enc, Str),
{_, Len1, _, _} = Elem1 = printTerm(E, D - 1, tsub(T, 1), RF, Enc, Str),
[Elem1 | print_length_list1(Es, D - 1, tsub(T, Len1 + 1), RF, Enc, Str)];
print_length_list1(E, D, T, RF, Enc, Str) ->
print_length(E, D - 1, T, RF, Enc, Str).
printTerm(E, D - 1, T, RF, Enc, Str).
list_length([], Acc, DotsAcc) ->
{Acc, DotsAcc};
@ -696,11 +704,11 @@ list_length_tail({_, Len, Dots, _}, Acc, DotsAcc) ->
-define(CHARS, 4).
%% only flat lists are "printable"
printable_list(_L, 1, _T, _Enc) ->
visualList(_L, 1, _T, _Enc) ->
false;
printable_list(L, _D, T, latin1) when T < 0 ->
visualList(L, _D, T, latin1) when T < 0 ->
io_lib:printable_latin1_list(L);
printable_list(L, _D, T, latin1) when T >= 0 ->
visualList(L, _D, T, latin1) when T >= 0 ->
N = tsub(T, 2),
case printable_latin1_list(L, N) of
all ->
@ -711,7 +719,7 @@ printable_list(L, _D, T, latin1) when T >= 0 ->
_NC ->
false
end;
printable_list(L, _D, T, _Unicode) when T >= 0 ->
visualList(L, _D, T, _Unicode) when T >= 0 ->
N = tsub(T, 2),
%% Be careful not to traverse more of L than necessary.
try string:slice(L, 0, N) of
@ -732,7 +740,7 @@ printable_list(L, _D, T, _Unicode) when T >= 0 ->
end
catch _:_ -> false
end;
printable_list(L, _D, T, _Uni) when T < 0 ->
visualList(L, _D, T, _Uni) when T < 0 ->
io_lib:printable_list(L).
is_flat(_L, 0) ->

+ 283
- 283
src/test/recon-2.5.1/recon.erl Wyświetl plik

@ -79,35 +79,35 @@
%%% @end
-module(recon).
-export([info/1, info/2, info/3, info/4,
proc_count/2, proc_window/3,
bin_leak/1,
node_stats_print/2, node_stats_list/2, node_stats/4,
scheduler_usage/1]).
proc_count/2, proc_window/3,
bin_leak/1,
node_stats_print/2, node_stats_list/2, node_stats/4,
scheduler_usage/1]).
-export([get_state/1, get_state/2]).
-export([remote_load/1, remote_load/2,
source/1]).
source/1]).
-export([tcp/0, udp/0, sctp/0, files/0, port_types/0,
inet_count/2, inet_window/3,
port_info/1, port_info/2]).
inet_count/2, inet_window/3,
port_info/1, port_info/2]).
-export([rpc/1, rpc/2, rpc/3,
named_rpc/1, named_rpc/2, named_rpc/3]).
named_rpc/1, named_rpc/2, named_rpc/3]).
%%%%%%%%%%%%%
%%% TYPES %%%
%%%%%%%%%%%%%
-type proc_attrs() :: {pid(),
Attr::_,
[Name:: atom()
|{current_function, mfa()}
|{initial_call, mfa()}, ...]}.
Attr :: _,
[Name :: atom()
|{current_function, mfa()}
|{initial_call, mfa()}, ...]}.
-type inet_attrs() :: {port(),
Attr::_,
[{atom(), term()}]}.
Attr :: _,
[{atom(), term()}]}.
-type pid_term() :: pid() | atom() | string()
| {global, term()} | {via, module(), term()}
| {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
| {global, term()} | {via, module(), term()}
| {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
-type info_type() :: meta | signals | location | memory_used | work.
@ -115,11 +115,11 @@
-type info_signals_key() :: links | monitors | monitored_by | trap_exit.
-type info_location_key() :: initial_call | current_stacktrace.
-type info_memory_key() :: memory | message_queue_len | heap_size
| total_heap_size | garbage_collection.
| total_heap_size | garbage_collection.
-type info_work_key() :: reductions.
-type info_key() :: info_meta_key() | info_signals_key() | info_location_key()
| info_memory_key() | info_work_key().
| info_memory_key() | info_work_key().
-type port_term() :: port() | string() | atom() | pos_integer().
@ -132,16 +132,16 @@
-type port_info_specific_key() :: atom().
-type port_info_key() :: port_info_meta_key() | port_info_signals_key()
| port_info_io_key() | port_info_memory_key()
| port_info_specific_key().
| port_info_io_key() | port_info_memory_key()
| port_info_specific_key().
-export_type([proc_attrs/0, inet_attrs/0, pid_term/0, port_term/0]).
-export_type([info_type/0, info_key/0,
info_meta_key/0, info_signals_key/0, info_location_key/0,
info_memory_key/0, info_work_key/0]).
info_meta_key/0, info_signals_key/0, info_location_key/0,
info_memory_key/0, info_work_key/0]).
-export_type([port_info_type/0, port_info_key/0,
port_info_meta_key/0, port_info_signals_key/0, port_info_io_key/0,
port_info_memory_key/0, port_info_specific_key/0]).
port_info_meta_key/0, port_info_signals_key/0, port_info_io_key/0,
port_info_memory_key/0, port_info_specific_key/0]).
%%%%%%%%%%%%%%%%%%
%%% PUBLIC API %%%
@ -151,16 +151,16 @@
%% @doc Equivalent to `info(<A.B.C>)' where `A', `B', and `C' are integers part
%% of a pid
-spec info(N,N,N) -> [{info_type(), [{info_key(),term()}]},...] when
N :: non_neg_integer().
info(A,B,C) -> info(recon_lib:triple_to_pid(A,B,C)).
-spec info(N, N, N) -> [{info_type(), [{info_key(), term()}]}, ...] when
N :: non_neg_integer().
info(A, B, C) -> info(recon_lib:triple_to_pid(A, B, C)).
%% @doc Equivalent to `info(<A.B.C>, Key)' where `A', `B', and `C' are integers part
%% of a pid
-spec info(N,N,N, Key) -> term() when
N :: non_neg_integer(),
Key :: info_type() | [atom()] | atom().
info(A,B,C, Key) -> info(recon_lib:triple_to_pid(A,B,C), Key).
-spec info(N, N, N, Key) -> term() when
N :: non_neg_integer(),
Key :: info_type() | [atom()] | atom().
info(A, B, C, Key) -> info(recon_lib:triple_to_pid(A, B, C), Key).
%% @doc Allows to be similar to `erlang:process_info/1', but excludes fields
@ -174,11 +174,11 @@ info(A,B,C, Key) -> info(recon_lib:triple_to_pid(A,B,C), Key).
%% another registry supported in the `{via, Module, Name}' syntax (must have a
%% `Module:whereis_name/1' function). Pids can also be passed in as a string
%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used.
-spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]},...] when
Value :: term().
-spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]}, ...] when
Value :: term().
info(PidTerm) ->
Pid = recon_lib:term_to_pid(PidTerm),
[info(Pid, Type) || Type <- [meta, signals, location, memory_used, work]].
Pid = recon_lib:term_to_pid(PidTerm),
[info(Pid, Type) || Type <- [meta, signals, location, memory_used, work]].
%% @doc Allows to be similar to `erlang:process_info/2', but allows to
%% sort fields by safe categories and pre-selections, avoiding items such
@ -198,66 +198,66 @@ info(PidTerm) ->
%% A fake attribute `binary_memory' is also available to return the
%% amount of memory used by refc binaries for a process.
-spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}]}
; (pid_term(), [atom()]) -> [{atom(), term()}]
; (pid_term(), atom()) -> {atom(), term()}.
; (pid_term(), [atom()]) -> [{atom(), term()}]
; (pid_term(), atom()) -> {atom(), term()}.
info(PidTerm, meta) ->
info_type(PidTerm, meta, [registered_name, dictionary, group_leader,
status]);
info_type(PidTerm, meta, [registered_name, dictionary, group_leader,
status]);
info(PidTerm, signals) ->
info_type(PidTerm, signals, [links, monitors, monitored_by, trap_exit]);
info_type(PidTerm, signals, [links, monitors, monitored_by, trap_exit]);
info(PidTerm, location) ->
info_type(PidTerm, location, [initial_call, current_stacktrace]);
info_type(PidTerm, location, [initial_call, current_stacktrace]);
info(PidTerm, memory_used) ->
info_type(PidTerm, memory_used, [memory, message_queue_len, heap_size,
total_heap_size, garbage_collection]);
info_type(PidTerm, memory_used, [memory, message_queue_len, heap_size,
total_heap_size, garbage_collection]);
info(PidTerm, work) ->
info_type(PidTerm, work, [reductions]);
info_type(PidTerm, work, [reductions]);
info(PidTerm, Keys) ->
proc_info(recon_lib:term_to_pid(PidTerm), Keys).
proc_info(recon_lib:term_to_pid(PidTerm), Keys).
%% @private makes access to `info_type()' calls simpler.
-spec info_type(pid_term(), info_type(), [info_key()]) ->
{info_type(), [{info_key(), term()}]}.
{info_type(), [{info_key(), term()}]}.
info_type(PidTerm, Type, Keys) ->
Pid = recon_lib:term_to_pid(PidTerm),
{Type, proc_info(Pid, Keys)}.
Pid = recon_lib:term_to_pid(PidTerm),
{Type, proc_info(Pid, Keys)}.
%% @private wrapper around `erlang:process_info/2' that allows special
%% attribute handling for items like `binary_memory'.
proc_info(Pid, binary_memory) ->
{binary, Bins} = erlang:process_info(Pid, binary),
{binary_memory, recon_lib:binary_memory(Bins)};
{binary, Bins} = erlang:process_info(Pid, binary),
{binary_memory, recon_lib:binary_memory(Bins)};
proc_info(Pid, Term) when is_atom(Term) ->
erlang:process_info(Pid, Term);
erlang:process_info(Pid, Term);
proc_info(Pid, List) when is_list(List) ->
case lists:member(binary_memory, List) of
false ->
erlang:process_info(Pid, List);
true ->
Res = erlang:process_info(Pid, replace(binary_memory, binary, List)),
proc_fake(List, Res)
end.
case lists:member(binary_memory, List) of
false ->
erlang:process_info(Pid, List);
true ->
Res = erlang:process_info(Pid, replace(binary_memory, binary, List)),
proc_fake(List, Res)
end.
%% @private Replace keys around
replace(_, _, []) -> [];
replace(H, Val, [H|T]) -> [Val | replace(H, Val, T)];
replace(R, Val, [H|T]) -> [H | replace(R, Val, T)].
replace(H, Val, [H | T]) -> [Val | replace(H, Val, T)];
replace(R, Val, [H | T]) -> [H | replace(R, Val, T)].
proc_fake([], []) ->
[];
proc_fake([binary_memory|T1], [{binary,Bins}|T2]) ->
[{binary_memory, recon_lib:binary_memory(Bins)}
| proc_fake(T1,T2)];
proc_fake([_|T1], [H|T2]) ->
[H | proc_fake(T1,T2)].
[];
proc_fake([binary_memory | T1], [{binary, Bins} | T2]) ->
[{binary_memory, recon_lib:binary_memory(Bins)}
| proc_fake(T1, T2)];
proc_fake([_ | T1], [H | T2]) ->
[H | proc_fake(T1, T2)].
%% @doc Fetches a given attribute from all processes (except the
%% caller) and returns the biggest `Num' consumers.
-spec proc_count(AttributeName, Num) -> [proc_attrs()] when
AttributeName :: atom(),
Num :: non_neg_integer().
AttributeName :: atom(),
Num :: non_neg_integer().
proc_count(AttrName, Num) ->
recon_lib:sublist_top_n_attrs(recon_lib:proc_attrs(AttrName), Num).
recon_lib:sublist_top_n_attrs(recon_lib:proc_attrs(AttrName), Num).
%% @doc Fetches a given attribute from all processes (except the
%% caller) and returns the biggest entries, over a sliding time window.
@ -285,13 +285,13 @@ proc_count(AttrName, Num) ->
%% building a dictionary with entries to differentiate them. This can take a
%% heavy toll on memory when you have many dozens of thousands of processes.
-spec proc_window(AttributeName, Num, Milliseconds) -> [proc_attrs()] when
AttributeName :: atom(),
Num :: non_neg_integer(),
Milliseconds :: pos_integer().
AttributeName :: atom(),
Num :: non_neg_integer(),
Milliseconds :: pos_integer().
proc_window(AttrName, Num, Time) ->
Sample = fun() -> recon_lib:proc_attrs(AttrName) end,
{First,Last} = recon_lib:sample(Time, Sample),
recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num).
Sample = fun() -> recon_lib:proc_attrs(AttrName) end,
{First, Last} = recon_lib:sample(Time, Sample),
recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num).
%% @doc Refc binaries can be leaking when barely-busy processes route them
%% around and do little else, or when extremely busy processes reach a stable
@ -308,24 +308,24 @@ proc_window(AttrName, Num, Time) ->
%% for more details on refc binaries
-spec bin_leak(pos_integer()) -> [proc_attrs()].
bin_leak(N) ->
Procs = recon_lib:sublist_top_n_attrs([
try
{ok, {_,Pre,Id}} = recon_lib:proc_attrs(binary, Pid),
erlang:garbage_collect(Pid),
{ok, {_,Post,_}} = recon_lib:proc_attrs(binary, Pid),
{Pid, length(Pre) - length(Post), Id}
catch
_:_ -> {Pid, 0, []}
end || Pid <- processes()
], N),
[{Pid, -Val, Id} ||{Pid, Val, Id} <-Procs].
Procs = recon_lib:sublist_top_n_attrs([
try
{ok, {_, Pre, Id}} = recon_lib:proc_attrs(binary, Pid),
erlang:garbage_collect(Pid),
{ok, {_, Post, _}} = recon_lib:proc_attrs(binary, Pid),
{Pid, length(Pre) - length(Post), Id}
catch
_:_ -> {Pid, 0, []}
end || Pid <- processes()
], N),
[{Pid, -Val, Id} || {Pid, Val, Id} <- Procs].
%% @doc Shorthand for `node_stats(N, Interval, fun(X,_) -> io:format("~p~n",[X]) end, nostate)'.
-spec node_stats_print(Repeat, Interval) -> term() when
Repeat :: non_neg_integer(),
Interval :: pos_integer().
Repeat :: non_neg_integer(),
Interval :: pos_integer().
node_stats_print(N, Interval) ->
node_stats(N, Interval, fun(X, _) -> io:format("~p~n", [X]) end, ok).
node_stats(N, Interval, fun(X, _) -> io:format("~p~n", [X]) end, ok).
%% @doc Because Erlang CPU usage as reported from `top' isn't the most
%% reliable value (due to schedulers doing idle spinning to avoid going
@ -346,29 +346,29 @@ node_stats_print(N, Interval) ->
%%
%% A scheduler isn't busy when doing anything else.
-spec scheduler_usage(Millisecs) -> undefined | [{SchedulerId, Usage}] when
Millisecs :: non_neg_integer(),
SchedulerId :: pos_integer(),
Usage :: number().
Millisecs :: non_neg_integer(),
SchedulerId :: pos_integer(),
Usage :: number().
scheduler_usage(Interval) when is_integer(Interval) ->
%% We start and stop the scheduler_wall_time system flag if
%% it wasn't in place already. Usually setting the flag should
%% have a CPU impact (making it higher) only when under low usage.
FormerFlag = erlang:system_flag(scheduler_wall_time, true),
First = erlang:statistics(scheduler_wall_time),
timer:sleep(Interval),
Last = erlang:statistics(scheduler_wall_time),
erlang:system_flag(scheduler_wall_time, FormerFlag),
recon_lib:scheduler_usage_diff(First, Last).
%% We start and stop the scheduler_wall_time system flag if
%% it wasn't in place already. Usually setting the flag should
%% have a CPU impact (making it higher) only when under low usage.
FormerFlag = erlang:system_flag(scheduler_wall_time, true),
First = erlang:statistics(scheduler_wall_time),
timer:sleep(Interval),
Last = erlang:statistics(scheduler_wall_time),
erlang:system_flag(scheduler_wall_time, FormerFlag),
recon_lib:scheduler_usage_diff(First, Last).
%% @doc Shorthand for `node_stats(N, Interval, fun(X,Acc) -> [X|Acc] end, [])'
%% with the results reversed to be in the right temporal order.
-spec node_stats_list(Repeat, Interval) -> [Stats] when
Repeat :: non_neg_integer(),
Interval :: pos_integer(),
Stats :: {[Absolutes::{atom(),term()}],
[Increments::{atom(),term()}]}.
Repeat :: non_neg_integer(),
Interval :: pos_integer(),
Stats :: {[Absolutes :: {atom(), term()}],
[Increments :: {atom(), term()}]}.
node_stats_list(N, Interval) ->
lists:reverse(node_stats(N, Interval, fun(X, Acc) -> [X|Acc] end, [])).
lists:reverse(node_stats(N, Interval, fun(X, Acc) -> [X | Acc] end, [])).
%% @doc Gathers statistics `N' time, waiting `Interval' milliseconds between
%% each run, and accumulates results using a folding function `FoldFun'.
@ -386,71 +386,71 @@ node_stats_list(N, Interval) ->
%% runs, words of memory that were garbage collected, and the global reductions
%% count for the node.
-spec node_stats(N, Interval, FoldFun, Acc) -> Acc when
N :: non_neg_integer(),
Interval :: pos_integer(),
FoldFun :: fun((Stats, Acc) -> Acc),
Acc :: term(),
Stats :: {[Absolutes::{atom(),term()}],
[Increments::{atom(),term()}]}.
N :: non_neg_integer(),
Interval :: pos_integer(),
FoldFun :: fun((Stats, Acc) -> Acc),
Acc :: term(),
Stats :: {[Absolutes :: {atom(), term()}],
[Increments :: {atom(), term()}]}.
node_stats(N, Interval, FoldFun, Init) ->
Logger = case whereis(error_logger) of
undefined -> logger;
_ -> error_logger
end,
%% Turn on scheduler wall time if it wasn't there already
FormerFlag = erlang:system_flag(scheduler_wall_time, true),
%% Stats is an ugly fun, but it does its thing.
Stats = fun({{OldIn,OldOut},{OldGCs,OldWords,_}, SchedWall}) ->
%% Absolutes
ProcC = erlang:system_info(process_count),
RunQ = erlang:statistics(run_queue),
LogQ = case Logger of
error_logger ->
{_,LogQLen} = process_info(whereis(error_logger),
message_queue_len),
LogQLen;
_ ->
undefined
end,
%% Mem (Absolutes)
Mem = erlang:memory(),
Tot = proplists:get_value(total, Mem),
ProcM = proplists:get_value(processes_used,Mem),
Atom = proplists:get_value(atom_used,Mem),
Bin = proplists:get_value(binary, Mem),
Ets = proplists:get_value(ets, Mem),
%% Incremental
{{input,In},{output,Out}} = erlang:statistics(io),
GC={GCs,Words,_} = erlang:statistics(garbage_collection),
BytesIn = In-OldIn,
BytesOut = Out-OldOut,
GCCount = GCs-OldGCs,
GCWords = Words-OldWords,
{_, Reds} = erlang:statistics(reductions),
SchedWallNew = erlang:statistics(scheduler_wall_time),
SchedUsage = recon_lib:scheduler_usage_diff(SchedWall, SchedWallNew),
%% Stats Results
{{[{process_count,ProcC}, {run_queue,RunQ}] ++
[{error_logger_queue_len,LogQ} || LogQ =/= undefined] ++
[{memory_total,Tot},
{memory_procs,ProcM}, {memory_atoms,Atom},
{memory_bin,Bin}, {memory_ets,Ets}],
[{bytes_in,BytesIn}, {bytes_out,BytesOut},
{gc_count,GCCount}, {gc_words_reclaimed,GCWords},
{reductions,Reds}, {scheduler_usage, SchedUsage}]},
Logger = case whereis(error_logger) of
undefined -> logger;
_ -> error_logger
end,
%% Turn on scheduler wall time if it wasn't there already
FormerFlag = erlang:system_flag(scheduler_wall_time, true),
%% Stats is an ugly fun, but it does its thing.
Stats = fun({{OldIn, OldOut}, {OldGCs, OldWords, _}, SchedWall}) ->
%% Absolutes
ProcC = erlang:system_info(process_count),
RunQ = erlang:statistics(run_queue),
LogQ = case Logger of
error_logger ->
{_, LogQLen} = process_info(whereis(error_logger),
message_queue_len),
LogQLen;
_ ->
undefined
end,
%% Mem (Absolutes)
Mem = erlang:memory(),
Tot = proplists:get_value(total, Mem),
ProcM = proplists:get_value(processes_used, Mem),
Atom = proplists:get_value(atom_used, Mem),
Bin = proplists:get_value(binary, Mem),
Ets = proplists:get_value(ets, Mem),
%% Incremental
{{input, In}, {output, Out}} = erlang:statistics(io),
GC = {GCs, Words, _} = erlang:statistics(garbage_collection),
BytesIn = In - OldIn,
BytesOut = Out - OldOut,
GCCount = GCs - OldGCs,
GCWords = Words - OldWords,
{_, Reds} = erlang:statistics(reductions),
SchedWallNew = erlang:statistics(scheduler_wall_time),
SchedUsage = recon_lib:scheduler_usage_diff(SchedWall, SchedWallNew),
%% Stats Results
{{[{process_count, ProcC}, {run_queue, RunQ}] ++
[{error_logger_queue_len, LogQ} || LogQ =/= undefined] ++
[{memory_total, Tot},
{memory_procs, ProcM}, {memory_atoms, Atom},
{memory_bin, Bin}, {memory_ets, Ets}],
[{bytes_in, BytesIn}, {bytes_out, BytesOut},
{gc_count, GCCount}, {gc_words_reclaimed, GCWords},
{reductions, Reds}, {scheduler_usage, SchedUsage}]},
%% New State
{{In,Out}, GC, SchedWallNew}}
end,
{{input,In},{output,Out}} = erlang:statistics(io),
Gc = erlang:statistics(garbage_collection),
SchedWall = erlang:statistics(scheduler_wall_time),
Result = recon_lib:time_fold(
N, Interval, Stats,
{{In,Out}, Gc, SchedWall},
FoldFun, Init),
%% Set scheduler wall time back to what it was
erlang:system_flag(scheduler_wall_time, FormerFlag),
Result.
{{In, Out}, GC, SchedWallNew}}
end,
{{input, In}, {output, Out}} = erlang:statistics(io),
Gc = erlang:statistics(garbage_collection),
SchedWall = erlang:statistics(scheduler_wall_time),
Result = recon_lib:time_fold(
N, Interval, Stats,
{{In, Out}, Gc, SchedWall},
FoldFun, Init),
%% Set scheduler wall time back to what it was
erlang:system_flag(scheduler_wall_time, FormerFlag),
Result.
%%% OTP & Manipulations %%%
@ -462,22 +462,22 @@ get_state(PidTerm) -> get_state(PidTerm, 5000).
%% @doc Fetch the internal state of an OTP process.
%% Calls `sys:get_state/2' directly in R16B01+, and fetches
%% it dynamically on older versions of OTP.
-spec get_state(pid_term(), Ms:: non_neg_integer() | 'infinity') -> term().
-spec get_state(pid_term(), Ms :: non_neg_integer() | 'infinity') -> term().
get_state(PidTerm, Timeout) ->
Proc = recon_lib:term_to_pid(PidTerm),
try
sys:get_state(Proc, Timeout)
catch
error:undef ->
case sys:get_status(Proc, Timeout) of
{status,_Pid,{module,gen_server},Data} ->
{data, Props} = lists:last(lists:nth(5, Data)),
proplists:get_value("State", Props);
{status,_Pod,{module,gen_fsm},Data} ->
{data, Props} = lists:last(lists:nth(5, Data)),
proplists:get_value("StateData", Props)
end
end.
Proc = recon_lib:term_to_pid(PidTerm),
try
sys:get_state(Proc, Timeout)
catch
error:undef ->
case sys:get_status(Proc, Timeout) of
{status, _Pid, {module, gen_server}, Data} ->
{data, Props} = lists:last(lists:nth(5, Data)),
proplists:get_value("State", Props);
{status, _Pod, {module, gen_fsm}, Data} ->
{data, Props} = lists:last(lists:nth(5, Data)),
proplists:get_value("StateData", Props)
end
end.
%%% Code & Stuff %%%
@ -488,14 +488,14 @@ remote_load(Mod) -> remote_load(nodes(), Mod).
%% @doc Loads one or more modules remotely, in a diskless manner. Allows to
%% share code loaded locally with a remote node that doesn't have it
-spec remote_load(Nodes, module()) -> term() when
Nodes :: [node(),...] | node().
remote_load(Nodes=[_|_], Mod) when is_atom(Mod) ->
{Mod, Bin, File} = code:get_object_code(Mod),
rpc:multicall(Nodes, code, load_binary, [Mod, File, Bin]);
remote_load(Nodes=[_|_], Modules) when is_list(Modules) ->
[remote_load(Nodes, Mod) || Mod <- Modules];
Nodes :: [node(), ...] | node().
remote_load(Nodes = [_ | _], Mod) when is_atom(Mod) ->
{Mod, Bin, File} = code:get_object_code(Mod),
rpc:multicall(Nodes, code, load_binary, [Mod, File, Bin]);
remote_load(Nodes = [_ | _], Modules) when is_list(Modules) ->
[remote_load(Nodes, Mod) || Mod <- Modules];
remote_load(Node, Mod) ->
remote_load([Node], Mod).
remote_load([Node], Mod).
%% @doc Obtain the source code of a module compiled with `debug_info'.
%% The returned list sadly does not allow to format the types and typed
@ -505,9 +505,9 @@ remote_load(Node, Mod) ->
%% @todo Figure out a way to pretty-print typespecs and records.
-spec source(module()) -> iolist().
source(Module) ->
Path = code:which(Module),
{ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Path, [abstract_code]),
erl_prettypr:format(erl_syntax:form_list(AC)).
Path = code:which(Module),
{ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Path, [abstract_code]),
erl_prettypr:format(erl_syntax:form_list(AC)).
%%% Ports Info %%%
@ -532,13 +532,13 @@ files() -> recon_lib:port_list(name, "efile").
%% @doc Shows a list of all different ports on the node with their respective
%% types.
-spec port_types() -> [{Type:: string(), Count:: pos_integer()}].
-spec port_types() -> [{Type :: string(), Count :: pos_integer()}].
port_types() ->
lists:usort(
%% sorts by biggest count, smallest type
fun({KA,VA}, {KB,VB}) -> {VA,KB} > {VB,KA} end,
recon_lib:count([Name || {_, Name} <- recon_lib:port_list(name)])
).
lists:usort(
%% sorts by biggest count, smallest type
fun({KA, VA}, {KB, VB}) -> {VA, KB} > {VB, KA} end,
recon_lib:count([Name || {_, Name} <- recon_lib:port_list(name)])
).
%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP)
%% and returns the biggest `Num' consumers.
@ -549,11 +549,11 @@ port_types() ->
%% respectively). Individual absolute values for each metric will be returned
%% in the 3rd position of the resulting tuple.
-spec inet_count(AttributeName, Num) -> [inet_attrs()] when
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct'
| 'cnt' | 'oct',
Num :: non_neg_integer().
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct'
| 'cnt' | 'oct',
Num :: non_neg_integer().
inet_count(Attr, Num) ->
recon_lib:sublist_top_n_attrs(recon_lib:inet_attrs(Attr), Num).
recon_lib:sublist_top_n_attrs(recon_lib:inet_attrs(Attr), Num).
%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP)
%% and returns the biggest entries, over a sliding time window.
@ -568,14 +568,14 @@ inet_count(Attr, Num) ->
%% respectively). Individual absolute values for each metric will be returned
%% in the 3rd position of the resulting tuple.
-spec inet_window(AttributeName, Num, Milliseconds) -> [inet_attrs()] when
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct'
| 'cnt' | 'oct',
Num :: non_neg_integer(),
Milliseconds :: pos_integer().
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct'
| 'cnt' | 'oct',
Num :: non_neg_integer(),
Milliseconds :: pos_integer().
inet_window(Attr, Num, Time) when is_atom(Attr) ->
Sample = fun() -> recon_lib:inet_attrs(Attr) end,
{First,Last} = recon_lib:sample(Time, Sample),
recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num).
Sample = fun() -> recon_lib:inet_attrs(Attr) end,
{First, Last} = recon_lib:sample(Time, Sample),
recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num).
%% @doc Allows to be similar to `erlang:port_info/1', but allows
%% more flexible port usage: usual ports, ports that were registered
@ -591,11 +591,11 @@ inet_window(Attr, Num, Time) when is_atom(Attr) ->
%% The information-specific and the basic port info are sorted and
%% categorized in broader categories ({@link port_info_type()}).
-spec port_info(port_term()) -> [{port_info_type(),
[{port_info_key(), term()}]},...].
[{port_info_key(), term()}]}, ...].
port_info(PortTerm) ->
Port = recon_lib:term_to_port(PortTerm),
[port_info(Port, Type) || Type <- [meta, signals, io, memory_used,
specific]].
Port = recon_lib:term_to_port(PortTerm),
[port_info(Port, Type) || Type <- [meta, signals, io, memory_used,
specific]].
%% @doc Allows to be similar to `erlang:port_info/2', but allows
%% more flexible port usage: usual ports, ports that were registered
@ -608,106 +608,106 @@ port_info(PortTerm) ->
%% doesn't show it in the generated documentation, individual items
%% accepted by `erlang:port_info/2' are accepted, and lists of them too.
-spec port_info(port_term(), port_info_type()) -> {port_info_type(),
[{port_info_key(), _}]}
; (port_term(), [atom()]) -> [{atom(), term()}]
; (port_term(), atom()) -> {atom(), term()}.
[{port_info_key(), _}]}
; (port_term(), [atom()]) -> [{atom(), term()}]
; (port_term(), atom()) -> {atom(), term()}.
port_info(PortTerm, meta) ->
{meta, List} = port_info_type(PortTerm, meta, [id, name, os_pid]),
case port_info(PortTerm, registered_name) of
[] -> {meta, List};
Name -> {meta, [Name | List]}
end;
{meta, List} = port_info_type(PortTerm, meta, [id, name, os_pid]),
case port_info(PortTerm, registered_name) of
[] -> {meta, List};
Name -> {meta, [Name | List]}
end;
port_info(PortTerm, signals) ->
port_info_type(PortTerm, signals, [connected, links, monitors]);
port_info_type(PortTerm, signals, [connected, links, monitors]);
port_info(PortTerm, io) ->
port_info_type(PortTerm, io, [input, output]);
port_info_type(PortTerm, io, [input, output]);
port_info(PortTerm, memory_used) ->
port_info_type(PortTerm, memory_used, [memory, queue_size]);
port_info_type(PortTerm, memory_used, [memory, queue_size]);
port_info(PortTerm, specific) ->
Port = recon_lib:term_to_port(PortTerm),
Props = case erlang:port_info(Port, name) of
{_,Type} when Type =:= "udp_inet";
Type =:= "tcp_inet";
Type =:= "sctp_inet" ->
case inet:getstat(Port) of
{ok, Stats} -> [{statistics, Stats}];
_ -> []
end ++
case inet:peername(Port) of
{ok, Peer} -> [{peername, Peer}];
{error, _} -> []
end ++
case inet:sockname(Port) of
{ok, Local} -> [{sockname, Local}];
{error, _} -> []
end ++
case inet:getopts(Port, [active, broadcast, buffer, delay_send,
dontroute, exit_on_close, header,
high_watermark, ipv6_v6only, keepalive,
linger, low_watermark, mode, nodelay,
packet, packet_size, priority,
read_packets, recbuf, reuseaddr,
send_timeout, sndbuf]) of
{ok, Opts} -> [{options, Opts}];
{error, _} -> []
end;
{_,"efile"} ->
%% would be nice to support file-specific info, but things
%% are too vague with the file_server and how it works in
%% order to make this work efficiently
[];
_ ->
[]
end,
{type, Props};
Port = recon_lib:term_to_port(PortTerm),
Props = case erlang:port_info(Port, name) of
{_, Type} when Type =:= "udp_inet";
Type =:= "tcp_inet";
Type =:= "sctp_inet" ->
case inet:getstat(Port) of
{ok, Stats} -> [{statistics, Stats}];
_ -> []
end ++
case inet:peername(Port) of
{ok, Peer} -> [{peername, Peer}];
{error, _} -> []
end ++
case inet:sockname(Port) of
{ok, Local} -> [{sockname, Local}];
{error, _} -> []
end ++
case inet:getopts(Port, [active, broadcast, buffer, delay_send,
dontroute, exit_on_close, header,
high_watermark, ipv6_v6only, keepalive,
linger, low_watermark, mode, nodelay,
packet, packet_size, priority,
read_packets, recbuf, reuseaddr,
send_timeout, sndbuf]) of
{ok, Opts} -> [{options, Opts}];
{error, _} -> []
end;
{_, "efile"} ->
%% would be nice to support file-specific info, but things
%% are too vague with the file_server and how it works in
%% order to make this work efficiently
[];
_ ->
[]
end,
{type, Props};
port_info(PortTerm, Keys) when is_list(Keys) ->
Port = recon_lib:term_to_port(PortTerm),
[erlang:port_info(Port,Key) || Key <- Keys];
Port = recon_lib:term_to_port(PortTerm),
[erlang:port_info(Port, Key) || Key <- Keys];
port_info(PortTerm, Key) when is_atom(Key) ->
erlang:port_info(recon_lib:term_to_port(PortTerm), Key).
erlang:port_info(recon_lib:term_to_port(PortTerm), Key).
%% @private makes access to `port_info_type()' calls simpler.
%-spec port_info_type(pid_term(), port_info_type(), [port_info_key()]) ->
% {port_info_type(), [{port_info_key(), term()}]}.
port_info_type(PortTerm, Type, Keys) ->
Port = recon_lib:term_to_port(PortTerm),
{Type, [erlang:port_info(Port,Key) || Key <- Keys]}.
Port = recon_lib:term_to_port(PortTerm),
{Type, [erlang:port_info(Port, Key) || Key <- Keys]}.
%%% RPC Utils %%%
%% @doc Shorthand for `rpc([node()|nodes()], Fun)'.
-spec rpc(fun(() -> term())) -> {[Success::_],[Fail::_]}.
-spec rpc(fun(() -> term())) -> {[Success :: _], [Fail :: _]}.
rpc(Fun) ->
rpc([node()|nodes()], Fun).
rpc([node() | nodes()], Fun).
%% @doc Shorthand for `rpc(Nodes, Fun, infinity)'.
-spec rpc(node()|[node(),...], fun(() -> term())) -> {[Success::_],[Fail::_]}.
-spec rpc(node()|[node(), ...], fun(() -> term())) -> {[Success :: _], [Fail :: _]}.
rpc(Nodes, Fun) ->
rpc(Nodes, Fun, infinity).
rpc(Nodes, Fun, infinity).
%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes.
-spec rpc(node()|[node(),...], fun(() -> term()), timeout()) -> {[Success::_],[Fail::_]}.
rpc(Nodes=[_|_], Fun, Timeout) when is_function(Fun,0) ->
rpc:multicall(Nodes, erlang, apply, [Fun,[]], Timeout);
-spec rpc(node()|[node(), ...], fun(() -> term()), timeout()) -> {[Success :: _], [Fail :: _]}.
rpc(Nodes = [_ | _], Fun, Timeout) when is_function(Fun, 0) ->
rpc:multicall(Nodes, erlang, apply, [Fun, []], Timeout);
rpc(Node, Fun, Timeout) when is_atom(Node) ->
rpc([Node], Fun, Timeout).
rpc([Node], Fun, Timeout).
%% @doc Shorthand for `named_rpc([node()|nodes()], Fun)'.
-spec named_rpc(fun(() -> term())) -> {[Success::_],[Fail::_]}.
-spec named_rpc(fun(() -> term())) -> {[Success :: _], [Fail :: _]}.
named_rpc(Fun) ->
named_rpc([node()|nodes()], Fun).
named_rpc([node() | nodes()], Fun).
%% @doc Shorthand for `named_rpc(Nodes, Fun, infinity)'.
-spec named_rpc(node()|[node(),...], fun(() -> term())) -> {[Success::_],[Fail::_]}.
-spec named_rpc(node()|[node(), ...], fun(() -> term())) -> {[Success :: _], [Fail :: _]}.
named_rpc(Nodes, Fun) ->
named_rpc(Nodes, Fun, infinity).
named_rpc(Nodes, Fun, infinity).
%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes, and returns the
%% name of the node that computed a given result along with it, in a tuple.
-spec named_rpc(node()|[node(),...], fun(() -> term()), timeout()) -> {[Success::_],[Fail::_]}.
named_rpc(Nodes=[_|_], Fun, Timeout) when is_function(Fun,0) ->
rpc:multicall(Nodes, erlang, apply, [fun() -> {node(),Fun()} end,[]], Timeout);
-spec named_rpc(node()|[node(), ...], fun(() -> term()), timeout()) -> {[Success :: _], [Fail :: _]}.
named_rpc(Nodes = [_ | _], Fun, Timeout) when is_function(Fun, 0) ->
rpc:multicall(Nodes, erlang, apply, [fun() -> {node(), Fun()} end, []], Timeout);
named_rpc(Node, Fun, Timeout) when is_atom(Node) ->
named_rpc([Node], Fun, Timeout).
named_rpc([Node], Fun, Timeout).

+ 354
- 354
src/test/recon-2.5.1/recon_alloc.erl Wyświetl plik

@ -115,19 +115,19 @@
%%%
-module(recon_alloc).
-define(UTIL_ALLOCATORS, [temp_alloc,
eheap_alloc,
binary_alloc,
ets_alloc,
driver_alloc,
sl_alloc,
ll_alloc,
fix_alloc,
std_alloc
]).
eheap_alloc,
binary_alloc,
ets_alloc,
driver_alloc,
sl_alloc,
ll_alloc,
fix_alloc,
std_alloc
]).
-type allocator() :: temp_alloc | eheap_alloc | binary_alloc | ets_alloc
| driver_alloc | sl_alloc | ll_alloc | fix_alloc
| std_alloc.
| driver_alloc | sl_alloc | ll_alloc | fix_alloc
| std_alloc.
-type instance() :: non_neg_integer().
-type allocdata(T) :: {{allocator(), instance()}, T}.
-type allocdata_types(T) :: {{allocator(), [instance()]}, T}.
@ -137,18 +137,18 @@
-define(MAX_POS, 4). % pos in sizes tuples for max value
-export([memory/1, memory/2, fragmentation/1, cache_hit_rates/0,
average_block_sizes/1, sbcs_to_mbcs/1, allocators/0,
allocators/1]).
average_block_sizes/1, sbcs_to_mbcs/1, allocators/0,
allocators/1]).
%% Snapshot handling
-type memory() :: [{atom(),atom()}].
-type snapshot() :: {memory(),[allocdata(term())]}.
-type memory() :: [{atom(), atom()}].
-type snapshot() :: {memory(), [allocdata(term())]}.
-export_type([memory/0, snapshot/0]).
-export([snapshot/0, snapshot_clear/0,
snapshot_print/0, snapshot_get/0,
snapshot_save/1, snapshot_load/1]).
-export([snapshot/0, snapshot_clear/0,
snapshot_print/0, snapshot_get/0,
snapshot_save/1, snapshot_load/1]).
%% Unit handling
-export([set_unit/1]).
@ -160,9 +160,9 @@
%% @doc Equivalent to `memory(Key, current)'.
-spec memory(used | allocated | unused) -> pos_integer()
; (usage) -> number()
; (allocated_types | allocated_instances) ->
[{allocator(), pos_integer()}].
; (usage) -> number()
; (allocated_types | allocated_instances) ->
[{allocator(), pos_integer()}].
memory(Key) -> memory(Key, current).
%% @doc reports one of multiple possible memory values for the entire
@ -201,31 +201,31 @@ memory(Key) -> memory(Key, current).
%% memory, in which case exploring which specific allocator is at fault
%% is recommended (see {@link fragmentation/1})
-spec memory(used | allocated | unused, current | max) -> pos_integer()
; (usage, current | max) -> number()
; (allocated_types|allocated_instances, current | max) ->
[{allocator(),pos_integer()}].
memory(used,Keyword) ->
lists:sum(lists:map(fun({_,Prop}) ->
container_size(Prop,Keyword,blocks_size)
end,util_alloc()));
memory(allocated,Keyword) ->
lists:sum(lists:map(fun({_,Prop}) ->
container_size(Prop,Keyword,carriers_size)
end,util_alloc()));
memory(allocated_types,Keyword) ->
lists:foldl(fun({{Alloc,_N},Props},Acc) ->
CZ = container_size(Props,Keyword,carriers_size),
orddict:update_counter(Alloc,CZ,Acc)
end,orddict:new(),util_alloc());
memory(allocated_instances,Keyword) ->
lists:foldl(fun({{_Alloc,N},Props},Acc) ->
CZ = container_size(Props,Keyword,carriers_size),
orddict:update_counter(N,CZ,Acc)
end,orddict:new(),util_alloc());
memory(unused,Keyword) ->
memory(allocated,Keyword) - memory(used,Keyword);
memory(usage,Keyword) ->
memory(used,Keyword) / memory(allocated,Keyword).
; (usage, current | max) -> number()
; (allocated_types|allocated_instances, current | max) ->
[{allocator(), pos_integer()}].
memory(used, Keyword) ->
lists:sum(lists:map(fun({_, Prop}) ->
container_size(Prop, Keyword, blocks_size)
end, util_alloc()));
memory(allocated, Keyword) ->
lists:sum(lists:map(fun({_, Prop}) ->
container_size(Prop, Keyword, carriers_size)
end, util_alloc()));
memory(allocated_types, Keyword) ->
lists:foldl(fun({{Alloc, _N}, Props}, Acc) ->
CZ = container_size(Props, Keyword, carriers_size),
orddict:update_counter(Alloc, CZ, Acc)
end, orddict:new(), util_alloc());
memory(allocated_instances, Keyword) ->
lists:foldl(fun({{_Alloc, N}, Props}, Acc) ->
CZ = container_size(Props, Keyword, carriers_size),
orddict:update_counter(N, CZ, Acc)
end, orddict:new(), util_alloc());
memory(unused, Keyword) ->
memory(allocated, Keyword) - memory(used, Keyword);
memory(usage, Keyword) ->
memory(used, Keyword) / memory(allocated, Keyword).
%% @doc Compares the block sizes to the carrier sizes, both for
%% single block (`sbcs') and multiblock (`mbcs') carriers.
@ -244,16 +244,16 @@ memory(usage,Keyword) ->
%% carriers.
-spec fragmentation(current | max) -> [allocdata([{atom(), term()}])].
fragmentation(Keyword) ->
WeighedData = [begin
BlockSbcs = container_value(Props, Keyword, sbcs, blocks_size),
CarSbcs = container_value(Props, Keyword, sbcs, carriers_size),
BlockMbcs = container_value(Props, Keyword, mbcs, blocks_size),
CarMbcs = container_value(Props, Keyword, mbcs, carriers_size),
{Weight, Vals} = weighed_values({BlockSbcs,CarSbcs},
{BlockMbcs,CarMbcs}),
{Weight, {Allocator,N}, Vals}
end || {{Allocator, N}, Props} <- util_alloc()],
[{Key,Val} || {_W, Key, Val} <- lists:reverse(lists:sort(WeighedData))].
WeighedData = [begin
BlockSbcs = container_value(Props, Keyword, sbcs, blocks_size),
CarSbcs = container_value(Props, Keyword, sbcs, carriers_size),
BlockMbcs = container_value(Props, Keyword, mbcs, blocks_size),
CarMbcs = container_value(Props, Keyword, mbcs, carriers_size),
{Weight, Vals} = weighed_values({BlockSbcs, CarSbcs},
{BlockMbcs, CarMbcs}),
{Weight, {Allocator, N}, Vals}
end || {{Allocator, N}, Props} <- util_alloc()],
[{Key, Val} || {_W, Key, Val} <- lists:reverse(lists:sort(WeighedData))].
%% @doc looks at the `mseg_alloc' allocator (allocator used by all the
%% allocators in {@link allocator()}) and returns information relative to
@ -277,20 +277,20 @@ fragmentation(Keyword) ->
%%
%% The values returned by this function are sorted by a weight combining
%% the lower cache hit joined to the largest memory values allocated.
-spec cache_hit_rates() -> [{{instance,instance()}, [{Key,Val}]}] when
Key :: hit_rate | hits | calls,
Val :: term().
-spec cache_hit_rates() -> [{{instance, instance()}, [{Key, Val}]}] when
Key :: hit_rate | hits | calls,
Val :: term().
cache_hit_rates() ->
WeighedData = [begin
Mem = proplists:get_value(memkind, Props),
{_,Hits} = lists:keyfind(cache_hits, 1, proplists:get_value(status,Mem)),
{_,Giga,Ones} = lists:keyfind(mseg_alloc,1,proplists:get_value(calls,Mem)),
Calls = 1000000000*Giga + Ones,
HitRate = usage(Hits,Calls),
Weight = (1.00 - HitRate)*Calls,
{Weight, {instance,N}, [{hit_rate,HitRate}, {hits,Hits}, {calls,Calls}]}
end || {{_, N}, Props} <- alloc([mseg_alloc])],
[{Key,Val} || {_W,Key,Val} <- lists:reverse(lists:sort(WeighedData))].
WeighedData = [begin
Mem = proplists:get_value(memkind, Props),
{_, Hits} = lists:keyfind(cache_hits, 1, proplists:get_value(status, Mem)),
{_, Giga, Ones} = lists:keyfind(mseg_alloc, 1, proplists:get_value(calls, Mem)),
Calls = 1000000000 * Giga + Ones,
HitRate = usage(Hits, Calls),
Weight = (1.00 - HitRate) * Calls,
{Weight, {instance, N}, [{hit_rate, HitRate}, {hits, Hits}, {calls, Calls}]}
end || {{_, N}, Props} <- alloc([mseg_alloc])],
[{Key, Val} || {_W, Key, Val} <- lists:reverse(lists:sort(WeighedData))].
%% @doc Checks all allocators in {@link allocator()} and returns the average
%% block sizes being used for `mbcs' and `sbcs'. This value is interesting
@ -307,24 +307,24 @@ cache_hit_rates() ->
%%
%% Do note that values for `lmbcs' and `smbcs' are going to be rounded up
%% to the next power of two when configuring them.
-spec average_block_sizes(current | max) -> [{allocator(), [{Key,Val}]}] when
Key :: mbcs | sbcs,
Val :: number().
-spec average_block_sizes(current | max) -> [{allocator(), [{Key, Val}]}] when
Key :: mbcs | sbcs,
Val :: number().
average_block_sizes(Keyword) ->
Dict = lists:foldl(fun({{Instance,_},Props},Dict0) ->
Dict = lists:foldl(fun({{Instance, _}, Props}, Dict0) ->
CarSbcs = container_value(Props, Keyword, sbcs, blocks),
SizeSbcs = container_value(Props, Keyword, sbcs, blocks_size),
CarMbcs = container_value(Props, Keyword, mbcs, blocks),
SizeMbcs = container_value(Props, Keyword, mbcs, blocks_size),
Dict1 = dict:update_counter({Instance,sbcs,count},CarSbcs,Dict0),
Dict2 = dict:update_counter({Instance,sbcs,size},SizeSbcs,Dict1),
Dict3 = dict:update_counter({Instance,mbcs,count},CarMbcs,Dict2),
Dict4 = dict:update_counter({Instance,mbcs,size},SizeMbcs,Dict3),
Dict1 = dict:update_counter({Instance, sbcs, count}, CarSbcs, Dict0),
Dict2 = dict:update_counter({Instance, sbcs, size}, SizeSbcs, Dict1),
Dict3 = dict:update_counter({Instance, mbcs, count}, CarMbcs, Dict2),
Dict4 = dict:update_counter({Instance, mbcs, size}, SizeMbcs, Dict3),
Dict4
end,
dict:new(),
util_alloc()),
average_group(average_calc(lists:sort(dict:to_list(Dict)))).
end,
dict:new(),
util_alloc()),
average_group(average_calc(lists:sort(dict:to_list(Dict)))).
%% @doc compares the amount of single block carriers (`sbcs') vs the
%% number of multiblock carriers (`mbcs') for each individual allocator in
@ -352,79 +352,79 @@ average_block_sizes(Keyword) ->
%% the worst the condition. The list is sorted accordingly.
-spec sbcs_to_mbcs(max | current) -> [allocdata(term())].
sbcs_to_mbcs(Keyword) ->
WeightedList = [begin
Sbcs = container_value(Props, Keyword, sbcs, blocks),
Mbcs = container_value(Props, Keyword, mbcs, blocks),
Ratio = case {Sbcs, Mbcs} of
{0,0} -> 0;
{_,0} -> infinity; % that is bad!
{_,_} -> Sbcs / Mbcs
end,
{Ratio, {Allocator,N}}
end || {{Allocator, N}, Props} <- util_alloc()],
[{Alloc,Ratio} || {Ratio,Alloc} <- lists:reverse(lists:sort(WeightedList))].
WeightedList = [begin
Sbcs = container_value(Props, Keyword, sbcs, blocks),
Mbcs = container_value(Props, Keyword, mbcs, blocks),
Ratio = case {Sbcs, Mbcs} of
{0, 0} -> 0;
{_, 0} -> infinity; % that is bad!
{_, _} -> Sbcs / Mbcs
end,
{Ratio, {Allocator, N}}
end || {{Allocator, N}, Props} <- util_alloc()],
[{Alloc, Ratio} || {Ratio, Alloc} <- lists:reverse(lists:sort(WeightedList))].
%% @doc returns a dump of all allocator settings and values
-spec allocators() -> [allocdata(term())].
allocators() ->
UtilAllocators = erlang:system_info(alloc_util_allocators),
Allocators = [sys_alloc,mseg_alloc|UtilAllocators],
[{{A,N}, format_alloc(A, Props)} ||
A <- Allocators,
Allocs <- [erlang:system_info({allocator,A})],
Allocs =/= false,
{_,N,Props} <- Allocs].
UtilAllocators = erlang:system_info(alloc_util_allocators),
Allocators = [sys_alloc, mseg_alloc | UtilAllocators],
[{{A, N}, format_alloc(A, Props)} ||
A <- Allocators,
Allocs <- [erlang:system_info({allocator, A})],
Allocs =/= false,
{_, N, Props} <- Allocs].
format_alloc(Alloc, Props) ->
%% {versions,_,_} is implicitly deleted in order to allow the use of the
%% orddict api, and never really having come across a case where it was
%% useful to know.
[{K, format_blocks(Alloc, K, V)} || {K, V} <- lists:sort(Props)].
%% {versions,_,_} is implicitly deleted in order to allow the use of the
%% orddict api, and never really having come across a case where it was
%% useful to know.
[{K, format_blocks(Alloc, K, V)} || {K, V} <- lists:sort(Props)].
format_blocks(_, _, []) ->
[];
[];
format_blocks(Alloc, Key, [{blocks, L} | List]) when is_list(L) ->
%% OTP-22 introduces carrier migrations across types, and OTP-23 changes the
%% format of data reported to be a bit richer; however it's not compatible
%% with most calculations made for this library.
%% So what we do here for `blocks' is merge all the info into the one the
%% library expects (`blocks' and `blocks_size'), then keep the original
%% one in case it is further needed.
%% There were further changes to `mbcs_pool' changing `foreign_blocks',
%% `blocks' and `blocks_size' into just `blocks' with a proplist, so we're breaking
%% up to use that one too.
%% In the end we go from `{blocks, [{Alloc, [...]}]}' to:
%% - `{blocks, ...}' (4-tuple in mbcs and sbcs, 2-tuple in mbcs_pool)
%% - `{blocks_size, ...}' (4-tuple in mbcs and sbcs, 2-tuple in mbcs_pool)
%% - `{foreign_blocks, [...]}' (just append lists =/= `Alloc')
%% - `{raw_blocks, [...]}' (original value)
Foreign = lists:filter(fun({A, _Props}) -> A =/= Alloc end, L),
Type = case Key of
mbcs_pool -> int;
_ -> quadruple
end,
MergeF = fun(K) ->
fun({_A, Props}, Acc) ->
case lists:keyfind(K, 1, Props) of
{K,Cur,Last,Max} -> {Cur, Last, Max};
{K,V} -> Acc+V
end
end
end,
%% Since tuple sizes change, hack around it using tuple_to_list conversion
%% and set the accumulator to a list so it defaults to not putting anything
{Blocks, BlocksSize} = case Type of
int ->
{{blocks, lists:foldl(MergeF(count), 0, L)},
{blocks_size, lists:foldl(MergeF(size), 0, L)}};
quadruple ->
{list_to_tuple([blocks | tuple_to_list(lists:foldl(MergeF(count), {0,0,0}, L))]),
list_to_tuple([blocks_size | tuple_to_list(lists:foldl(MergeF(size), {0,0,0}, L))])}
end,
[Blocks, BlocksSize, {foreign_blocks, Foreign}, {raw_blocks, L}
| format_blocks(Alloc, Key, List)];
%% OTP-22 introduces carrier migrations across types, and OTP-23 changes the
%% format of data reported to be a bit richer; however it's not compatible
%% with most calculations made for this library.
%% So what we do here for `blocks' is merge all the info into the one the
%% library expects (`blocks' and `blocks_size'), then keep the original
%% one in case it is further needed.
%% There were further changes to `mbcs_pool' changing `foreign_blocks',
%% `blocks' and `blocks_size' into just `blocks' with a proplist, so we're breaking
%% up to use that one too.
%% In the end we go from `{blocks, [{Alloc, [...]}]}' to:
%% - `{blocks, ...}' (4-tuple in mbcs and sbcs, 2-tuple in mbcs_pool)
%% - `{blocks_size, ...}' (4-tuple in mbcs and sbcs, 2-tuple in mbcs_pool)
%% - `{foreign_blocks, [...]}' (just append lists =/= `Alloc')
%% - `{raw_blocks, [...]}' (original value)
Foreign = lists:filter(fun({A, _Props}) -> A =/= Alloc end, L),
Type = case Key of
mbcs_pool -> int;
_ -> quadruple
end,
MergeF = fun(K) ->
fun({_A, Props}, Acc) ->
case lists:keyfind(K, 1, Props) of
{K, Cur, Last, Max} -> {Cur, Last, Max};
{K, V} -> Acc + V
end
end
end,
%% Since tuple sizes change, hack around it using tuple_to_list conversion
%% and set the accumulator to a list so it defaults to not putting anything
{Blocks, BlocksSize} = case Type of
int ->
{{blocks, lists:foldl(MergeF(count), 0, L)},
{blocks_size, lists:foldl(MergeF(size), 0, L)}};
quadruple ->
{list_to_tuple([blocks | tuple_to_list(lists:foldl(MergeF(count), {0, 0, 0}, L))]),
list_to_tuple([blocks_size | tuple_to_list(lists:foldl(MergeF(size), {0, 0, 0}, L))])}
end,
[Blocks, BlocksSize, {foreign_blocks, Foreign}, {raw_blocks, L}
| format_blocks(Alloc, Key, List)];
format_blocks(Alloc, Key, [H | T]) ->
[H | format_blocks(Alloc, Key, T)].
[H | format_blocks(Alloc, Key, T)].
%% @doc returns a dump of all allocator settings and values modified
%% depending on the argument.
@ -435,70 +435,70 @@ format_blocks(Alloc, Key, [H | T]) ->
%% </ul>
-spec allocators(types) -> [allocdata_types(term())].
allocators(types) ->
allocators_types(alloc(), []).
allocators_types([{{Type,No},Vs}|T], As) ->
case lists:keytake(Type, 1, As) of
false ->
allocators_types(T,[{Type,[No],sort_values(Type, Vs)}|As]);
{value,{Type,Nos,OVs},NAs} ->
MergedValues = merge_values(sort_values(Type, Vs),OVs),
allocators_types(T,[{Type,[No|Nos],MergedValues}|NAs])
end;
allocators_types(alloc(), []).
allocators_types([{{Type, No}, Vs} | T], As) ->
case lists:keytake(Type, 1, As) of
false ->
allocators_types(T, [{Type, [No], sort_values(Type, Vs)} | As]);
{value, {Type, Nos, OVs}, NAs} ->
MergedValues = merge_values(sort_values(Type, Vs), OVs),
allocators_types(T, [{Type, [No | Nos], MergedValues} | NAs])
end;
allocators_types([], As) ->
[{{Type,Nos},Vs} || {Type, Nos, Vs} <- As].
merge_values([{Key,Vs}|T1], [{Key,OVs}|T2]) when Key =:= memkind ->
[{Key, merge_values(Vs, OVs)} | merge_values(T1, T2)];
merge_values([{Key,Vs}|T1], [{Key,OVs}|T2]) when Key =:= calls;
Key =:= fix_types;
Key =:= sbmbcs;
Key =:= mbcs;
Key =:= mbcs_pool;
Key =:= sbcs;
Key =:= status ->
[{Key,lists:map(
fun({{K,MV1,V1}, {K,MV2,V2}}) ->
%% Merge the MegaVs + Vs into one
V = MV1 * 1000000 + V1 + MV2 * 1000000 + V2,
{K, V div 1000000, V rem 1000000};
({{K,V1}, {K,V2}}) when K =:= segments_watermark ->
%% We take the maximum watermark as that is
%% a value that we can use somewhat. Ideally
%% maybe the average should be used, but the
%% value is very rarely important so leave it
%% like this for now.
{K, lists:max([V1,V2])};
({{K,V1}, {K,V2}}) when K =:= foreign_blocks; K =:= raw_blocks ->
%% foreign blocks are just merged as a bigger list.
{K, V1++V2};
({{K,V1}, {K,V2}}) ->
{K, V1 + V2};
({{K,C1,L1,M1}, {K,C2,L2,M2}}) ->
%% Merge the Curr, Last, Max into one
{K, C1+C2, L1+L2, M1+M2}
end, lists:zip(Vs,OVs))} | merge_values(T1,T2)];
merge_values([{Type,_Vs}=E|T1], T2) when Type =:= mbcs_pool ->
%% For values never showing up in instance 0 but in all other
[E|merge_values(T1,T2)];
merge_values(T1, [{Type,_Vs}=E|T2]) when Type =:= fix_types ->
%% For values only showing up in instance 0
[E|merge_values(T1,T2)];
merge_values([E|T1], [E|T2]) ->
%% For values that are constant
[E|merge_values(T1,T2)];
merge_values([{options,_Vs1}|T1], [{options,_Vs2} = E|T2]) ->
%% Options change a but in between instance 0 and the other,
%% We show the others as they are the most interesting.
[E|merge_values(T1,T2)];
merge_values([],[]) ->
[].
[{{Type, Nos}, Vs} || {Type, Nos, Vs} <- As].
merge_values([{Key, Vs} | T1], [{Key, OVs} | T2]) when Key =:= memkind ->
[{Key, merge_values(Vs, OVs)} | merge_values(T1, T2)];
merge_values([{Key, Vs} | T1], [{Key, OVs} | T2]) when Key =:= calls;
Key =:= fix_types;
Key =:= sbmbcs;
Key =:= mbcs;
Key =:= mbcs_pool;
Key =:= sbcs;
Key =:= status ->
[{Key, lists:map(
fun({{K, MV1, V1}, {K, MV2, V2}}) ->
%% Merge the MegaVs + Vs into one
V = MV1 * 1000000 + V1 + MV2 * 1000000 + V2,
{K, V div 1000000, V rem 1000000};
({{K, V1}, {K, V2}}) when K =:= segments_watermark ->
%% We take the maximum watermark as that is
%% a value that we can use somewhat. Ideally
%% maybe the average should be used, but the
%% value is very rarely important so leave it
%% like this for now.
{K, lists:max([V1, V2])};
({{K, V1}, {K, V2}}) when K =:= foreign_blocks; K =:= raw_blocks ->
%% foreign blocks are just merged as a bigger list.
{K, V1 ++ V2};
({{K, V1}, {K, V2}}) ->
{K, V1 + V2};
({{K, C1, L1, M1}, {K, C2, L2, M2}}) ->
%% Merge the Curr, Last, Max into one
{K, C1 + C2, L1 + L2, M1 + M2}
end, lists:zip(Vs, OVs))} | merge_values(T1, T2)];
merge_values([{Type, _Vs} = E | T1], T2) when Type =:= mbcs_pool ->
%% For values never showing up in instance 0 but in all other
[E | merge_values(T1, T2)];
merge_values(T1, [{Type, _Vs} = E | T2]) when Type =:= fix_types ->
%% For values only showing up in instance 0
[E | merge_values(T1, T2)];
merge_values([E | T1], [E | T2]) ->
%% For values that are constant
[E | merge_values(T1, T2)];
merge_values([{options, _Vs1} | T1], [{options, _Vs2} = E | T2]) ->
%% Options change a but in between instance 0 and the other,
%% We show the others as they are the most interesting.
[E | merge_values(T1, T2)];
merge_values([], []) ->
[].
sort_values(mseg_alloc, Vs) ->
{value, {memkind, MemKindVs}, OVs} = lists:keytake(memkind, 1, Vs),
lists:sort([{memkind, lists:sort(MemKindVs)} | OVs]);
{value, {memkind, MemKindVs}, OVs} = lists:keytake(memkind, 1, Vs),
lists:sort([{memkind, lists:sort(MemKindVs)} | OVs]);
sort_values(_Type, Vs) ->
lists:sort(Vs).
lists:sort(Vs).
%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Snapshot handling %%%
@ -510,7 +510,7 @@ sort_values(_Type, Vs) ->
%% To unsert the snapshot, see {@link snapshot_clear/1}.
-spec snapshot() -> snapshot() | undefined.
snapshot() ->
put(recon_alloc_snapshot, snapshot_int()).
put(recon_alloc_snapshot, snapshot_int()).
%% @doc clear the current snapshot in the process dictionary, if present,
%% and return the value it had before being unset.
@ -518,37 +518,37 @@ snapshot() ->
%% Maybe we should use erlang:delete(Key) here instead?
-spec snapshot_clear() -> snapshot() | undefined.
snapshot_clear() ->
put(recon_alloc_snapshot, undefined).
put(recon_alloc_snapshot, undefined).
%% @doc print a dump of the current snapshot stored by {@link snapshot/0}
%% Prints `undefined' if no snapshot has been taken.
-spec snapshot_print() -> ok.
snapshot_print() ->
io:format("~p.~n",[snapshot_get()]).
io:format("~p.~n", [snapshot_get()]).
%% @doc returns the current snapshot stored by {@link snapshot/0}.
%% Returns `undefined' if no snapshot has been taken.
-spec snapshot_get() -> snapshot() | undefined.
snapshot_get() ->
get(recon_alloc_snapshot).
get(recon_alloc_snapshot).
%% @doc save the current snapshot taken by {@link snapshot/0} to a file.
%% If there is no current snapshot, a snaphot of the current allocator
%% statistics will be written to the file.
-spec snapshot_save(Filename) -> ok when
Filename :: file:name().
Filename :: file:name().
snapshot_save(Filename) ->
Snapshot = case snapshot_get() of
undefined ->
snapshot_int();
Snap ->
Snap
end,
case file:write_file(Filename,io_lib:format("~p.~n",[Snapshot])) of
ok -> ok;
{error,Reason} ->
erlang:error(Reason,[Filename])
end.
Snapshot = case snapshot_get() of
undefined ->
snapshot_int();
Snap ->
Snap
end,
case file:write_file(Filename, io_lib:format("~p.~n", [Snapshot])) of
ok -> ok;
{error, Reason} ->
erlang:error(Reason, [Filename])
end.
%% @doc load a snapshot from a given file. The format of the data in the
@ -577,26 +577,26 @@ snapshot_save(Filename) ->
%% 18411064'''
%%
-spec snapshot_load(Filename) -> snapshot() | undefined when
Filename :: file:name().
Filename :: file:name().
snapshot_load(Filename) ->
{ok,[Terms]} = file:consult(Filename),
Snapshot =
case Terms of
%% We handle someone using
%% {erlang:memory(),
%% [{A,erlang:system_info({allocator,A})} ||
%% A <- erlang:system_info(alloc_util_allocators)++[sys_alloc,mseg_alloc]]}
%% to dump data.
{M,[{Alloc,_D}|_] = Allocs} when is_atom(Alloc) ->
{M,[{{A,N},lists:sort(proplists:delete(versions,Props))} ||
{A,Instances = [_|_]} <- Allocs,
{_, N, Props} <- Instances]};
%% We assume someone used recon_alloc:snapshot() to store this one
{M,Allocs} ->
{M,[{AN,lists:sort(proplists:delete(versions,Props))} ||
{AN, Props} <- Allocs]}
end,
put(recon_alloc_snapshot,Snapshot).
{ok, [Terms]} = file:consult(Filename),
Snapshot =
case Terms of
%% We handle someone using
%% {erlang:memory(),
%% [{A,erlang:system_info({allocator,A})} ||
%% A <- erlang:system_info(alloc_util_allocators)++[sys_alloc,mseg_alloc]]}
%% to dump data.
{M, [{Alloc, _D} | _] = Allocs} when is_atom(Alloc) ->
{M, [{{A, N}, lists:sort(proplists:delete(versions, Props))} ||
{A, Instances = [_ | _]} <- Allocs,
{_, N, Props} <- Instances]};
%% We assume someone used recon_alloc:snapshot() to store this one
{M, Allocs} ->
{M, [{AN, lists:sort(proplists:delete(versions, Props))} ||
{AN, Props} <- Allocs]}
end,
put(recon_alloc_snapshot, Snapshot).
%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Handling of units %%%
@ -615,62 +615,62 @@ snapshot_load(Filename) ->
%%
-spec set_unit(byte | kilobyte | megabyte | gigabyte) -> ok.
set_unit(byte) ->
put(recon_alloc_unit,undefined);
put(recon_alloc_unit, undefined);
set_unit(kilobyte) ->
put(recon_alloc_unit,1024);
put(recon_alloc_unit, 1024);
set_unit(megabyte) ->
put(recon_alloc_unit,1024*1024);
put(recon_alloc_unit, 1024 * 1024);
set_unit(gigabyte) ->
put(recon_alloc_unit,1024*1024*1024).
conv({Mem,Allocs} = D) ->
case get(recon_alloc_unit) of
undefined ->
D;
Factor ->
{conv_mem(Mem,Factor),conv_alloc(Allocs,Factor)}
end.
conv_mem(Mem,Factor) ->
[{T,M / Factor} || {T,M} <- Mem].
conv_alloc([{{sys_alloc,_I},_Props} = Alloc|R], Factor) ->
[Alloc|conv_alloc(R,Factor)];
conv_alloc([{{mseg_alloc,_I} = AI,Props}|R], Factor) ->
MemKind = orddict:fetch(memkind,Props),
Status = orddict:fetch(status,MemKind),
{segments_size,Curr,Last,Max} = lists:keyfind(segments_size,1,Status),
NewSegSize = {segments_size,Curr/Factor,Last/Factor,Max/Factor},
NewStatus = lists:keyreplace(segments_size,1,Status,NewSegSize),
NewProps = orddict:store(memkind,orddict:store(status,NewStatus,MemKind),
Props),
[{AI,NewProps}|conv_alloc(R,Factor)];
conv_alloc([{AI,Props}|R], Factor) ->
FactorFun = fun({T,Curr}) when
T =:= blocks_size; T =:= carriers_size ->
{T,Curr/Factor};
({T,Curr,Last,Max}) when
T =:= blocks_size; T =:= carriers_size;
T =:= mseg_alloc_carriers_size;
T =:= sys_alloc_carriers_size ->
{T,Curr/Factor,Last/Factor,Max/Factor};
(T) ->
T
end,
NewMbcsProp = [FactorFun(Prop) || Prop <- orddict:fetch(mbcs,Props)],
NewSbcsProp = [FactorFun(Prop) || Prop <- orddict:fetch(sbcs,Props)],
NewProps = orddict:store(sbcs,NewSbcsProp,
orddict:store(mbcs,NewMbcsProp,Props)),
case orddict:find(mbcs_pool,Props) of
error ->
[{AI,NewProps}|conv_alloc(R,Factor)];
{ok,MbcsPoolProps} ->
NewMbcsPoolProp = [FactorFun(Prop) || Prop <- MbcsPoolProps],
NewPoolProps = orddict:store(mbcs_pool,NewMbcsPoolProp,NewProps),
[{AI,NewPoolProps}|conv_alloc(R,Factor)]
end;
conv_alloc([],_Factor) ->
[].
put(recon_alloc_unit, 1024 * 1024 * 1024).
conv({Mem, Allocs} = D) ->
case get(recon_alloc_unit) of
undefined ->
D;
Factor ->
{conv_mem(Mem, Factor), conv_alloc(Allocs, Factor)}
end.
conv_mem(Mem, Factor) ->
[{T, M / Factor} || {T, M} <- Mem].
conv_alloc([{{sys_alloc, _I}, _Props} = Alloc | R], Factor) ->
[Alloc | conv_alloc(R, Factor)];
conv_alloc([{{mseg_alloc, _I} = AI, Props} | R], Factor) ->
MemKind = orddict:fetch(memkind, Props),
Status = orddict:fetch(status, MemKind),
{segments_size, Curr, Last, Max} = lists:keyfind(segments_size, 1, Status),
NewSegSize = {segments_size, Curr / Factor, Last / Factor, Max / Factor},
NewStatus = lists:keyreplace(segments_size, 1, Status, NewSegSize),
NewProps = orddict:store(memkind, orddict:store(status, NewStatus, MemKind),
Props),
[{AI, NewProps} | conv_alloc(R, Factor)];
conv_alloc([{AI, Props} | R], Factor) ->
FactorFun = fun({T, Curr}) when
T =:= blocks_size; T =:= carriers_size ->
{T, Curr / Factor};
({T, Curr, Last, Max}) when
T =:= blocks_size; T =:= carriers_size;
T =:= mseg_alloc_carriers_size;
T =:= sys_alloc_carriers_size ->
{T, Curr / Factor, Last / Factor, Max / Factor};
(T) ->
T
end,
NewMbcsProp = [FactorFun(Prop) || Prop <- orddict:fetch(mbcs, Props)],
NewSbcsProp = [FactorFun(Prop) || Prop <- orddict:fetch(sbcs, Props)],
NewProps = orddict:store(sbcs, NewSbcsProp,
orddict:store(mbcs, NewMbcsProp, Props)),
case orddict:find(mbcs_pool, Props) of
error ->
[{AI, NewProps} | conv_alloc(R, Factor)];
{ok, MbcsPoolProps} ->
NewMbcsPoolProp = [FactorFun(Prop) || Prop <- MbcsPoolProps],
NewPoolProps = orddict:store(mbcs_pool, NewMbcsPoolProp, NewProps),
[{AI, NewPoolProps} | conv_alloc(R, Factor)]
end;
conv_alloc([], _Factor) ->
[].
%%%%%%%%%%%%%%%
%%% Private %%%
@ -680,99 +680,99 @@ conv_alloc([],_Factor) ->
%% The weight cares about both the sbcs and mbcs values, and also
%% returns a proplist of possibly interesting values.
weighed_values({SbcsBlockSize, SbcsCarrierSize},
{MbcsBlockSize, MbcsCarrierSize}) ->
SbcsUsage = usage(SbcsBlockSize, SbcsCarrierSize),
MbcsUsage = usage(MbcsBlockSize, MbcsCarrierSize),
SbcsWeight = (1.00 - SbcsUsage)*SbcsCarrierSize,
MbcsWeight = (1.00 - MbcsUsage)*MbcsCarrierSize,
Weight = SbcsWeight + MbcsWeight,
{Weight, [{sbcs_usage, SbcsUsage},
{mbcs_usage, MbcsUsage},
{sbcs_block_size, SbcsBlockSize},
{sbcs_carriers_size, SbcsCarrierSize},
{mbcs_block_size, MbcsBlockSize},
{mbcs_carriers_size, MbcsCarrierSize}]}.
{MbcsBlockSize, MbcsCarrierSize}) ->
SbcsUsage = usage(SbcsBlockSize, SbcsCarrierSize),
MbcsUsage = usage(MbcsBlockSize, MbcsCarrierSize),
SbcsWeight = (1.00 - SbcsUsage) * SbcsCarrierSize,
MbcsWeight = (1.00 - MbcsUsage) * MbcsCarrierSize,
Weight = SbcsWeight + MbcsWeight,
{Weight, [{sbcs_usage, SbcsUsage},
{mbcs_usage, MbcsUsage},
{sbcs_block_size, SbcsBlockSize},
{sbcs_carriers_size, SbcsCarrierSize},
{mbcs_block_size, MbcsBlockSize},
{mbcs_carriers_size, MbcsCarrierSize}]}.
%% Returns the `BlockSize/CarrierSize' as a 0.0 -> 1.0 percentage,
%% but also takes 0/0 to be 100% to make working with sorting and
%% weights simpler.
usage(0,0) -> 1.00;
usage(0.0,0.0) -> 1.00;
usage(0, 0) -> 1.00;
usage(0.0, 0.0) -> 1.00;
%usage(N,0) -> ???;
usage(Block,Carrier) -> Block/Carrier.
usage(Block, Carrier) -> Block / Carrier.
%% Calculation for the average of blocks being used.
average_calc([]) ->
[];
average_calc([{{Instance,Type,count},Ct},{{Instance,Type,size},Size}|Rest]) ->
case {Size,Ct} of
{_,0} when Size == 0 -> [{Instance, Type, 0} | average_calc(Rest)];
_ -> [{Instance,Type,Size/Ct} | average_calc(Rest)]
end.
[];
average_calc([{{Instance, Type, count}, Ct}, {{Instance, Type, size}, Size} | Rest]) ->
case {Size, Ct} of
{_, 0} when Size == 0 -> [{Instance, Type, 0} | average_calc(Rest)];
_ -> [{Instance, Type, Size / Ct} | average_calc(Rest)]
end.
%% Regrouping/merging values together in proplists
average_group([]) -> [];
average_group([{Instance,Type1,N},{Instance,Type2,M} | Rest]) ->
[{Instance,[{Type1,N},{Type2,M}]} | average_group(Rest)].
average_group([{Instance, Type1, N}, {Instance, Type2, M} | Rest]) ->
[{Instance, [{Type1, N}, {Type2, M}]} | average_group(Rest)].
%% Get the total carrier size
container_size(Props, Keyword, Container) ->
Sbcs = container_value(Props, Keyword, sbcs, Container),
Mbcs = container_value(Props, Keyword, mbcs, Container),
Sbcs+Mbcs.
Sbcs = container_value(Props, Keyword, sbcs, Container),
Mbcs = container_value(Props, Keyword, mbcs, Container),
Sbcs + Mbcs.
container_value(Props, Keyword, Type, Container)
when is_atom(Keyword) ->
container_value(Props, key2pos(Keyword), Type, Container);
when is_atom(Keyword) ->
container_value(Props, key2pos(Keyword), Type, Container);
container_value(Props, Pos, mbcs = Type, Container)
when Pos == ?CURRENT_POS,
((Container =:= blocks) or (Container =:= blocks_size)
or (Container =:= carriers) or (Container =:= carriers_size))->
%% We include the mbcs_pool into the value for mbcs.
%% The mbcs_pool contains carriers that have been abandoned
%% by the specific allocator instance and can therefore be
%% grabbed by another instance of the same type.
%% The pool was added in R16B02 and enabled by default in 17.0.
%% See erts/emulator/internal_docs/CarrierMigration.md in
%% Erlang/OTP repo for more details.
Pool = case proplists:get_value(mbcs_pool, Props) of
PoolProps when PoolProps =/= undefined ->
element(Pos,lists:keyfind(Container, 1, PoolProps));
_ -> 0
end,
TypeProps = proplists:get_value(Type, Props),
Pool + element(Pos,lists:keyfind(Container, 1, TypeProps));
when Pos == ?CURRENT_POS,
((Container =:= blocks) or (Container =:= blocks_size)
or (Container =:= carriers) or (Container =:= carriers_size)) ->
%% We include the mbcs_pool into the value for mbcs.
%% The mbcs_pool contains carriers that have been abandoned
%% by the specific allocator instance and can therefore be
%% grabbed by another instance of the same type.
%% The pool was added in R16B02 and enabled by default in 17.0.
%% See erts/emulator/internal_docs/CarrierMigration.md in
%% Erlang/OTP repo for more details.
Pool = case proplists:get_value(mbcs_pool, Props) of
PoolProps when PoolProps =/= undefined ->
element(Pos, lists:keyfind(Container, 1, PoolProps));
_ -> 0
end,
TypeProps = proplists:get_value(Type, Props),
Pool + element(Pos, lists:keyfind(Container, 1, TypeProps));
container_value(Props, Pos, Type, Container)
when Type =:= sbcs; Type =:= mbcs ->
TypeProps = proplists:get_value(Type, Props),
element(Pos,lists:keyfind(Container, 1, TypeProps)).
when Type =:= sbcs; Type =:= mbcs ->
TypeProps = proplists:get_value(Type, Props),
element(Pos, lists:keyfind(Container, 1, TypeProps)).
%% Create a new snapshot
snapshot_int() ->
{erlang:memory(),allocators()}.
{erlang:memory(), allocators()}.
%% If no snapshot has been taken/loaded then we use current values
snapshot_get_int() ->
case snapshot_get() of
undefined ->
conv(snapshot_int());
Snapshot ->
conv(Snapshot)
end.
case snapshot_get() of
undefined ->
conv(snapshot_int());
Snapshot ->
conv(Snapshot)
end.
%% Get the alloc part of a snapshot
alloc() ->
{_Mem,Allocs} = snapshot_get_int(),
Allocs.
{_Mem, Allocs} = snapshot_get_int(),
Allocs.
alloc(Type) ->
[{{T,Instance},Props} || {{T,Instance},Props} <- alloc(),
lists:member(T,Type)].
[{{T, Instance}, Props} || {{T, Instance}, Props} <- alloc(),
lists:member(T, Type)].
%% Get only alloc_util allocs
util_alloc() ->
alloc(?UTIL_ALLOCATORS).
alloc(?UTIL_ALLOCATORS).
key2pos(current) ->
?CURRENT_POS;
?CURRENT_POS;
key2pos(max) ->
?MAX_POS.
?MAX_POS.

+ 160
- 160
src/test/recon-2.5.1/recon_lib.erl Wyświetl plik

@ -6,14 +6,14 @@
%%% @end
-module(recon_lib).
-export([sliding_window/2, sample/2, count/1,
port_list/1, port_list/2,
proc_attrs/1, proc_attrs/2,
inet_attrs/1, inet_attrs/2,
triple_to_pid/3, term_to_pid/1,
term_to_port/1,
time_map/5, time_fold/6,
scheduler_usage_diff/2,
sublist_top_n_attrs/2]).
port_list/1, port_list/2,
proc_attrs/1, proc_attrs/2,
inet_attrs/1, inet_attrs/2,
triple_to_pid/3, term_to_pid/1,
term_to_port/1,
time_map/5, time_fold/6,
scheduler_usage_diff/2,
sublist_top_n_attrs/2]).
%% private exports
-export([binary_memory/1]).
@ -22,66 +22,66 @@
%% @doc Compare two samples and return a list based on some key. The type mentioned
%% for the structure is `diff()' (`{Key,Val,Other}'), which is compatible with
%% the {@link recon:proc_attrs()} type.
-spec sliding_window(First:: diff(), Last:: diff()) -> diff().
-spec sliding_window(First :: diff(), Last :: diff()) -> diff().
sliding_window(First, Last) ->
Dict = lists:foldl(
fun({Key, {Current, Other}}, Acc) ->
dict:update(Key,
fun({Old,_Other}) -> {Current-Old, Other} end,
{Current, Other},
Acc)
end,
dict:from_list([{K,{V,O}} || {K,V,O} <- First]),
[{K,{V,O}} || {K,V,O} <- Last]
),
[{K,V,O} || {K,{V,O}} <- dict:to_list(Dict)].
Dict = lists:foldl(
fun({Key, {Current, Other}}, Acc) ->
dict:update(Key,
fun({Old, _Other}) -> {Current - Old, Other} end,
{Current, Other},
Acc)
end,
dict:from_list([{K, {V, O}} || {K, V, O} <- First]),
[{K, {V, O}} || {K, V, O} <- Last]
),
[{K, V, O} || {K, {V, O}} <- dict:to_list(Dict)].
%% @doc Runs a fun once, waits `Ms', runs the fun again,
%% and returns both results.
-spec sample(Ms:: non_neg_integer(), fun(() -> term())) ->
{First:: term(), Second:: term()}.
-spec sample(Ms :: non_neg_integer(), fun(() -> term())) ->
{First :: term(), Second :: term()}.
sample(Delay, Fun) ->
First = Fun(),
timer:sleep(Delay),
Second = Fun(),
{First, Second}.
First = Fun(),
timer:sleep(Delay),
Second = Fun(),
{First, Second}.
%% @doc Takes a list of terms, and counts how often each of
%% them appears in the list. The list returned is in no
%% particular order.
-spec count([term()]) -> [{term(), Count:: integer()}].
-spec count([term()]) -> [{term(), Count :: integer()}].
count(Terms) ->
Dict = lists:foldl(
fun(Val, Acc) -> dict:update_counter(Val, 1, Acc) end,
dict:new(),
Terms
),
dict:to_list(Dict).
Dict = lists:foldl(
fun(Val, Acc) -> dict:update_counter(Val, 1, Acc) end,
dict:new(),
Terms
),
dict:to_list(Dict).
%% @doc Returns a list of all the open ports in the VM, coupled with
%% one of the properties desired from `erlang:port_info/1-2'.
-spec port_list(Attr:: atom()) -> [{port(), term()}].
-spec port_list(Attr :: atom()) -> [{port(), term()}].
port_list(Attr) ->
[{Port,Val} || Port <- erlang:ports(),
{_, Val} <- [erlang:port_info(Port, Attr)]].
[{Port, Val} || Port <- erlang:ports(),
{_, Val} <- [erlang:port_info(Port, Attr)]].
%% @doc Returns a list of all the open ports in the VM, but only
%% if the `Attr''s resulting value matches `Val'. `Attr' must be
%% a property accepted by `erlang:port_info/2'.
-spec port_list(Attr:: atom(), term()) -> [port()].
-spec port_list(Attr :: atom(), term()) -> [port()].
port_list(Attr, Val) ->
[Port || Port <- erlang:ports(),
{Attr, Val} =:= erlang:port_info(Port, Attr)].
[Port || Port <- erlang:ports(),
{Attr, Val} =:= erlang:port_info(Port, Attr)].
%% @doc Returns the attributes ({@link recon:proc_attrs()}) of
%% all processes of the node, except the caller.
-spec proc_attrs(term()) -> [recon:proc_attrs()].
proc_attrs(AttrName) ->
Self = self(),
[Attrs || Pid <- processes(),
Pid =/= Self,
{ok, Attrs} <- [proc_attrs(AttrName, Pid)]
].
Self = self(),
[Attrs || Pid <- processes(),
Pid =/= Self,
{ok, Attrs} <- [proc_attrs(AttrName, Pid)]
].
%% @doc Returns the attributes of a given process. This form of attributes
%% is standard for most comparison functions for processes in recon.
@ -90,196 +90,196 @@ proc_attrs(AttrName) ->
%% by the process for binary data on the global heap.
-spec proc_attrs(term(), pid()) -> {ok, recon:proc_attrs()} | {error, term()}.
proc_attrs(binary_memory, Pid) ->
case process_info(Pid, [binary, registered_name,
current_function, initial_call]) of
[{_, Bins}, {registered_name,Name}, Init, Cur] ->
{ok, {Pid, binary_memory(Bins), [Name || is_atom(Name)]++[Init, Cur]}};
undefined ->
{error, undefined}
end;
case process_info(Pid, [binary, registered_name,
current_function, initial_call]) of
[{_, Bins}, {registered_name, Name}, Init, Cur] ->
{ok, {Pid, binary_memory(Bins), [Name || is_atom(Name)] ++ [Init, Cur]}};
undefined ->
{error, undefined}
end;
proc_attrs(AttrName, Pid) ->
case process_info(Pid, [AttrName, registered_name,
current_function, initial_call]) of
[{_, Attr}, {registered_name,Name}, Init, Cur] ->
{ok, {Pid, Attr, [Name || is_atom(Name)]++[Init, Cur]}};
undefined ->
{error, undefined}
end.
case process_info(Pid, [AttrName, registered_name,
current_function, initial_call]) of
[{_, Attr}, {registered_name, Name}, Init, Cur] ->
{ok, {Pid, Attr, [Name || is_atom(Name)] ++ [Init, Cur]}};
undefined ->
{error, undefined}
end.
%% @doc Returns the attributes ({@link recon:inet_attrs()}) of
%% all inet ports (UDP, SCTP, TCP) of the node.
-spec inet_attrs(term()) -> [recon:inet_attrs()].
inet_attrs(AttrName) ->
Ports = [Port || Port <- erlang:ports(),
{_, Name} <- [erlang:port_info(Port, name)],
Name =:= "tcp_inet" orelse
Name =:= "udp_inet" orelse
Name =:= "sctp_inet"],
[Attrs || Port <- Ports,
{ok, Attrs} <- [inet_attrs(AttrName, Port)]].
Ports = [Port || Port <- erlang:ports(),
{_, Name} <- [erlang:port_info(Port, name)],
Name =:= "tcp_inet" orelse
Name =:= "udp_inet" orelse
Name =:= "sctp_inet"],
[Attrs || Port <- Ports,
{ok, Attrs} <- [inet_attrs(AttrName, Port)]].
%% @doc Returns the attributes required for a given inet port (UDP,
%% SCTP, TCP). This form of attributes is standard for most comparison
%% functions for processes in recon.
-spec inet_attrs(AttributeName, port()) -> {ok, recon:inet_attrs()}
| {error,term()} when
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct'
| 'cnt' | 'oct'.
| {error, term()} when
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct'
| 'cnt' | 'oct'.
inet_attrs(Attr, Port) ->
Attrs = case Attr of
cnt -> [recv_cnt, send_cnt];
oct -> [recv_oct, send_oct];
_ -> [Attr]
end,
case inet:getstat(Port, Attrs) of
{ok, Props} ->
ValSum = lists:foldl(fun({_,X},Y) -> X+Y end, 0, Props),
{ok, {Port,ValSum,Props}};
{error, Reason} ->
{error, Reason}
end.
Attrs = case Attr of
cnt -> [recv_cnt, send_cnt];
oct -> [recv_oct, send_oct];
_ -> [Attr]
end,
case inet:getstat(Port, Attrs) of
{ok, Props} ->
ValSum = lists:foldl(fun({_, X}, Y) -> X + Y end, 0, Props),
{ok, {Port, ValSum, Props}};
{error, Reason} ->
{error, Reason}
end.
%% @doc Equivalent of `pid(X,Y,Z)' in the Erlang shell.
-spec triple_to_pid(N,N,N) -> pid() when
N :: non_neg_integer().
-spec triple_to_pid(N, N, N) -> pid() when
N :: non_neg_integer().
triple_to_pid(X, Y, Z) ->
list_to_pid("<" ++ integer_to_list(X) ++ "." ++
integer_to_list(Y) ++ "." ++
integer_to_list(Z) ++ ">").
list_to_pid("<" ++ integer_to_list(X) ++ "." ++
integer_to_list(Y) ++ "." ++
integer_to_list(Z) ++ ">").
%% @doc Transforms a given term to a pid.
-spec term_to_pid(recon:pid_term()) -> pid().
term_to_pid(Pid) when is_pid(Pid) -> Pid;
term_to_pid(Name) when is_atom(Name) -> whereis(Name);
term_to_pid(List = "<0."++_) -> list_to_pid(List);
term_to_pid(List = "<0." ++ _) -> list_to_pid(List);
term_to_pid(Binary = <<"<0.", _/binary>>) -> list_to_pid(binary_to_list(Binary));
term_to_pid({global, Name}) -> global:whereis_name(Name);
term_to_pid({via, Module, Name}) -> Module:whereis_name(Name);
term_to_pid({X,Y,Z}) when is_integer(X), is_integer(Y), is_integer(Z) ->
triple_to_pid(X,Y,Z).
term_to_pid({X, Y, Z}) when is_integer(X), is_integer(Y), is_integer(Z) ->
triple_to_pid(X, Y, Z).
%% @doc Transforms a given term to a port
-spec term_to_port(recon:port_term()) -> port().
term_to_port(Port) when is_port(Port) -> Port;
term_to_port(Name) when is_atom(Name) -> whereis(Name);
term_to_port("#Port<0."++Id) ->
N = list_to_integer(lists:sublist(Id, length(Id)-1)), % drop trailing '>'
term_to_port(N);
term_to_port("#Port<0." ++ Id) ->
N = list_to_integer(lists:sublist(Id, length(Id) - 1)), % drop trailing '>'
term_to_port(N);
term_to_port(N) when is_integer(N) ->
%% We rebuild the term from the int received:
%% http://www.erlang.org/doc/apps/erts/erl_ext_dist.html#id86892
Name = iolist_to_binary(atom_to_list(node())),
NameLen = iolist_size(Name),
Vsn = binary:last(term_to_binary(self())),
Bin = <<131, % term encoding value
102, % port tag
100, % atom ext tag, used for node name
NameLen:2/unit:8,
Name:NameLen/binary,
N:4/unit:8, % actual counter value
Vsn:8>>, % version
binary_to_term(Bin).
%% We rebuild the term from the int received:
%% http://www.erlang.org/doc/apps/erts/erl_ext_dist.html#id86892
Name = iolist_to_binary(atom_to_list(node())),
NameLen = iolist_size(Name),
Vsn = binary:last(term_to_binary(self())),
Bin = <<131, % term encoding value
102, % port tag
100, % atom ext tag, used for node name
NameLen:2/unit:8,
Name:NameLen/binary,
N:4/unit:8, % actual counter value
Vsn:8>>, % version
binary_to_term(Bin).
%% @doc Calls a given function every `Interval' milliseconds and supports
%% a map-like interface (each result is modified and returned)
-spec time_map(N, Interval, Fun, State, MapFun) -> [term()] when
N :: non_neg_integer(),
Interval :: pos_integer(),
Fun :: fun((State) -> {term(), State}),
State :: term(),
MapFun :: fun((_) -> term()).
N :: non_neg_integer(),
Interval :: pos_integer(),
Fun :: fun((State) -> {term(), State}),
State :: term(),
MapFun :: fun((_) -> term()).
time_map(0, _, _, _, _) ->
[];
[];
time_map(N, Interval, Fun, State, MapFun) ->
{Res, NewState} = Fun(State),
timer:sleep(Interval),
[MapFun(Res) | time_map(N-1,Interval,Fun,NewState,MapFun)].
{Res, NewState} = Fun(State),
timer:sleep(Interval),
[MapFun(Res) | time_map(N - 1, Interval, Fun, NewState, MapFun)].
%% @doc Calls a given function every `Interval' milliseconds and supports
%% a fold-like interface (each result is modified and accumulated)
-spec time_fold(N, Interval, Fun, State, FoldFun, Init) -> [term()] when
N :: non_neg_integer(),
Interval :: pos_integer(),
Fun :: fun((State) -> {term(), State}),
State :: term(),
FoldFun :: fun((term(), Init) -> Init),
Init :: term().
N :: non_neg_integer(),
Interval :: pos_integer(),
Fun :: fun((State) -> {term(), State}),
State :: term(),
FoldFun :: fun((term(), Init) -> Init),
Init :: term().
time_fold(0, _, _, _, _, Acc) ->
Acc;
Acc;
time_fold(N, Interval, Fun, State, FoldFun, Init) ->
timer:sleep(Interval),
{Res, NewState} = Fun(State),
Acc = FoldFun(Res,Init),
time_fold(N-1,Interval,Fun,NewState,FoldFun,Acc).
timer:sleep(Interval),
{Res, NewState} = Fun(State),
Acc = FoldFun(Res, Init),
time_fold(N - 1, Interval, Fun, NewState, FoldFun, Acc).
%% @doc Diffs two runs of erlang:statistics(scheduler_wall_time) and
%% returns usage metrics in terms of cores and 0..1 percentages.
-spec scheduler_usage_diff(SchedTime, SchedTime) -> undefined | [{SchedulerId, Usage}] when
SchedTime :: [{SchedulerId, ActiveTime, TotalTime}],
SchedulerId :: pos_integer(),
Usage :: number(),
ActiveTime :: non_neg_integer(),
TotalTime :: non_neg_integer().
SchedTime :: [{SchedulerId, ActiveTime, TotalTime}],
SchedulerId :: pos_integer(),
Usage :: number(),
ActiveTime :: non_neg_integer(),
TotalTime :: non_neg_integer().
scheduler_usage_diff(First, Last) when First =:= undefined orelse Last =:= undefined ->
undefined;
undefined;
scheduler_usage_diff(First, Last) ->
lists:map(
fun ({{I, _A0, T}, {I, _A1, T}}) -> {I, 0.0}; % Avoid divide by zero
({{I, A0, T0}, {I, A1, T1}}) -> {I, (A1 - A0)/(T1 - T0)}
end,
lists:zip(lists:sort(First), lists:sort(Last))
).
lists:map(
fun({{I, _A0, T}, {I, _A1, T}}) -> {I, 0.0}; % Avoid divide by zero
({{I, A0, T0}, {I, A1, T1}}) -> {I, (A1 - A0) / (T1 - T0)}
end,
lists:zip(lists:sort(First), lists:sort(Last))
).
%% @doc Returns the top n element of a list of process or inet attributes
-spec sublist_top_n_attrs([Attrs], pos_integer()) -> [Attrs]
when Attrs :: recon:proc_attrs() | recon:inet_attrs().
when Attrs :: recon:proc_attrs() | recon:inet_attrs().
sublist_top_n_attrs(_, 0) ->
%% matching lists:sublist/2 behaviour
[];
%% matching lists:sublist/2 behaviour
[];
sublist_top_n_attrs(List, Len) ->
pheap_fill(List, Len, []).
pheap_fill(List, Len, []).
%% @private crush binaries from process_info into their amount of place
%% taken in memory.
binary_memory(Bins) ->
lists:foldl(fun({_,Mem,_}, Tot) -> Mem+Tot end, 0, Bins).
lists:foldl(fun({_, Mem, _}, Tot) -> Mem + Tot end, 0, Bins).
%%%%%%%%%%%%%%%
%%% PRIVATE %%%
%%%%%%%%%%%%%%%
pheap_fill(List, 0, Heap) ->
pheap_full(List, Heap);
pheap_full(List, Heap);
pheap_fill([], _, Heap) ->
pheap_to_list(Heap, []);
pheap_fill([{Y, X, _} = H|T], N, Heap) ->
pheap_fill(T, N-1, insert({{X, Y}, H}, Heap)).
pheap_to_list(Heap, []);
pheap_fill([{Y, X, _} = H | T], N, Heap) ->
pheap_fill(T, N - 1, insert({{X, Y}, H}, Heap)).
pheap_full([], Heap) ->
pheap_to_list(Heap, []);
pheap_full([{Y, X, _} = H|T], [{K, _}|HeapT] = Heap) ->
case {X, Y} of
N when N > K ->
pheap_full(T, insert({N, H}, merge_pairs(HeapT)));
_ ->
pheap_full(T, Heap)
end.
pheap_to_list(Heap, []);
pheap_full([{Y, X, _} = H | T], [{K, _} | HeapT] = Heap) ->
case {X, Y} of
N when N > K ->
pheap_full(T, insert({N, H}, merge_pairs(HeapT)));
_ ->
pheap_full(T, Heap)
end.
pheap_to_list([], Acc) -> Acc;
pheap_to_list([{_, H}|T], Acc) ->
pheap_to_list(merge_pairs(T), [H|Acc]).
pheap_to_list([{_, H} | T], Acc) ->
pheap_to_list(merge_pairs(T), [H | Acc]).
-compile({inline, [insert/2, merge/2]}).
insert(E, []) -> [E]; %% merge([E], H)
insert(E, [E2|_] = H) when E =< E2 -> [E, H];
insert(E, [E2|H]) -> [E2, [E]|H].
insert(E, [E2 | _] = H) when E =< E2 -> [E, H];
insert(E, [E2 | H]) -> [E2, [E] | H].
merge(H1, []) -> H1;
merge([E1|H1], [E2|_]=H2) when E1 =< E2 -> [E1, H2|H1];
merge(H1, [E2|H2]) -> [E2, H1|H2].
merge([E1 | H1], [E2 | _] = H2) when E1 =< E2 -> [E1, H2 | H1];
merge(H1, [E2 | H2]) -> [E2, H1 | H2].
merge_pairs([]) -> [];
merge_pairs([H]) -> H;
merge_pairs([A, B|T]) -> merge(merge(A, B), merge_pairs(T)).
merge_pairs([A, B | T]) -> merge(merge(A, B), merge_pairs(T)).

+ 98
- 98
src/test/recon-2.5.1/recon_map.erl Wyświetl plik

@ -23,15 +23,15 @@
%% @doc quickly check if we want to do any record formatting
-spec is_active() -> boolean().
is_active() ->
case whereis(recon_ets_maps) of
undefined -> false;
_ -> true
end.
case whereis(recon_ets_maps) of
undefined -> false;
_ -> true
end.
%% @doc remove all imported definitions, destroy the table, clean up
clear() ->
maybe_kill(recon_ets_maps),
ok.
maybe_kill(recon_ets_maps),
ok.
%% @doc Limit output to selected keys of a map (can be 'none', 'all', a key or a list of keys).
%% Pattern selects maps to process: a "pattern" is just a map, and if all key/value pairs of a pattern
@ -44,48 +44,48 @@ clear() ->
%% @end
-spec limit(map_label(), pattern(), limit()) -> ok | {error, any()}.
limit(Label, #{} = Pattern, Limit) when is_atom(Label) ->
store_pattern(Label, Pattern, Limit);
store_pattern(Label, Pattern, Limit);
limit(Label, Pattern, Limit) when is_atom(Label), is_function(Pattern) ->
store_pattern(Label, Pattern, Limit).
store_pattern(Label, Pattern, Limit).
%% @doc prints out all "known" map definitions and their limit settings.
%% Printout tells a map's name, the matching fields required, and the limit options.
%% @end
list() ->
ensure_table_exists(),
io:format("~nmap definitions and limits:~n"),
list(ets:tab2list(patterns_table_name())).
ensure_table_exists(),
io:format("~nmap definitions and limits:~n"),
list(ets:tab2list(patterns_table_name())).
%% @doc remove a given map entry
-spec remove(map_label()) -> true.
remove(Label) ->
ensure_table_exists(),
ets:delete(patterns_table_name(), Label).
ensure_table_exists(),
ets:delete(patterns_table_name(), Label).
%% @doc rename a given map entry, which allows to to change priorities for
%% matching. The first argument is the current name, and the second
%% argument is the new name.
-spec rename(map_label(), map_label()) -> renamed | missing.
rename(Name, NewName) ->
ensure_table_exists(),
case ets:lookup(patterns_table_name(), Name) of
[{Name, Pattern, Limit}] ->
ets:insert(patterns_table_name(), {NewName, Pattern, Limit}),
ets:delete(patterns_table_name(), Name),
renamed;
[] ->
missing
end.
ensure_table_exists(),
case ets:lookup(patterns_table_name(), Name) of
[{Name, Pattern, Limit}] ->
ets:insert(patterns_table_name(), {NewName, Pattern, Limit}),
ets:delete(patterns_table_name(), Name),
renamed;
[] ->
missing
end.
%% @doc prints out all "known" map filter definitions and their settings.
%% Printout tells the map's label, the matching patterns, and the limit options
%% @end
list([]) ->
io:format("~n"),
ok;
io:format("~n"),
ok;
list([{Label, Pattern, Limit} | Rest]) ->
io:format("~p: ~p -> ~p~n", [Label, Pattern, Limit]),
list(Rest).
io:format("~p: ~p -> ~p~n", [Label, Pattern, Limit]),
list(Rest).
%% @private given a map, scans saved patterns for one that matches; if found, returns a label
%% and a map with limits applied; otherwise returns 'none' and original map.
@ -96,48 +96,48 @@ list([{Label, Pattern, Limit} | Rest]) ->
%% </ul>
-spec process_map(map()) -> map() | {atom(), map()}.
process_map(M) ->
process_map(M, ets:tab2list(patterns_table_name())).
process_map(M, ets:tab2list(patterns_table_name())).
process_map(M, []) ->
M;
M;
process_map(M, [{Label, Pattern, Limit} | Rest]) ->
case map_matches(M, Pattern) of
true ->
{Label, apply_map_limits(Limit, M)};
false ->
process_map(M, Rest)
end.
case map_matches(M, Pattern) of
true ->
{Label, apply_map_limits(Limit, M)};
false ->
process_map(M, Rest)
end.
map_matches(#{} = M, Pattern) when is_function(Pattern) ->
Pattern(M);
Pattern(M);
map_matches(_, []) ->
true;
true;
map_matches(M, [{K, V} | Rest]) ->
case maps:is_key(K, M) of
true ->
case maps:get(K, M) of
V ->
map_matches(M, Rest);
_ ->
false
end;
false ->
false
end.
case maps:is_key(K, M) of
true ->
case maps:get(K, M) of
V ->
map_matches(M, Rest);
_ ->
false
end;
false ->
false
end.
apply_map_limits(none, M) ->
M;
M;
apply_map_limits(all, _) ->
#{};
#{};
apply_map_limits(Fields, M) ->
maps:with(Fields, M).
maps:with(Fields, M).
patterns_table_name() -> recon_map_patterns.
store_pattern(Label, Pattern, Limit) ->
ensure_table_exists(),
ets:insert(patterns_table_name(), {Label, prepare_pattern(Pattern), prepare_limit(Limit)}),
ok.
ensure_table_exists(),
ets:insert(patterns_table_name(), {Label, prepare_pattern(Pattern), prepare_limit(Limit)}),
ok.
prepare_limit(all) -> all;
prepare_limit(none) -> none;
@ -150,59 +150,59 @@ prepare_pattern(Pattern) when is_map(Pattern) -> maps:to_list(Pattern).
ensure_table_exists() ->
case ets:info(patterns_table_name()) of
undefined ->
case whereis(recon_ets_maps) of
undefined ->
Parent = self(),
Ref = make_ref(),
%% attach to the currently running session
{Pid, MonRef} = spawn_monitor(fun() ->
register(recon_ets_maps, self()),
ets:new(patterns_table_name(), [ordered_set, public, named_table]),
Parent ! Ref,
ets_keeper()
end),
receive
Ref ->
erlang:demonitor(MonRef, [flush]),
Pid;
{'DOWN', MonRef, _, _, Reason} ->
error(Reason)
end;
Pid ->
Pid
end;
Pid ->
Pid
end.
case ets:info(patterns_table_name()) of
undefined ->
case whereis(recon_ets_maps) of
undefined ->
Parent = self(),
Ref = make_ref(),
%% attach to the currently running session
{Pid, MonRef} = spawn_monitor(fun() ->
register(recon_ets_maps, self()),
ets:new(patterns_table_name(), [ordered_set, public, named_table]),
Parent ! Ref,
ets_keeper()
end),
receive
Ref ->
erlang:demonitor(MonRef, [flush]),
Pid;
{'DOWN', MonRef, _, _, Reason} ->
error(Reason)
end;
Pid ->
Pid
end;
Pid ->
Pid
end.
ets_keeper() ->
receive
stop -> ok;
_ -> ets_keeper()
end.
receive
stop -> ok;
_ -> ets_keeper()
end.
%%%%%%%%%%%%%%%
%%% HELPERS %%%
%%%%%%%%%%%%%%%
maybe_kill(Name) ->
case whereis(Name) of
undefined ->
ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_for_death(Pid, Name)
end.
case whereis(Name) of
undefined ->
ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_for_death(Pid, Name)
end.
wait_for_death(Pid, Name) ->
case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
true ->
timer:sleep(10),
wait_for_death(Pid, Name);
false ->
ok
end.
case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
true ->
timer:sleep(10),
wait_for_death(Pid, Name);
false ->
ok
end.

+ 134
- 134
src/test/recon-2.5.1/recon_rec.erl Wyświetl plik

@ -29,8 +29,8 @@
-type limit() :: all | none | field() | [field()].
-type listentry() :: {module(), record_name(), [field()], limit()}.
-type import_result() :: {imported, module(), record_name(), arity()}
| {overwritten, module(), record_name(), arity()}
| {ignored, module(), record_name(), arity(), module()}.
| {overwritten, module(), record_name(), arity()}
| {ignored, module(), record_name(), arity(), module()}.
%% @doc import record definitions from a module. If a record definition of the same name
%% and arity has already been imported from another module then the new
@ -41,26 +41,26 @@
%% @end
-spec import(module() | [module()]) -> import_result() | [import_result()].
import(Modules) when is_list(Modules) ->
lists:foldl(fun import/2, [], Modules);
lists:foldl(fun import/2, [], Modules);
import(Module) ->
import(Module, []).
import(Module, []).
%% @doc quickly check if we want to do any record formatting
-spec is_active() -> boolean().
is_active() ->
case whereis(recon_ets) of
undefined -> false;
_ -> true
end.
case whereis(recon_ets) of
undefined -> false;
_ -> true
end.
%% @doc remove definitions imported from a module.
clear(Module) ->
lists:map(fun(R) -> rem_for_module(R, Module) end, ets:tab2list(records_table_name())).
lists:map(fun(R) -> rem_for_module(R, Module) end, ets:tab2list(records_table_name())).
%% @doc remove all imported definitions, destroy the table, clean up
clear() ->
maybe_kill(recon_ets),
ok.
maybe_kill(recon_ets),
ok.
%% @doc prints out all "known" (imported) record definitions and their limit settings.
%% Printout tells module a record originates from, its name and a list of field names,
@ -68,21 +68,21 @@ clear() ->
%% limits its output to, if set.
%% @end
list() ->
F = fun({Module, Name, Fields, Limits}) ->
Fnames = lists:map(fun atom_to_list/1, Fields),
Flds = join(",", Fnames),
io:format("~p: #~p(~p){~s} ~p~n",
[Module, Name, length(Fields), Flds, Limits])
end,
io:format("Module: #Name(Size){<Fields>} Limits~n==========~n", []),
lists:foreach(F, get_list()).
F = fun({Module, Name, Fields, Limits}) ->
Fnames = lists:map(fun atom_to_list/1, Fields),
Flds = join(",", Fnames),
io:format("~p: #~p(~p){~s} ~p~n",
[Module, Name, length(Fields), Flds, Limits])
end,
io:format("Module: #Name(Size){<Fields>} Limits~n==========~n", []),
lists:foreach(F, get_list()).
%% @doc returns a list of active record definitions
-spec get_list() -> [listentry()].
get_list() ->
ensure_table_exists(),
Lst = lists:map(fun make_list_entry/1, ets:tab2list(records_table_name())),
lists:sort(Lst).
ensure_table_exists(),
Lst = lists:map(fun make_list_entry/1, ets:tab2list(records_table_name())),
lists:sort(Lst).
%% @doc Limit output to selected fields of a record (can be 'none', 'all', a field or a list of fields).
%% Limit set to 'none' means there is no limit, and all fields are displayed; limit 'all' means that
@ -90,20 +90,20 @@ get_list() ->
%% @end
-spec limit(record_name(), arity(), limit()) -> ok | {error, any()}.
limit(Name, Arity, Limit) when is_atom(Name), is_integer(Arity) ->
case lookup_record(Name, Arity) of
[] ->
{error, record_unknown};
[{Key, Fields, Mod, _}] ->
ets:insert(records_table_name(), {Key, Fields, Mod, Limit}),
ok
end.
case lookup_record(Name, Arity) of
[] ->
{error, record_unknown};
[{Key, Fields, Mod, _}] ->
ets:insert(records_table_name(), {Key, Fields, Mod, Limit}),
ok
end.
%% @private if a tuple is a known record, formats is as "#recname{field=value}", otherwise returns
%% just a printout of a tuple.
format_tuple(Tuple) ->
ensure_table_exists(),
First = element(1, Tuple),
format_tuple(First, Tuple).
ensure_table_exists(),
First = element(1, Tuple),
format_tuple(First, Tuple).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% PRIVATE
@ -111,93 +111,93 @@ format_tuple(Tuple) ->
make_list_entry({{Name, _}, Fields, Module, Limits}) ->
FmtLimit = case Limits of
[] -> none;
Other -> Other
end,
{Module, Name, Fields, FmtLimit}.
FmtLimit = case Limits of
[] -> none;
Other -> Other
end,
{Module, Name, Fields, FmtLimit}.
import(Module, ResultList) ->
ensure_table_exists(),
lists:foldl(fun(Rec, Res) -> store_record(Rec, Module, Res) end,
ResultList,
get_record_defs(Module)).
ensure_table_exists(),
lists:foldl(fun(Rec, Res) -> store_record(Rec, Module, Res) end,
ResultList,
get_record_defs(Module)).
store_record(Rec, Module, ResultList) ->
{Name, Fields} = Rec,
Arity = length(Fields),
Result = case lookup_record(Name, Arity) of
[] ->
ets:insert(records_table_name(), rec_info(Rec, Module)),
{imported, Module, Name, Arity};
[{_, _, Module, _}] ->
ets:insert(records_table_name(), rec_info(Rec, Module)),
{overwritten, Module, Name, Arity};
[{_, _, Mod, _}] ->
{ignored, Module, Name, Arity, Mod}
end,
[Result | ResultList].
{Name, Fields} = Rec,
Arity = length(Fields),
Result = case lookup_record(Name, Arity) of
[] ->
ets:insert(records_table_name(), rec_info(Rec, Module)),
{imported, Module, Name, Arity};
[{_, _, Module, _}] ->
ets:insert(records_table_name(), rec_info(Rec, Module)),
{overwritten, Module, Name, Arity};
[{_, _, Mod, _}] ->
{ignored, Module, Name, Arity, Mod}
end,
[Result | ResultList].
get_record_defs(Module) ->
Path = code:which(Module),
{ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Path, [abstract_code]),
lists:foldl(fun get_record/2, [], AC).
Path = code:which(Module),
{ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Path, [abstract_code]),
lists:foldl(fun get_record/2, [], AC).
get_record({attribute, _, record, Rec}, Acc) -> [Rec | Acc];
get_record(_, Acc) -> Acc.
%% @private
lookup_record(RecName, FieldCount) ->
ensure_table_exists(),
ets:lookup(records_table_name(), {RecName, FieldCount}).
ensure_table_exists(),
ets:lookup(records_table_name(), {RecName, FieldCount}).
%% @private
ensure_table_exists() ->
case ets:info(records_table_name()) of
undefined ->
case whereis(recon_ets) of
undefined ->
Parent = self(),
Ref = make_ref(),
%% attach to the currently running session
{Pid, MonRef} = spawn_monitor(fun() ->
register(recon_ets, self()),
ets:new(records_table_name(), [set, public, named_table]),
Parent ! Ref,
ets_keeper()
end),
receive
Ref ->
erlang:demonitor(MonRef, [flush]),
Pid;
{'DOWN', MonRef, _, _, Reason} ->
error(Reason)
end;
Pid ->
Pid
end;
Pid ->
Pid
end.
case ets:info(records_table_name()) of
undefined ->
case whereis(recon_ets) of
undefined ->
Parent = self(),
Ref = make_ref(),
%% attach to the currently running session
{Pid, MonRef} = spawn_monitor(fun() ->
register(recon_ets, self()),
ets:new(records_table_name(), [set, public, named_table]),
Parent ! Ref,
ets_keeper()
end),
receive
Ref ->
erlang:demonitor(MonRef, [flush]),
Pid;
{'DOWN', MonRef, _, _, Reason} ->
error(Reason)
end;
Pid ->
Pid
end;
Pid ->
Pid
end.
records_table_name() -> recon_record_definitions.
rec_info({Name, Fields}, Module) ->
{{Name, length(Fields)}, field_names(Fields), Module, none}.
{{Name, length(Fields)}, field_names(Fields), Module, none}.
rem_for_module({_, _, Module, _} = Rec, Module) ->
ets:delete_object(records_table_name(), Rec);
ets:delete_object(records_table_name(), Rec);
rem_for_module(_, _) ->
ok.
ok.
ets_keeper() ->
receive
stop -> ok;
_ -> ets_keeper()
end.
receive
stop -> ok;
_ -> ets_keeper()
end.
field_names(Fields) ->
lists:map(fun field_name/1, Fields).
lists:map(fun field_name/1, Fields).
field_name({record_field, _, {atom, _, Name}}) -> Name;
field_name({record_field, _, {atom, _, Name}, _Default}) -> Name;
@ -208,72 +208,72 @@ field_name({typed_record_field, Field, _Type}) -> field_name(Field).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
format_tuple(Name, Rec) when is_atom(Name) ->
case lookup_record(Name, size(Rec) - 1) of
[RecDef] -> format_record(Rec, RecDef);
_ ->
List = tuple_to_list(Rec),
["{", join(", ", [recon_trace:format_trace_output(true, El) || El <- List]), "}"]
end;
case lookup_record(Name, size(Rec) - 1) of
[RecDef] -> format_record(Rec, RecDef);
_ ->
List = tuple_to_list(Rec),
["{", join(", ", [recon_trace:format_trace_output(true, El) || El <- List]), "}"]
end;
format_tuple(_, Tuple) ->
format_default(Tuple).
format_default(Tuple).
format_default(Val) ->
io_lib:format("~p", [Val]).
io_lib:format("~p", [Val]).
format_record(Rec, {{Name, Arity}, Fields, _, Limits}) ->
ExpectedLength = Arity + 1,
case tuple_size(Rec) of
ExpectedLength ->
[_ | Values] = tuple_to_list(Rec),
List = lists:zip(Fields, Values),
LimitedList = apply_limits(List, Limits),
["#", atom_to_list(Name), "{",
join(", ", [format_kv(Key, Val) || {Key, Val} <- LimitedList]),
"}"];
_ ->
format_default(Rec)
end.
ExpectedLength = Arity + 1,
case tuple_size(Rec) of
ExpectedLength ->
[_ | Values] = tuple_to_list(Rec),
List = lists:zip(Fields, Values),
LimitedList = apply_limits(List, Limits),
["#", atom_to_list(Name), "{",
join(", ", [format_kv(Key, Val) || {Key, Val} <- LimitedList]),
"}"];
_ ->
format_default(Rec)
end.
format_kv(Key, Val) ->
%% Some messy mutually recursive calls we can't avoid
[recon_trace:format_trace_output(true, Key), "=", recon_trace:format_trace_output(true, Val)].
%% Some messy mutually recursive calls we can't avoid
[recon_trace:format_trace_output(true, Key), "=", recon_trace:format_trace_output(true, Val)].
apply_limits(List, none) -> List;
apply_limits(_List, all) -> [];
apply_limits(List, Field) when is_atom(Field) ->
[{Field, proplists:get_value(Field, List)}, {more, '...'}];
[{Field, proplists:get_value(Field, List)}, {more, '...'}];
apply_limits(List, Limits) ->
lists:filter(fun({K, _}) -> lists:member(K, Limits) end, List) ++ [{more, '...'}].
lists:filter(fun({K, _}) -> lists:member(K, Limits) end, List) ++ [{more, '...'}].
%%%%%%%%%%%%%%%
%%% HELPERS %%%
%%%%%%%%%%%%%%%
maybe_kill(Name) ->
case whereis(Name) of
undefined ->
ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_for_death(Pid, Name)
end.
case whereis(Name) of
undefined ->
ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_for_death(Pid, Name)
end.
wait_for_death(Pid, Name) ->
case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
true ->
timer:sleep(10),
wait_for_death(Pid, Name);
false ->
ok
end.
case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
true ->
timer:sleep(10),
wait_for_death(Pid, Name);
false ->
ok
end.
-ifdef(OTP_RELEASE).
-spec join(term(), [term()]) -> [term()].
join(Sep, List) ->
lists:join(Sep, List).
lists:join(Sep, List).
-else.
-spec join(string(), [string()]) -> string().
join(Sep, List) ->
string:join(List, Sep).
string:join(List, Sep).
-endif.

+ 312
- 312
src/test/recon-2.5.1/recon_trace.erl Wyświetl plik

@ -187,34 +187,34 @@
%% Internal exports
-export([count_tracer/1, rate_tracer/2, formatter/5, format_trace_output/1, format_trace_output/2]).
-type matchspec() :: [{[term()] | '_', [term()], [term()]}].
-type shellfun() :: fun((_) -> term()).
-type matchspec() :: [{[term()] | '_', [term()], [term()]}].
-type shellfun() :: fun((_) -> term()).
-type formatterfun() :: fun((_) -> iodata()).
-type millisecs() :: non_neg_integer().
-type pidspec() :: all | existing | new | recon:pid_term().
-type max_traces() :: non_neg_integer().
-type max_rate() :: {max_traces(), millisecs()}.
%% trace options
-type options() :: [ {pid, pidspec() | [pidspec(),...]} % default: all
| {timestamp, formatter | trace} % default: formatter
| {args, args | arity} % default: args
| {io_server, pid()} % default: group_leader()
| {formatter, formatterfun()} % default: internal formatter
| return_to | {return_to, boolean()} % default: false
%% match pattern options
| {scope, global | local} % default: global
].
-type mod() :: '_' | module().
-type fn() :: '_' | atom().
-type args() :: '_' | 0..255 | return_trace | matchspec() | shellfun().
-type tspec() :: {mod(), fn(), args()}.
-type max() :: max_traces() | max_rate().
-type num_matches() :: non_neg_integer().
-type millisecs() :: non_neg_integer().
-type pidspec() :: all | existing | new | recon:pid_term().
-type max_traces() :: non_neg_integer().
-type max_rate() :: {max_traces(), millisecs()}.
%% trace options
-type options() :: [{pid, pidspec() | [pidspec(), ...]} % default: all
| {timestamp, formatter | trace} % default: formatter
| {args, args | arity} % default: args
| {io_server, pid()} % default: group_leader()
| {formatter, formatterfun()} % default: internal formatter
| return_to | {return_to, boolean()} % default: false
%% match pattern options
| {scope, global | local} % default: global
].
-type mod() :: '_' | module().
-type fn() :: '_' | atom().
-type args() :: '_' | 0..255 | return_trace | matchspec() | shellfun().
-type tspec() :: {mod(), fn(), args()}.
-type max() :: max_traces() | max_rate().
-type num_matches() :: non_neg_integer().
-export_type([mod/0, fn/0, args/0, tspec/0, num_matches/0, options/0,
max_traces/0, max_rate/0]).
max_traces/0, max_rate/0]).
%%%%%%%%%%%%%%
%%% PUBLIC %%%
@ -223,19 +223,19 @@
%% @doc Stops all tracing at once.
-spec clear() -> ok.
clear() ->
erlang:trace(all, false, [all]),
erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_count,call_time]),
erlang:trace_pattern({'_','_','_'}, false, []), % unsets global
maybe_kill(recon_trace_tracer),
maybe_kill(recon_trace_formatter),
ok.
erlang:trace(all, false, [all]),
erlang:trace_pattern({'_', '_', '_'}, false, [local, meta, call_count, call_time]),
erlang:trace_pattern({'_', '_', '_'}, false, []), % unsets global
maybe_kill(recon_trace_tracer),
maybe_kill(recon_trace_formatter),
ok.
%% @equiv calls({Mod, Fun, Args}, Max, [])
-spec calls(tspec() | [tspec(),...], max()) -> num_matches().
-spec calls(tspec() | [tspec(), ...], max()) -> num_matches().
calls({Mod, Fun, Args}, Max) ->
calls([{Mod,Fun,Args}], Max, []);
calls(TSpecs = [_|_], Max) ->
calls(TSpecs, Max, []).
calls([{Mod, Fun, Args}], Max, []);
calls(TSpecs = [_ | _], Max) ->
calls(TSpecs, Max, []).
%% @doc Allows to set trace patterns and pid specifications to trace
%% function calls.
@ -332,31 +332,31 @@ calls(TSpecs = [_|_], Max) ->
%% can be risky if more trace messages are generated than any process on
%% the node could ever handle, despite the precautions taken by this library.
%% @end
-spec calls(tspec() | [tspec(),...], max(), options()) -> num_matches().
-spec calls(tspec() | [tspec(), ...], max(), options()) -> num_matches().
calls({Mod, Fun, Args}, Max, Opts) ->
calls([{Mod,Fun,Args}], Max, Opts);
calls(TSpecs = [_|_], {Max, Time}, Opts) ->
Pid = setup(rate_tracer, [Max, Time],
validate_formatter(Opts), validate_io_server(Opts)),
trace_calls(TSpecs, Pid, Opts);
calls(TSpecs = [_|_], Max, Opts) ->
Pid = setup(count_tracer, [Max],
validate_formatter(Opts), validate_io_server(Opts)),
trace_calls(TSpecs, Pid, Opts).
calls([{Mod, Fun, Args}], Max, Opts);
calls(TSpecs = [_ | _], {Max, Time}, Opts) ->
Pid = setup(rate_tracer, [Max, Time],
validate_formatter(Opts), validate_io_server(Opts)),
trace_calls(TSpecs, Pid, Opts);
calls(TSpecs = [_ | _], Max, Opts) ->
Pid = setup(count_tracer, [Max],
validate_formatter(Opts), validate_io_server(Opts)),
trace_calls(TSpecs, Pid, Opts).
%%%%%%%%%%%%%%%%%%%%%%%
%%% PRIVATE EXPORTS %%%
%%%%%%%%%%%%%%%%%%%%%%%
%% @private Stops when N trace messages have been received
count_tracer(0) ->
exit(normal);
exit(normal);
count_tracer(N) ->
receive
Msg ->
recon_trace_formatter ! Msg,
count_tracer(N-1)
end.
receive
Msg ->
recon_trace_formatter ! Msg,
count_tracer(N - 1)
end.
%% @private Stops whenever the trace message rates goes higher than
%% `Max' messages in `Time' milliseconds. Note that if the rate
@ -367,35 +367,35 @@ count_tracer(N) ->
rate_tracer(Max, Time) -> rate_tracer(Max, Time, 0, os:timestamp()).
rate_tracer(Max, Time, Count, Start) ->
receive
Msg ->
recon_trace_formatter ! Msg,
Now = os:timestamp(),
Delay = timer:now_diff(Now, Start) div 1000,
if Delay > Time -> rate_tracer(Max, Time, 0, Now)
; Max > Count -> rate_tracer(Max, Time, Count+1, Start)
; Max =:= Count -> exit(normal)
end
end.
receive
Msg ->
recon_trace_formatter ! Msg,
Now = os:timestamp(),
Delay = timer:now_diff(Now, Start) div 1000,
if Delay > Time -> rate_tracer(Max, Time, 0, Now)
; Max > Count -> rate_tracer(Max, Time, Count + 1, Start)
; Max =:= Count -> exit(normal)
end
end.
%% @private Formats traces to be output
formatter(Tracer, Parent, Ref, FormatterFun, IOServer) ->
process_flag(trap_exit, true),
link(Tracer),
Parent ! {Ref, linked},
formatter(Tracer, IOServer, FormatterFun).
process_flag(trap_exit, true),
link(Tracer),
Parent ! {Ref, linked},
formatter(Tracer, IOServer, FormatterFun).
formatter(Tracer, IOServer, FormatterFun) ->
receive
{'EXIT', Tracer, normal} ->
io:format("Recon tracer rate limit tripped.~n"),
exit(normal);
{'EXIT', Tracer, Reason} ->
exit(Reason);
TraceMsg ->
io:format(IOServer, FormatterFun(TraceMsg), []),
formatter(Tracer, IOServer, FormatterFun)
end.
receive
{'EXIT', Tracer, normal} ->
io:format("Recon tracer rate limit tripped.~n"),
exit(normal);
{'EXIT', Tracer, Reason} ->
exit(Reason);
TraceMsg ->
io:format(IOServer, FormatterFun(TraceMsg), []),
formatter(Tracer, IOServer, FormatterFun)
end.
%%%%%%%%%%%%%%%%%%%%%%%
@ -405,28 +405,28 @@ formatter(Tracer, IOServer, FormatterFun) ->
%% starts the tracer and formatter processes, and
%% cleans them up before each call.
setup(TracerFun, TracerArgs, FormatterFun, IOServer) ->
clear(),
Ref = make_ref(),
Tracer = spawn_link(?MODULE, TracerFun, TracerArgs),
register(recon_trace_tracer, Tracer),
Format = spawn(?MODULE, formatter, [Tracer, self(), Ref, FormatterFun, IOServer]),
register(recon_trace_formatter, Format),
receive
{Ref, linked} -> Tracer
after 5000 ->
error(setup_failed)
end.
clear(),
Ref = make_ref(),
Tracer = spawn_link(?MODULE, TracerFun, TracerArgs),
register(recon_trace_tracer, Tracer),
Format = spawn(?MODULE, formatter, [Tracer, self(), Ref, FormatterFun, IOServer]),
register(recon_trace_formatter, Format),
receive
{Ref, linked} -> Tracer
after 5000 ->
error(setup_failed)
end.
%% Sets the traces in action
trace_calls(TSpecs, Pid, Opts) ->
{PidSpecs, TraceOpts, MatchOpts} = validate_opts(Opts),
Matches = [begin
{Arity, Spec} = validate_tspec(Mod, Fun, Args),
erlang:trace_pattern({Mod, Fun, Arity}, Spec, MatchOpts)
end || {Mod, Fun, Args} <- TSpecs],
[erlang:trace(PidSpec, true, [call, {tracer, Pid} | TraceOpts])
|| PidSpec <- PidSpecs],
lists:sum(Matches).
{PidSpecs, TraceOpts, MatchOpts} = validate_opts(Opts),
Matches = [begin
{Arity, Spec} = validate_tspec(Mod, Fun, Args),
erlang:trace_pattern({Mod, Fun, Arity}, Spec, MatchOpts)
end || {Mod, Fun, Args} <- TSpecs],
[erlang:trace(PidSpec, true, [call, {tracer, Pid} | TraceOpts])
|| PidSpec <- PidSpecs],
lists:sum(Matches).
%%%%%%%%%%%%%%%%%%
@ -434,251 +434,251 @@ trace_calls(TSpecs, Pid, Opts) ->
%%%%%%%%%%%%%%%%%%
validate_opts(Opts) ->
PidSpecs = validate_pid_specs(proplists:get_value(pid, Opts, all)),
Scope = proplists:get_value(scope, Opts, global),
TraceOpts = case proplists:get_value(timestamp, Opts, formatter) of
formatter -> [];
trace -> [timestamp]
end ++
case proplists:get_value(args, Opts, args) of
args -> [];
arity -> [arity]
end ++
case proplists:get_value(return_to, Opts, undefined) of
true when Scope =:= local ->
[return_to];
true when Scope =:= global ->
io:format("Option return_to only works with option {scope, local}~n"),
%% Set it anyway
[return_to];
_ ->
[]
end,
MatchOpts = [Scope],
{PidSpecs, TraceOpts, MatchOpts}.
PidSpecs = validate_pid_specs(proplists:get_value(pid, Opts, all)),
Scope = proplists:get_value(scope, Opts, global),
TraceOpts = case proplists:get_value(timestamp, Opts, formatter) of
formatter -> [];
trace -> [timestamp]
end ++
case proplists:get_value(args, Opts, args) of
args -> [];
arity -> [arity]
end ++
case proplists:get_value(return_to, Opts, undefined) of
true when Scope =:= local ->
[return_to];
true when Scope =:= global ->
io:format("Option return_to only works with option {scope, local}~n"),
%% Set it anyway
[return_to];
_ ->
[]
end,
MatchOpts = [Scope],
{PidSpecs, TraceOpts, MatchOpts}.
%% Support the regular specs, but also allow `recon:pid_term()' and lists
%% of further pid specs.
-spec validate_pid_specs(pidspec() | [pidspec(),...]) ->
[all | new | existing | pid(), ...].
-spec validate_pid_specs(pidspec() | [pidspec(), ...]) ->
[all | new | existing | pid(), ...].
validate_pid_specs(all) -> [all];
validate_pid_specs(existing) -> [existing];
validate_pid_specs(new) -> [new];
validate_pid_specs([Spec]) -> validate_pid_specs(Spec);
validate_pid_specs(PidTerm = [Spec|Rest]) ->
%% can be "<a.b.c>" or [pidspec()]
try
[recon_lib:term_to_pid(PidTerm)]
catch
error:function_clause ->
validate_pid_specs(Spec) ++ validate_pid_specs(Rest)
end;
validate_pid_specs(PidTerm = [Spec | Rest]) ->
%% can be "<a.b.c>" or [pidspec()]
try
[recon_lib:term_to_pid(PidTerm)]
catch
error:function_clause ->
validate_pid_specs(Spec) ++ validate_pid_specs(Rest)
end;
validate_pid_specs(PidTerm) ->
%% has to be `recon:pid_term()'.
[recon_lib:term_to_pid(PidTerm)].
%% has to be `recon:pid_term()'.
[recon_lib:term_to_pid(PidTerm)].
validate_tspec(Mod, Fun, Args) when is_function(Args) ->
validate_tspec(Mod, Fun, fun_to_ms(Args));
validate_tspec(Mod, Fun, fun_to_ms(Args));
%% helper to save typing for common actions
validate_tspec(Mod, Fun, return_trace) ->
validate_tspec(Mod, Fun, [{'_', [], [{return_trace}]}]);
validate_tspec(Mod, Fun, [{'_', [], [{return_trace}]}]);
validate_tspec(Mod, Fun, Args) ->
BannedMods = ['_', ?MODULE, io, lists],
%% The banned mod check can be bypassed by using
%% match specs if you really feel like being dumb.
case {lists:member(Mod, BannedMods), Args} of
{true, '_'} -> error({dangerous_combo, {Mod,Fun,Args}});
{true, []} -> error({dangerous_combo, {Mod,Fun,Args}});
_ -> ok
end,
case Args of
'_' -> {'_', true};
_ when is_list(Args) -> {'_', Args};
_ when Args >= 0, Args =< 255 -> {Args, true}
end.
BannedMods = ['_', ?MODULE, io, lists],
%% The banned mod check can be bypassed by using
%% match specs if you really feel like being dumb.
case {lists:member(Mod, BannedMods), Args} of
{true, '_'} -> error({dangerous_combo, {Mod, Fun, Args}});
{true, []} -> error({dangerous_combo, {Mod, Fun, Args}});
_ -> ok
end,
case Args of
'_' -> {'_', true};
_ when is_list(Args) -> {'_', Args};
_ when Args >= 0, Args =< 255 -> {Args, true}
end.
validate_formatter(Opts) ->
case proplists:get_value(formatter, Opts) of
F when is_function(F, 1) -> F;
_ -> fun format/1
end.
case proplists:get_value(formatter, Opts) of
F when is_function(F, 1) -> F;
_ -> fun format/1
end.
validate_io_server(Opts) ->
proplists:get_value(io_server, Opts, group_leader()).
proplists:get_value(io_server, Opts, group_leader()).
%%%%%%%%%%%%%%%%%%%%%%%%
%%% TRACE FORMATTING %%%
%%%%%%%%%%%%%%%%%%%%%%%%
%% Thanks Geoff Cant for the foundations for this.
format(TraceMsg) ->
{Type, Pid, {Hour,Min,Sec}, TraceInfo} = extract_info(TraceMsg),
{FormatStr, FormatArgs} = case {Type, TraceInfo} of
%% {trace, Pid, 'receive', Msg}
{'receive', [Msg]} ->
{"< ~p", [Msg]};
%% {trace, Pid, send, Msg, To}
{send, [Msg, To]} ->
{" > ~p: ~p", [To, Msg]};
%% {trace, Pid, send_to_non_existing_process, Msg, To}
{send_to_non_existing_process, [Msg, To]} ->
{" > (non_existent) ~p: ~p", [To, Msg]};
%% {trace, Pid, call, {M, F, Args}}
{call, [{M,F,Args}]} ->
{"~p:~p~s", [M,F,format_args(Args)]};
%% {trace, Pid, call, {M, F, Args}, Msg}
{call, [{M,F,Args}, Msg]} ->
{"~p:~p~s ~s", [M,F,format_args(Args), format_trace_output(Msg)]};
%% {trace, Pid, return_to, {M, F, Arity}}
{return_to, [{M,F,Arity}]} ->
{" '--> ~p:~p/~p", [M,F,Arity]};
%% {trace, Pid, return_from, {M, F, Arity}, ReturnValue}
{return_from, [{M,F,Arity}, Return]} ->
{"~p:~p/~p --> ~s", [M,F,Arity, format_trace_output(Return)]};
%% {trace, Pid, exception_from, {M, F, Arity}, {Class, Value}}
{exception_from, [{M,F,Arity}, {Class,Val}]} ->
{"~p:~p/~p ~p ~p", [M,F,Arity, Class, Val]};
%% {trace, Pid, spawn, Spawned, {M, F, Args}}
{spawn, [Spawned, {M,F,Args}]} ->
{"spawned ~p as ~p:~p~s", [Spawned, M, F, format_args(Args)]};
%% {trace, Pid, exit, Reason}
{exit, [Reason]} ->
{"EXIT ~p", [Reason]};
%% {trace, Pid, link, Pid2}
{link, [Linked]} ->
{"link(~p)", [Linked]};
%% {trace, Pid, unlink, Pid2}
{unlink, [Linked]} ->
{"unlink(~p)", [Linked]};
%% {trace, Pid, getting_linked, Pid2}
{getting_linked, [Linker]} ->
{"getting linked by ~p", [Linker]};
%% {trace, Pid, getting_unlinked, Pid2}
{getting_unlinked, [Unlinker]} ->
{"getting unlinked by ~p", [Unlinker]};
%% {trace, Pid, register, RegName}
{register, [Name]} ->
{"registered as ~p", [Name]};
%% {trace, Pid, unregister, RegName}
{unregister, [Name]} ->
{"no longer registered as ~p", [Name]};
%% {trace, Pid, in, {M, F, Arity} | 0}
{in, [{M,F,Arity}]} ->
{"scheduled in for ~p:~p/~p", [M,F,Arity]};
{in, [0]} ->
{"scheduled in", []};
%% {trace, Pid, out, {M, F, Arity} | 0}
{out, [{M,F,Arity}]} ->
{"scheduled out from ~p:~p/~p", [M, F, Arity]};
{out, [0]} ->
{"scheduled out", []};
%% {trace, Pid, gc_start, Info}
{gc_start, [Info]} ->
HeapSize = proplists:get_value(heap_size, Info),
OldHeapSize = proplists:get_value(old_heap_size, Info),
MbufSize = proplists:get_value(mbuf_size, Info),
{"gc beginning -- heap ~p bytes",
[HeapSize + OldHeapSize + MbufSize]};
%% {trace, Pid, gc_end, Info}
{gc_end, [Info]} ->
HeapSize = proplists:get_value(heap_size, Info),
OldHeapSize = proplists:get_value(old_heap_size, Info),
MbufSize = proplists:get_value(mbuf_size, Info),
{"gc finished -- heap ~p bytes",
[HeapSize + OldHeapSize + MbufSize]};
_ ->
{"unknown trace type ~p -- ~p", [Type, TraceInfo]}
end,
io_lib:format("~n~p:~p:~9.6.0f ~p " ++ FormatStr ++ "~n",
[Hour, Min, Sec, Pid] ++ FormatArgs).
{Type, Pid, {Hour, Min, Sec}, TraceInfo} = extract_info(TraceMsg),
{FormatStr, FormatArgs} = case {Type, TraceInfo} of
%% {trace, Pid, 'receive', Msg}
{'receive', [Msg]} ->
{"< ~p", [Msg]};
%% {trace, Pid, send, Msg, To}
{send, [Msg, To]} ->
{" > ~p: ~p", [To, Msg]};
%% {trace, Pid, send_to_non_existing_process, Msg, To}
{send_to_non_existing_process, [Msg, To]} ->
{" > (non_existent) ~p: ~p", [To, Msg]};
%% {trace, Pid, call, {M, F, Args}}
{call, [{M, F, Args}]} ->
{"~p:~p~s", [M, F, format_args(Args)]};
%% {trace, Pid, call, {M, F, Args}, Msg}
{call, [{M, F, Args}, Msg]} ->
{"~p:~p~s ~s", [M, F, format_args(Args), format_trace_output(Msg)]};
%% {trace, Pid, return_to, {M, F, Arity}}
{return_to, [{M, F, Arity}]} ->
{" '--> ~p:~p/~p", [M, F, Arity]};
%% {trace, Pid, return_from, {M, F, Arity}, ReturnValue}
{return_from, [{M, F, Arity}, Return]} ->
{"~p:~p/~p --> ~s", [M, F, Arity, format_trace_output(Return)]};
%% {trace, Pid, exception_from, {M, F, Arity}, {Class, Value}}
{exception_from, [{M, F, Arity}, {Class, Val}]} ->
{"~p:~p/~p ~p ~p", [M, F, Arity, Class, Val]};
%% {trace, Pid, spawn, Spawned, {M, F, Args}}
{spawn, [Spawned, {M, F, Args}]} ->
{"spawned ~p as ~p:~p~s", [Spawned, M, F, format_args(Args)]};
%% {trace, Pid, exit, Reason}
{exit, [Reason]} ->
{"EXIT ~p", [Reason]};
%% {trace, Pid, link, Pid2}
{link, [Linked]} ->
{"link(~p)", [Linked]};
%% {trace, Pid, unlink, Pid2}
{unlink, [Linked]} ->
{"unlink(~p)", [Linked]};
%% {trace, Pid, getting_linked, Pid2}
{getting_linked, [Linker]} ->
{"getting linked by ~p", [Linker]};
%% {trace, Pid, getting_unlinked, Pid2}
{getting_unlinked, [Unlinker]} ->
{"getting unlinked by ~p", [Unlinker]};
%% {trace, Pid, register, RegName}
{register, [Name]} ->
{"registered as ~p", [Name]};
%% {trace, Pid, unregister, RegName}
{unregister, [Name]} ->
{"no longer registered as ~p", [Name]};
%% {trace, Pid, in, {M, F, Arity} | 0}
{in, [{M, F, Arity}]} ->
{"scheduled in for ~p:~p/~p", [M, F, Arity]};
{in, [0]} ->
{"scheduled in", []};
%% {trace, Pid, out, {M, F, Arity} | 0}
{out, [{M, F, Arity}]} ->
{"scheduled out from ~p:~p/~p", [M, F, Arity]};
{out, [0]} ->
{"scheduled out", []};
%% {trace, Pid, gc_start, Info}
{gc_start, [Info]} ->
HeapSize = proplists:get_value(heap_size, Info),
OldHeapSize = proplists:get_value(old_heap_size, Info),
MbufSize = proplists:get_value(mbuf_size, Info),
{"gc beginning -- heap ~p bytes",
[HeapSize + OldHeapSize + MbufSize]};
%% {trace, Pid, gc_end, Info}
{gc_end, [Info]} ->
HeapSize = proplists:get_value(heap_size, Info),
OldHeapSize = proplists:get_value(old_heap_size, Info),
MbufSize = proplists:get_value(mbuf_size, Info),
{"gc finished -- heap ~p bytes",
[HeapSize + OldHeapSize + MbufSize]};
_ ->
{"unknown trace type ~p -- ~p", [Type, TraceInfo]}
end,
io_lib:format("~n~p:~p:~9.6.0f ~p " ++ FormatStr ++ "~n",
[Hour, Min, Sec, Pid] ++ FormatArgs).
extract_info(TraceMsg) ->
case tuple_to_list(TraceMsg) of
[trace_ts, Pid, Type | Info] ->
{TraceInfo, [Timestamp]} = lists:split(length(Info)-1, Info),
{Type, Pid, to_hms(Timestamp), TraceInfo};
[trace, Pid, Type | TraceInfo] ->
{Type, Pid, to_hms(os:timestamp()), TraceInfo}
end.
case tuple_to_list(TraceMsg) of
[trace_ts, Pid, Type | Info] ->
{TraceInfo, [Timestamp]} = lists:split(length(Info) - 1, Info),
{Type, Pid, to_hms(Timestamp), TraceInfo};
[trace, Pid, Type | TraceInfo] ->
{Type, Pid, to_hms(os:timestamp()), TraceInfo}
end.
to_hms(Stamp = {_, _, Micro}) ->
{_,{H, M, Secs}} = calendar:now_to_local_time(Stamp),
Seconds = Secs rem 60 + (Micro / 1000000),
{H,M,Seconds};
{_, {H, M, Secs}} = calendar:now_to_local_time(Stamp),
Seconds = Secs rem 60 + (Micro / 1000000),
{H, M, Seconds};
to_hms(_) ->
{0,0,0}.
{0, 0, 0}.
format_args(Arity) when is_integer(Arity) ->
[$/, integer_to_list(Arity)];
[$/, integer_to_list(Arity)];
format_args(Args) when is_list(Args) ->
[$(, join(", ", [format_trace_output(Arg) || Arg <- Args]), $)].
[$(, join(", ", [format_trace_output(Arg) || Arg <- Args]), $)].
%% @doc formats call arguments and return values - most types are just printed out, except for
%% tuples recognised as records, which mimic the source code syntax
%% @end
format_trace_output(Args) ->
format_trace_output(recon_rec:is_active(), recon_map:is_active(), Args).
format_trace_output(recon_rec:is_active(), recon_map:is_active(), Args).
format_trace_output(Recs, Args) ->
format_trace_output(Recs, recon_map:is_active(), Args).
format_trace_output(Recs, recon_map:is_active(), Args).
format_trace_output(true, _, Args) when is_tuple(Args) ->
recon_rec:format_tuple(Args);
recon_rec:format_tuple(Args);
format_trace_output(false, true, Args) when is_tuple(Args) ->
format_tuple(false, true, Args);
format_tuple(false, true, Args);
format_trace_output(Recs, Maps, Args) when is_list(Args), Recs orelse Maps ->
case io_lib:printable_list(Args) of
true ->
io_lib:format("~p", [Args]);
false ->
format_maybe_improper_list(Recs, Maps, Args)
end;
case io_lib:printable_list(Args) of
true ->
io_lib:format("~p", [Args]);
false ->
format_maybe_improper_list(Recs, Maps, Args)
end;
format_trace_output(Recs, true, Args) when is_map(Args) ->
{Label, Map} = case recon_map:process_map(Args) of
{L, M} -> {atom_to_list(L), M};
M -> {"", M}
end,
ItemList = maps:to_list(Map),
[Label,
"#{",
join(", ", [format_kv(Recs, true, Key, Val) || {Key, Val} <- ItemList]),
"}"];
{Label, Map} = case recon_map:process_map(Args) of
{L, M} -> {atom_to_list(L), M};
M -> {"", M}
end,
ItemList = maps:to_list(Map),
[Label,
"#{",
join(", ", [format_kv(Recs, true, Key, Val) || {Key, Val} <- ItemList]),
"}"];
format_trace_output(Recs, false, Args) when is_map(Args) ->
ItemList = maps:to_list(Args),
["#{",
join(", ", [format_kv(Recs, false, Key, Val) || {Key, Val} <- ItemList]),
"}"];
ItemList = maps:to_list(Args),
["#{",
join(", ", [format_kv(Recs, false, Key, Val) || {Key, Val} <- ItemList]),
"}"];
format_trace_output(_, _, Args) ->
io_lib:format("~p", [Args]).
io_lib:format("~p", [Args]).
format_kv(Recs, Maps, Key, Val) ->
[format_trace_output(Recs, Maps, Key), "=>", format_trace_output(Recs, Maps, Val)].
[format_trace_output(Recs, Maps, Key), "=>", format_trace_output(Recs, Maps, Val)].
format_tuple(Recs, Maps, Tup) ->
[${ | format_tuple_(Recs, Maps, tuple_to_list(Tup))].
[${ | format_tuple_(Recs, Maps, tuple_to_list(Tup))].
format_tuple_(_Recs, _Maps, []) ->
"}";
format_tuple_(Recs, Maps, [H|T]) ->
[format_trace_output(Recs, Maps, H), $,,
format_tuple_(Recs, Maps, T)].
"}";
format_tuple_(Recs, Maps, [H | T]) ->
[format_trace_output(Recs, Maps, H), $,,
format_tuple_(Recs, Maps, T)].
format_maybe_improper_list(Recs, Maps, List) ->
[$[ | format_maybe_improper_list_(Recs, Maps, List)].
[$[ | format_maybe_improper_list_(Recs, Maps, List)].
format_maybe_improper_list_(_, _, []) ->
"]";
format_maybe_improper_list_(Recs, Maps, [H|[]]) ->
[format_trace_output(Recs, Maps, H), $]];
format_maybe_improper_list_(Recs, Maps, [H|T]) when is_list(T) ->
[format_trace_output(Recs, Maps, H), $,,
format_maybe_improper_list_(Recs, Maps, T)];
format_maybe_improper_list_(Recs, Maps, [H|T]) when not is_list(T) ->
%% Handling improper lists
[format_trace_output(Recs, Maps, H), $|,
format_trace_output(Recs, Maps, T), $]].
"]";
format_maybe_improper_list_(Recs, Maps, [H | []]) ->
[format_trace_output(Recs, Maps, H), $]];
format_maybe_improper_list_(Recs, Maps, [H | T]) when is_list(T) ->
[format_trace_output(Recs, Maps, H), $,,
format_maybe_improper_list_(Recs, Maps, T)];
format_maybe_improper_list_(Recs, Maps, [H | T]) when not is_list(T) ->
%% Handling improper lists
[format_trace_output(Recs, Maps, H), $|,
format_trace_output(Recs, Maps, T), $]].
%%%%%%%%%%%%%%%
@ -686,48 +686,48 @@ format_maybe_improper_list_(Recs, Maps, [H|T]) when not is_list(T) ->
%%%%%%%%%%%%%%%
maybe_kill(Name) ->
case whereis(Name) of
undefined ->
ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_for_death(Pid, Name)
end.
case whereis(Name) of
undefined ->
ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_for_death(Pid, Name)
end.
wait_for_death(Pid, Name) ->
case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
true ->
timer:sleep(10),
wait_for_death(Pid, Name);
false ->
ok
end.
case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
true ->
timer:sleep(10),
wait_for_death(Pid, Name);
false ->
ok
end.
%% Borrowed from dbg
fun_to_ms(ShellFun) when is_function(ShellFun) ->
case erl_eval:fun_data(ShellFun) of
{fun_data,ImportList,Clauses} ->
case ms_transform:transform_from_shell(
dbg,Clauses,ImportList) of
{error,[{_,[{_,_,Code}|_]}|_],_} ->
io:format("Error: ~s~n",
[ms_transform:format_error(Code)]),
{error,transform_error};
Else ->
Else
end;
false ->
exit(shell_funs_only)
end.
case erl_eval:fun_data(ShellFun) of
{fun_data, ImportList, Clauses} ->
case ms_transform:transform_from_shell(
dbg, Clauses, ImportList) of
{error, [{_, [{_, _, Code} | _]} | _], _} ->
io:format("Error: ~s~n",
[ms_transform:format_error(Code)]),
{error, transform_error};
Else ->
Else
end;
false ->
exit(shell_funs_only)
end.
-ifdef(OTP_RELEASE).
-spec join(term(), [term()]) -> [term()].
join(Sep, List) ->
lists:join(Sep, List).
lists:join(Sep, List).
-else.
-spec join(string(), [string()]) -> string().
join(Sep, List) ->
string:join(List, Sep).
string:join(List, Sep).
-endif.

+ 16
- 16
src/test/recon-2.5.1/test.erl Wyświetl plik

@ -43,7 +43,7 @@ memInfoPrint(CurModule, CurLine, Threshold) ->
end.
get_test() ->
#{'$map_tab' => tb_client,black_hole => 6,bomb_drill => 7,cat_coin => 680,
#{'$map_tab' => tb_client, black_hole => 6, bomb_drill => 7, cat_coin => 680,
client_data => <<"{\"db_base\":{\"uid\":1006,\"talking_scene_info\":[],\"talking_data\":[]},\"db_item_list\":[[\"item_uid\",\"item_tid\",\"item_num\"],[1,1001,1006,4],[2,1002,1007,4],[3,1003,1008,4],[4,1004,1015,4],[5,1005,1016,4],[6,1006,1017,4]],\"db_inland_idip\":{\"coin_before_count\":0,\"coin_after_count\":0,\"coin_freeze_info\":0,\"coin_opt_id\":0,\"band_join_ranking_info\":[]},\"db_cat_feed\":{\"money_time\":0,\"shovel\":0,\"day_index\":0,\"accum_food_time\":0,\"money_list\":[],\"clean_frd_time\":[],\"accum_water_time\":0,\"accum_money\":0,\"water_time\":0,\"remain_water\":0,\"remain_money\":0,\"food_time\":0,\"remain_food\":0,\"first_add_money_time\":0,\"used_shovel\":0,\"last_add_day\":0},\"db_store_ad\":{\"watched_count\":0,\"next_ad_time\":0,\"today_cookie\":\"\"},\"db_level\":{\"level_item_list\":[],\"eng_infinite_tag\":0,\"flag_lv\":0,\"lv_type\":0,\"level_buy_time\":0,\"continue_cost\":0,\"activity_form\":\"\",\"lv_failn_advice\":0,\"level_state\":0,\"set_result\":0,\"use_item_list\":[],\"lv_end_gold\":false,\"lv_failed_count\":[]},\"db_data_verify\":{\"saved_index\":0,\"generate_index\":0,\"server_data_version_code\":0,\"upload_data_time\":0},\"db_timeline_stuff_style\":[],\"db_cat_map\":[],\"db_finished_activity\":[],\"db_monthcard\":{\"have_got\":false,\"day_left_os_time\":0,\"week_day\":0,\"disc_num\":0,\"card_day\":0,\"left_sec\":0,\"card_id\":0,\"day_left_time\":0,\"tag\":0,\"left_os_time\":0,\"show_monthcard_cell\":[]},\"db_logger\":{\"online_sec\":0,\"coin_cost\":0,\"use_doublerocket\":0,\"use_crosshammer\":0,\"use_bombanddril\":0,\"seed\":0,\"day_idx\":0,\"log\":0,\"use_glove\":0,\"gold_cost\":0,\"use_hammer\":0,\"use_blackhole\":0,\"day_num\":0,\"pay_num\":0,\"reshuffle\":0},\"db_role\":{\"order_id_list\":[],\"dr_infinite_left_sec\":0,\"lv_tag\":1,\"first_tlv\":true,\"login_time\":1608963650,\"energy\":5,\"lv_idx\":1,\"gold\":1000,\"login_day_count\":0,\"have_sign\":[],\"chapter\":1,\"energy_time\":0,\"spend_money\":0,\"gold_re\":0,\"bh_infinite_left_sec\":0,\"main_decorate_stuff\":0,\"server_mail_id\":0,\"token\":0,\"energy_recover_halve\":0,\"self_room_stars\":0,\"ad_remaining_num\":0,\"level\":1,\"eneryg_infinite_left_times\":0,\"bd_infinite_left_sec\":0,\"max_product_id\":0,\"play_time_sec\":0,\"last_login_day_index\":0,\"dr_infinite\":0,\"star\":0,\"bh_infinite\":0,\"cat_coin\":0,\"newplayer_signin_complete\":0,\"server_day_idx\":0,\"fb_first_award\":false,\"logout_time\":1608963650,\"special_point\":0,\"tutorial_on\":true,\"diamond\":0,\"energy_infinite\":0,\"role_name\":\"\",\"bd_infinite\":0},\"db_cat_room_map\":[],\"db_chapter_map\":[[\"chapter\",\"begin\",\"award_idx\",\"stuffs\"],{1:1,2:1,3:false,4:0,5:[]}],\"db_activity_settle\":{\"infinite_challenge_info\":[0,0,0]},\"db_cat_gift\":{\"max_award_num\":false,\"award_num\":false,\"end_ti\":0,\"first_feed_gift\":false,\"select_hour_type\":false,\"left_sec\":0,\"awards\":false,\"start_ti\":0},\"db_cat_stuff_map\":[],\"db_cat_gift_new\":{\"cur_gift\":[],\"is_unlock\":false,\"gifts\":[]},\"db_activity\":[],\"db_cat_ad\":{\"ad_num\":0,\"is_unlock\":false,\"cd_time\":0},\"db_clothes_out_cat\":[],\"db_award_map\":[],\"db_tutorial\":[],\"db_stuff_timeline\":[],\"db_inland_server\":{\"account_channel\":\"\",\"uuid\":\"\",\"channel_role\":[],\"bind_account_award\":false,\"decorate_progress\":0,\"story_progress\":0},\"db_cat_extra\":{\"unlock_list\":[]},\"db_mail_list\":[],\"db_stuff_child_visible\":[],\"db_head\":{\"show_head\":\"1\",\"own_head\":[]},\"db_cat_event\":{\"is_init\":false,\"start_time\":0,\"is_unlock\":false,\"num\":0},\"db_talking_choose\":[],\"db_process\":{\"process\":[]}}">>,
client_idx => 170,
client_incr =>
@ -260,38 +260,38 @@ get_test() ->
<<"{\"s\":{\"db_chapter_map\":{\"s\":[{\"m\":{\"begin\":true}}]},\"db_level\":{\"m\":{\"activity_form\":\"Empty\"}},\"db_data_verify\":{\"m\":{\"generate_index\":3}},\"db_cat_ad\":{\"m\":{\"ad_num\":1,\"is_unlock\":true}}}}">>,
<<"{\"s\":{\"db_data_verify\":{\"m\":{\"generate_index\":2}},\"db_logger\":{\"m\":{\"day_num\":1,\"day_idx\":18622}},\"db_cat_map\":{\"m\":{4001:{\"name\":\"\">>,\"dress\":false,\"in_star_level\":0,\"dress_id\":0,\"level\":0,\"exp\":0,\"cat_id\":4001,\"have_list\":[]}}},\"db_role\":{\"m\":{\"login_day_count\":1,\"last_login_day_index\":\"361\"}}}}">>
],
client_map => #{},cross_hammer => 4,d_rockets => 5,energy => 0,
event_idx => 212,exchange => 4,game_time => 0,gold => 6416,hammer => 5,
client_map => #{}, cross_hammer => 4, d_rockets => 5, energy => 0,
event_idx => 212, exchange => 4, game_time => 0, gold => 6416, hammer => 5,
house_data =>
"{\"db_stuff_timeline\":[],\"db_chapter_map\":[[\"chapter\",\"begin\",\"award_idx\",\"stuffs\"],[2001,2001,true,0,[3,3,3,3]]],\"db_stuff_child_visible\":[],\"version\":\"0.2.0\",\"db_item_list\":[],\"db_timeline_stuff_style\":[],\"db_cat_map\":[[\"name\",\"dress\",\"in_star_level\",\"cat_id\",\"level\",\"exp\",\"dress_id\",\"have_list\"],{1:4003,2:\"\",3:false,4:0,5:4003,6:0,7:0,8:0,9:[]},{1:4001,2:\"\",3:false,4:0,5:4001,6:0,7:0,8:0,9:[]}],\"db_cat_room_map\":[[\"toy_id_list\",\"chapter_id\",\"cat_id_list\"],[2001,[],2001,[4001,4003]]]}",
key_data =>
#{'$map_tab' => key_data,
activity =>
#{'$map_tab' => act_dv,
act_comic_strips => #{5101 => {0,2}},
act_comic_strips => #{5101 => {0, 2}},
act_icecream_truck =>
#{4001 =>
{0,
#{-1047817473 => 0,-884040730 => 0,132422520 => 40},
#{-1047817473 => 0, -884040730 => 0, 132422520 => 40},
#{1 => 0}}},
act_milk => #{2001 => {3,1}},
act_milk => #{2001 => {3, 1}},
act_sign => 18622},
ad =>
#{21 => 0,25 => 0,53 => 0,81 => 0,83 => 0,84 => 0,86 => 0,87 => 0,
104 => 0,'$map_tab' => ad_dv,day => 18622},
#{21 => 0, 25 => 0, 53 => 0, 81 => 0, 83 => 0, 84 => 0, 86 => 0, 87 => 0,
104 => 0, '$map_tab' => ad_dv, day => 18622},
award_uid => 0,
cat_room => {2001,[200104,200103,200102,200101]},
cat_room => {2001, [200104, 200103, 200102, 200101]},
cats => <<"p">>,
chapter => {202,2,"ÔÓÒÑÐÏÎÍÌËÊÉ"},
feed => {0,0,0,0,1},
chapter => {202, 2, "ÔÓÒÑÐÏÎÍÌËÊÉ"},
feed => {0, 0, 0, 0, 1},
guide_awards => [[]],
icons => <<>>,
limit_room => {[],[]},
milk_bottle => 1,ml => [],special_back => #{},spoint => 0,
store => <<>>,styles => []
limit_room => {[], []},
milk_bottle => 1, ml => [], special_back => #{}, spoint => 0,
store => <<>>, styles => []
},
lv => 32,res_version => "0.2.0",role_id => 10010001,story => 1,
sync_idx => 1,token => 0,upload_time => 1609147409,verify_result => 0,
lv => 32, res_version => "0.2.0", role_id => 10010001, story => 1,
sync_idx => 1, token => 0, upload_time => 1609147409, verify_result => 0,
version => <<"0.0.0">>}.
get_test2() ->

Ładowanie…
Anuluj
Zapisz