|
-module(lg_SUITE).
|
|
-compile(export_all).
|
|
|
|
-import(ct_helper, [config/2]).
|
|
-import(ct_helper, [doc/1]).
|
|
|
|
%% ct.
|
|
|
|
all() ->
|
|
[{group, all}].
|
|
|
|
%% We cannot run the tests in parallel or they would
|
|
%% interfere with each other.
|
|
groups() ->
|
|
[{all, [], ct_helper:all(?MODULE)}].
|
|
|
|
%% Tests.
|
|
|
|
app(Config) ->
|
|
doc("Trace a specific application."),
|
|
eTpf:trace({app, stdlib}, tpTracerFile, config(priv_dir, Config) ++ "/app.lz4"),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/app.lz4").
|
|
|
|
callback(Config) ->
|
|
doc("Trace using patterns from a callback function."),
|
|
eTpf:trace({callback, ?MODULE, do_callback}, tpTracerFile,
|
|
config(priv_dir, Config) ++ "/callback.lz4"),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/callback.lz4").
|
|
|
|
do_callback() ->
|
|
[{scope, [self()]}, lists].
|
|
|
|
callgrind_running(Config) ->
|
|
doc("Save events to files on disk then build callgrind files."),
|
|
PrivDir = config(priv_dir, Config),
|
|
eTpf:trace([{scope, [self()]}, ?MODULE, {app, stdlib}], tpTracerFile,
|
|
PrivDir ++ "/callgrind_running.lz4",
|
|
#{mode => profile, running => true}),
|
|
do_callgrind_running(),
|
|
eTpf:stop(),
|
|
tpCallGrind:pfm(
|
|
PrivDir ++ "/callgrind_running.lz4.*",
|
|
PrivDir ++ "/callgrind_running.out",
|
|
#{running => true}),
|
|
%% For debugging purposes, print the contents of the callgrind.out files.
|
|
%% Uncomment for easier debugging, otherwise look into the files directly.
|
|
% _ = [begin
|
|
% {ok, File} = file:read_file(PrivDir ++ "/callgrind_running.out." ++ integer_to_list(N)),
|
|
% io:format(user, "# callgrind_running.out.~p~n~s", [N, File]),
|
|
% lg_file_reader:foreach(fun(E) -> io:format(user, "~p~n", [E]) end,
|
|
% PrivDir ++ "/callgrind_running.lz4." ++ integer_to_list(N))
|
|
% end || N <- lists:seq(1, erlang:system_info(schedulers))],
|
|
ok.
|
|
|
|
do_callgrind_running() ->
|
|
timer:sleep(1000),
|
|
Ref = make_ref(),
|
|
erlang:send_after(1000, self(), {go, Ref}),
|
|
lists:seq(1, 100),
|
|
do_callgrind_running_receive(Ref),
|
|
lists:seq(1, 100),
|
|
ok.
|
|
|
|
do_callgrind_running_receive(Ref) ->
|
|
receive
|
|
{go, Ref} ->
|
|
ok
|
|
end.
|
|
|
|
callgrind_running_cycle(Config) ->
|
|
doc("Save events to files on disk then build callgrind files. "
|
|
"Create a recursive cycle using two functions calling each other."),
|
|
PrivDir = config(priv_dir, Config),
|
|
eTpf:trace([{scope, [self()]}, ?MODULE, {app, stdlib}], tpTracerFile,
|
|
PrivDir ++ "/callgrind_running_cycle.lz4",
|
|
#{mode => profile, running => true}),
|
|
do_callgrind_running_cycle(),
|
|
eTpf:stop(),
|
|
tpCallGrind:pfm(
|
|
PrivDir ++ "/callgrind_running_cycle.lz4.*",
|
|
PrivDir ++ "/callgrind_running_cycle.out",
|
|
#{running => true}),
|
|
%% For debugging purposes, print the contents of the callgrind.out files.
|
|
%% Uncomment for easier debugging, otherwise look into the files directly.
|
|
% _ = [begin
|
|
% {ok, File} = file:read_file(PrivDir ++ "/callgrind_running_cycle.out." ++ integer_to_list(N)),
|
|
% io:format(user, "# callgrind_running_cycle.out.~p~n~s", [N, File]),
|
|
% lg_file_reader:foreach(fun(E) -> io:format(user, "~p~n", [E]) end,
|
|
% PrivDir ++ "/callgrind_running_cycle.lz4." ++ integer_to_list(N))
|
|
% end || N <- lists:seq(1, erlang:system_info(schedulers))],
|
|
ok.
|
|
|
|
do_callgrind_running_cycle() ->
|
|
timer:sleep(1000),
|
|
lists:seq(1, 100),
|
|
do_callgrind_running_cycle1(do_callgrind_running_cycle_timer(20)),
|
|
lists:seq(1, 100),
|
|
ok.
|
|
|
|
do_callgrind_running_cycle_timer(N) ->
|
|
erlang:start_timer(N * 10, self(), N).
|
|
|
|
do_callgrind_running_cycle1(Ref) ->
|
|
receive
|
|
{timeout, Ref, 0} ->
|
|
ok;
|
|
{timeout, Ref, N} when N rem 5 =:= 0 ->
|
|
do_callgrind_running_cycle2(do_callgrind_running_cycle_timer(N - 1));
|
|
{timeout, Ref, N} ->
|
|
do_callgrind_running_cycle1(do_callgrind_running_cycle_timer(N - 1))
|
|
end.
|
|
|
|
do_callgrind_running_cycle2(Ref) ->
|
|
receive
|
|
{timeout, Ref, 0} ->
|
|
ok;
|
|
{timeout, Ref, N} when N rem 4 =:= 0 ->
|
|
do_callgrind_running_cycle1(do_callgrind_running_cycle_timer(N - 1));
|
|
{timeout, Ref, N} ->
|
|
do_callgrind_running_cycle2(do_callgrind_running_cycle_timer(N - 1))
|
|
end.
|
|
|
|
file_tracer(Config) ->
|
|
doc("Save events to files on disk."),
|
|
eTpf:trace(lists, tpTracerFile, config(priv_dir, Config) ++ "/file_tracer.lz4"),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/file_tracer.lz4").
|
|
|
|
file_tracer_rotation(Config) ->
|
|
doc("Save events to files on disk; rotate the files if they get too big."),
|
|
Prefix = config(priv_dir, Config) ++ "/file_tracer.lz4",
|
|
eTpf:trace(lists, tpTracerFile, #{
|
|
fBaseName => Prefix,
|
|
fMaxSize => 100, %% Intentionally low.
|
|
fMaxLog => 10 %% Needed to trigger the rotation, default is too high.
|
|
}),
|
|
lists:seq(1, 1000),
|
|
eTpf:stop(),
|
|
%% We should have one or more rotated files.
|
|
Result = [begin
|
|
Filename = Prefix ++ "." ++ integer_to_list(N) ++ ".bak",
|
|
filelib:is_file(Filename)
|
|
end || N <- lists:seq(1, erlang:system_info(schedulers))],
|
|
true = lists:member(true, lists:usort(Result)),
|
|
ok.
|
|
|
|
mod(Config) ->
|
|
doc("Trace a specific module."),
|
|
eTpf:trace(lists, tpTracerFile, config(priv_dir, Config) ++ "/mod.lz4"),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/mod.lz4").
|
|
|
|
profile_mode(Config) ->
|
|
doc("Trace a specific module in profile mode."),
|
|
eTpf:trace(lists, tpTracerFile, config(priv_dir, Config) ++ "/profile_mode.lz4",
|
|
#{mode => profile}),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/profile_mode.lz4").
|
|
|
|
raw_console_tracer(_) ->
|
|
doc("Print raw events to the console."),
|
|
ct:print("Start tracing to the console."),
|
|
%% @todo It seems the order matters when starting. Should it?
|
|
eTpf:trace([{scope, [self()]}, lists]),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
ct:print("Stop tracing to the console.").
|
|
|
|
running_true(Config) ->
|
|
doc("Trace a specific module with running option enabled."),
|
|
eTpf:trace(lists, tpTracerFile, config(priv_dir, Config) ++ "/running_true.lz4",
|
|
#{running => true}),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/running_true.lz4").
|
|
|
|
send_true(Config) ->
|
|
doc("Trace a specific module with send option enabled."),
|
|
eTpf:trace(lists, tpTracerFile, config(priv_dir, Config) ++ "/send_true.lz4",
|
|
#{send => true}),
|
|
Self = self(),
|
|
%% Send a message to and from an existing process.
|
|
Pid = spawn(fun() ->
|
|
receive {msg_from, Self} ->
|
|
Self ! {msg_from, self()}
|
|
end
|
|
end),
|
|
Pid ! {msg_from, Self},
|
|
receive {msg_from, Pid} -> ok end,
|
|
%% Also send a message to a non existing process.
|
|
DeadPid = spawn(fun() -> ok end),
|
|
receive after 100 -> ok end,
|
|
DeadPid ! {msg_from, Self},
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/send_true.lz4").
|
|
|
|
socket_tracer(_) ->
|
|
doc("Send events to a socket."),
|
|
Port = 61234,
|
|
eTpf:trace(lists, tpTracerSocket, Port, #{poolSize => 1}),
|
|
{ok, Socket} = gen_tcp:connect("localhost", Port,
|
|
[binary, {packet, 2}, {active, true}]),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_socket_tracer_recv(Socket).
|
|
|
|
socket_tracer_client(Config) ->
|
|
doc("Send events to a socket client."),
|
|
Port = 61234,
|
|
eTpf:trace(lists, tpTracerSocket, Port, #{poolSize => 1}),
|
|
BaseFilename = config(priv_dir, Config) ++ "/socket_tracer_client.lz4",
|
|
{ok, Pid} = tpSocketCli:start_link(Port, BaseFilename),
|
|
timer:sleep(1000),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
tpSocketCli:stop(Pid),
|
|
{ok, File} = file:read_file(BaseFilename ++ ".0"),
|
|
_ = lz4f:decompress(File),
|
|
true = filelib:file_size(BaseFilename ++ ".0") > 0,
|
|
ok.
|
|
|
|
socket_tracer_many(_) ->
|
|
doc("Send events to many sockets."),
|
|
Port = 61234,
|
|
eTpf:trace(lists, tpTracerSocket, Port, #{poolSize => 5}),
|
|
{ok, _} = gen_tcp:connect("localhost", Port, []),
|
|
{ok, _} = gen_tcp:connect("localhost", Port + 1, []),
|
|
{ok, _} = gen_tcp:connect("localhost", Port + 2, []),
|
|
{ok, _} = gen_tcp:connect("localhost", Port + 3, []),
|
|
{ok, _} = gen_tcp:connect("localhost", Port + 4, []),
|
|
{error, _} = gen_tcp:connect("localhost", Port + 5, []),
|
|
eTpf:stop().
|
|
|
|
socket_tracer_reconnect(_) ->
|
|
doc("Confirm we can reconnect to the tracer."),
|
|
Port = 61234,
|
|
eTpf:trace(lists, tpTracerSocket, Port, #{poolSize => 1}),
|
|
{ok, Socket0} = gen_tcp:connect("localhost", Port,
|
|
[binary, {packet, 2}, {active, true}]),
|
|
ok = gen_tcp:close(Socket0),
|
|
{ok, Socket} = gen_tcp:connect("localhost", Port,
|
|
[binary, {packet, 2}, {active, true}]),
|
|
lists:seq(1, 10),
|
|
eTpf:stop(),
|
|
do_socket_tracer_recv(Socket).
|
|
|
|
do_socket_tracer_recv(Socket) ->
|
|
receive
|
|
{tcp, Socket, Data} ->
|
|
Term = binary_to_term(Data),
|
|
true = is_tuple(Term),
|
|
do_socket_tracer_recv(Socket);
|
|
{tcp_closed, Socket} ->
|
|
ok
|
|
after 1000 ->
|
|
error(timeout)
|
|
end.
|
|
|
|
stop_while_trace_is_running(Config) ->
|
|
doc("Stop tracing while events are still coming in."),
|
|
Self = self(),
|
|
Pid = spawn_link(fun() -> Self ! {self(), continue}, lists:seq(1, 10000000) end),
|
|
eTpf:trace([{scope, [Pid]}, lists], tpTracerFile,
|
|
config(priv_dir, Config) ++ "/stop_while_trace_is_running.lz4"),
|
|
receive {Pid, continue} -> ok after 100 -> error(timeout) end,
|
|
eTpf:stop(),
|
|
do_ensure_decompress(config(priv_dir, Config) ++ "/stop_while_trace_is_running.lz4").
|
|
|
|
%% Internal.
|
|
|
|
do_ensure_decompress(Prefix) ->
|
|
%% Ensure the files can be decompressed.
|
|
Sizes = [begin
|
|
Filename = Prefix ++ "." ++ integer_to_list(N),
|
|
{ok, File} = file:read_file(Filename),
|
|
_ = lz4f:decompress(File),
|
|
filelib:file_size(Filename)
|
|
end || N <- lists:seq(1, erlang:system_info(schedulers))],
|
|
%% We also need to make sure there is actual data in the files,
|
|
%% as lz4f:decompress will succeed when provided with no data.
|
|
true = 0 < lists:sum(Sizes),
|
|
ok.
|