Files
rebar_server/game_server/erlgrind
T
2026-05-19 11:30:21 +08:00

160 lines
6.2 KiB
Erlang

#!/usr/bin/env escript
%%% Copyright (c) 2011-2014
%%% Isac Sacchi e Souza <isacssouza@gmail.com>
%%% All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions are met:
%%%
%%% 1. Redistributions of source code must retain the above copyright notice, this
%%% list of conditions and the following disclaimer.
%%% 2. Redistributions in binary form must reproduce the above copyright notice,
%%% this list of conditions and the following disclaimer in the documentation
%%% and/or other materials provided with the distribution.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
%%% ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
%%% WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
%%% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
%%% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
%%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
%%% ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%%-------------------------------------------------------------------
%%% File : erlgrind
%%% Author : Isac Sacchi e Souza <isacssouza@gmail.com>
%%% Description : Escript to convert fprof output to callgrind output.
%%%
%%% Created : 2011-09-04
%%%-------------------------------------------------------------------
-record(opts, {pid = false :: boolean(),
in_file :: string(),
in_file_format :: trace | analysis,
out_file :: string()}).
main(Params) ->
try parse_params(Params, #opts{}) of
Opts ->
AnalysisFile = maybe_convert_trace(Opts),
{ok, OutFile} = file:open(Opts#opts.out_file, [write]),
{ok, Terms} = file:consult(AnalysisFile),
io:format(OutFile, "events: Time~n", []),
process_terms(OutFile, Terms, Opts)
catch
throw:help ->
print_usage();
_:Reason ->
io:format("Error. ~s.~n", [Reason]),
print_usage()
end.
process_terms(OutFile, [], _Opts) ->
file:close(OutFile);
process_terms(OutFile, [{analysis_options, _Opt} | Rest], Opts) ->
process_terms(OutFile, Rest, Opts);
process_terms(OutFile, [[{totals, _Cnt, Acc, _Own}] | Rest], Opts) ->
io:format(OutFile, "summary: ~w~n", [trunc(Acc*1000)]),
process_terms(OutFile, Rest, Opts);
process_terms(OutFile, [[{Pid, _Cnt, _Acc, _Own} | _T] | Rest], Opts)
when is_list(Pid) and (Opts#opts.pid =:= true) ->
io:format(OutFile, "ob=~s~n", [Pid]),
process_terms(OutFile, Rest, Opts);
process_terms(OutFile, [List | Rest], Opts) when is_list(List) ->
process_terms(OutFile, Rest, Opts);
process_terms(OutFile, [Entry | Rest], Opts) ->
process_entry(OutFile, Entry),
process_terms(OutFile, Rest, Opts).
process_entry(OutFile, {_CallingList, Actual, CalledList}) ->
process_actual(OutFile, Actual),
process_called_list(OutFile, CalledList).
process_actual(OutFile, {Func, _Cnt, _Acc, _Own} = Actual) ->
File = get_file(Func),
Time = actual_time(Actual),
io:format(OutFile, "fl=~w~n", [File]),
io:format(OutFile, "fn=~w~n", [Func]),
io:format(OutFile, "1 ~w~n", [Time]).
actual_time({suspend, _Cnt, Acc, _Own}) ->
%% fprof doesn't include suspend time as own time, but kcachegrind needs a time value here
%% or total percents will add up to more than 100
trunc(Acc*1000);
actual_time({_Func, _Cnt, _Acc, Own}) ->
trunc(Own*1000).
process_called_list(_, []) ->
ok;
process_called_list(OutFile, [Called | Rest]) ->
process_called(OutFile, Called),
process_called_list(OutFile, Rest).
process_called(OutFile, {Func, Cnt, Acc, _Own}) ->
File = get_file(Func),
io:format(OutFile, "cfl=~w~n", [File]),
io:format(OutFile, "cfn=~w~n", [Func]),
io:format(OutFile, "calls=~w 1~n", [Cnt]),
io:format(OutFile, "1 ~w~n", [trunc(Acc*1000)]).
get_file({Mod, _Func, _Arity}) ->
Mod;
get_file(_Func) ->
pseudo.
parse_params(["-h" | _], _) ->
throw(help);
parse_params(["-p" | Rest], Opts) ->
parse_params(Rest, Opts#opts{pid=true});
parse_params([[$- | _] = Opt | _], _) ->
throw(io_lib:format("Invalid option \"~s\"", [Opt]));
parse_params([Arg | Rest], #opts{in_file=undefined} = Opts) ->
parse_params(Rest, Opts#opts{in_file=Arg,
in_file_format = infile_format(Arg)});
parse_params([Arg | Rest], #opts{out_file=undefined} = Opts) ->
parse_params(Rest, Opts#opts{out_file=Arg});
parse_params([], #opts{out_file=undefined, in_file=InFileName} = Opts) ->
%% No outfile specified:
%% generate output filename by replacing file extension with ".cgrind"
OutFileName = replace_extension(InFileName, ".cgrind"),
parse_params([], Opts#opts{out_file=OutFileName});
parse_params([], Opts) ->
Opts;
parse_params(Other, _) ->
throw(io_lib:format("Invalid argument \"~s\"", [Other])).
infile_format(InFileName) ->
case filename:extension(InFileName) of
".trace" ->
trace;
".analysis" ->
analysis;
_ ->
% if we can't identify the extension, default to analysis
analysis
end.
maybe_convert_trace(#opts{in_file_format=analysis, in_file=InFileName}) ->
InFileName;
maybe_convert_trace(#opts{in_file_format=trace, in_file=InFileName}) ->
AnalysisFileName = replace_extension(InFileName, ".analysis"),
fprof:profile([{file, InFileName}]),
fprof:analyse([{dest, AnalysisFileName}]),
AnalysisFileName.
replace_extension(Filename, NewExtension) ->
Idx = string:rchr(Filename, $.),
BaseFile = string:substr(Filename, 1, Idx - 1),
BaseFile ++ NewExtension.
print_usage() ->
io:format("Usage: erlgrind [Options] <input file .trace/.analysis> [<output file>]~n"),
io:format("Options:~n"
"\t-p\tuse process pid as ELF object~n"
"\t-h\tshow this message~n").