-module(analyse3).
%% To run this program
%% 1) compile
%% 2) run with
%% > analyse3:go(Dir).
%% Where Dir is a string pointing to the root of the directory
%% tree to be analysed.
%% The resulting file is called "calls_out.html"
%% 3) Please send me (joe@sics.se) your results
%% Better still publish your results on your web site and send
%% me the URL - then make a crontab program to run the analysis
%% every night
-compile(export_all).
-import(lists, [filter/2, member/2, sum/1, sort/1, sort/2, map/2, foldl/3]).
-import(lists, [suffix/2, sublist/3, reverse/1]).
-export([go/0, go/1, test/0]).
test() -> go("/home/joe/vol2/").
go() -> go(".").
go(Dir) ->
F = find:files(Dir, "*.erl", true),
D = {0, 0, dict:new()},
Sysmods = sysmods(),
{Nfiles, Nfuncs, Df} =
foldl(fun(File, Acc) -> analyse(File, Acc, Sysmods) end, D, F),
L = dict:to_list(Df),
%% L = {{M,F,A}, Count}
L1 = order(L),
L2 = {Nfiles, Nfuncs, L1},
%% L1 = lists:sort(fun({_,N},{_,M}) -> N > M end, L),
io:format("L2=~p~n",[L2]),
to_html(L2),
true.
url() ->
"http://www.sics.se/~joe/analyse3.erl".
analyse(File, XX={Nfiles, Nfuncs, D0}, Sysmods) ->
io:format("analysing: ~s ", [File]),
case epp:parse_file(File, "", "") of
{ok, Forms} ->
Imports = [{F1,M}||{attribute,_,import,{M,Fs}} <- Forms, F1 <- Fs],
Funcs = [C||{function,_,_,_,C} <- Forms],
Calls = get_calls(Funcs),
Tmp1 = resolve(Calls, dict:from_list(Imports)),
Tmp2 = filter(fun({M,F,A}) -> member(M, Sysmods);
({F,A}) -> false
end, Tmp1),
io:format(" ~w calls~n", [length(Tmp2)]),
{Nfiles+1, Nfuncs + length(Funcs), update_counters(Tmp2, D0)};
_ ->
XX
end.
get_calls({call,_,{remote, _, {atom,_,M}, {atom,_,F}}, Args}) ->
[{M,F,length(Args)}|get_calls(Args)];
get_calls({call,_,{atom,_,F}, Args}) ->
[{F,length(Args)}|get_calls(Args)];
get_calls(T) when tuple(T) ->
get_calls(tuple_to_list(T));
get_calls([H|T]) ->
get_calls(H) ++ get_calls(T);
get_calls(_) ->
[].
%% resolve([{F,A}|{M,F,A}], Imports) -> [{F,A}|{M,F,A}]
%% try to resolve the module from the imports list
resolve([K={F,A}|T], Imports) ->
case dict:find(K, Imports) of
{ok, M} -> [{M,F,A} | resolve(T, Imports)];
error -> [K | resolve(T, Imports)]
end;
resolve([H={M,F,A}|T], Imports) ->
[H|resolve(T, Imports)];
resolve([], _) ->
[].
update_counters([K={M,F,A}|T], D) ->
D1 = case dict:find(K, D) of
error ->
dict:store(K, 1, D);
{ok, N} ->
dict:store(K, N+1, D)
end,
update_counters(T, D1);
update_counters([], D) ->
D.
%%----------------------------------------------------------------------
%% order({{M,F,A},Count}) -> {Mod,[{{F,A}, Count}]}
order(L) ->
L1 = order(L, dict:new()),
L2 = map(fun({Mod, X}) ->
Sum = sum([C||{_,C} <- X]),
X1 = sort(fun({_,C1}, {_, C2}) -> C1 > C2 end, X),
{Mod, Sum, X1}
end, L1),
sort(fun({_,S1,_}, {_,S2,_}) -> S1 > S2 end, L2).
order([{{M,F,A},C}|T], D) ->
case dict:find(M, D) of
{ok, S} ->
order(T, dict:store(M, [{{F,A}, C}|S], D));
error ->
order(T, dict:store(M, [{{F,A}, C}], D))
end;
order([], D) ->
dict:to_list(D).
modsNotUsed(X) ->
Used = map(fun(I) -> element(1, I) end, X),
All = sysmods(),
sort(All -- Used).
%%----------------------------------------------------------------------
to_html(XX={Mods, Funcs, Stuff}) ->
ModsNotUsed = modsNotUsed(Stuff),
P1 = trunc(0.5+100*length(ModsNotUsed)/length(sysmods())),
Str =
["\n","
",
"Analysis results
",
"These results were produced by running ",
"analyse3.erl.",
"
I have analysed ", i2l(Funcs)," functions in ",i2l(Mods),
" modules, here are the most called functions:",
"
",
map(fun({Mod,Count, _}) ->
S = a2l(Mod),
["- ", S, " ",
"(", i2l(Count), ")\n"]
end, Stuff),
"
",
"",i2l(P1),"% of the modules in kernel and stdlib were not used:",
"
",
map(fun(I) -> [a2l(I), " "] end, ModsNotUsed),
"",
map(fun({Mod, Count, Fs}) ->
S = a2l(Mod),
Nused = funcs_not_used(Mod, Fs),
P = trunc(100*(length(Nused)/(length(Fs) + length(Nused))) +
0.5),
["
\n",
"", S,"
\n",
"", i2l(Count), " calls.",
"
\n",
"
\n",
map(fun({{F,A}, C}) ->
["- ",a2l(F),"/",i2l(A),
" (", i2l(C), ")\n"]
end, Fs),
"
",
"", i2l(P), "% of the functions in ", a2l(Mod),
" were not used: ",
map(fun({F,A}) ->
[a2l(F),"/",i2l(A)," "]
end, Nused),
"
"]
end, Stuff)],
file:write_file("calls_out.html", [Str]).
a2l(A) -> atom_to_list(A).
i2l(I) -> integer_to_list(I).
funcs_not_used(Mod, Fs) ->
Used = map(fun(I) -> element(1, I) end, Fs),
All = Mod:module_info(exports),
sort(All -- Used).
%%----------------------------------------------------------------------
mk_sysmods() ->
D1 = code:lib_dir(stdlib),
D2 = code:lib_dir(kernel),
F = find:files(D1, "*.beam", true) ++ find:files(D2, "*.beam", true),
Mods = sort(map(fun(I) ->
list_to_atom(
filename:rootname(
filename:basename(I),".beam"))
end, F)),
{ok, Out} = file:open("sysmods.tmp", write),
io:format(Out, "sysmods() ->~n~p.~n", [Mods]),
file:close(Out),
{found,length(Mods),modules}.
sysmods() ->
[application,
application_controller,
application_master,
application_starter,
auth,
beam_lib,
bplus_tree,
c,
calendar,
code,
code_aux,
code_server,
dets,
dets_server,
dets_utils,
dets_v8,
dets_v9,
dict,
digraph,
digraph_utils,
disk_log,
disk_log_1,
disk_log_server,
disk_log_sup,
dist_ac,
dist_util,
edlin,
epp,
erl_atom_cache,
erl_bits,
erl_boot_server,
erl_compile,
erl_ddll,
erl_distribution,
erl_epmd,
erl_eval,
erl_external,
erl_id_trans,
erl_internal,
erl_lint,
erl_open_port,
erl_parse,
erl_posix_msg,
erl_pp,
erl_prim_loader,
erl_reply,
erl_scan,
erl_tar,
erlang,
error_handler,
error_logger,
error_logger_file_h,
error_logger_tty_h,
erts_debug,
ets,
eval_bits,
file,
file_io_server,
file_server,
file_sorter,
filelib,
filename,
gb_sets,
gb_trees,
gen,
gen_event,
gen_fsm,
gen_server,
gen_tcp,
gen_udp,
global,
global_group,
global_search,
group,
heart,
hipe_pack_constants,
hipe_sparc_loader,
hipe_unified_loader,
hipe_x86_loader,
inet,
inet6_tcp,
inet6_tcp_dist,
inet6_udp,
inet_config,
inet_db,
inet_dns,
inet_gethost_native,
inet_hosts,
inet_parse,
inet_res,
inet_tcp,
inet_tcp_dist,
inet_udp,
init,
io,
io_lib,
io_lib_format,
io_lib_fread,
io_lib_pretty,
kernel,
kernel_config,
lib,
lists,
lists_sort,
log_mf_h,
math,
net,
net_adm,
net_kernel,
old_file_server,
orddict,
ordsets,
os,
otp_internal,
otp_ring0,
pg,
pg2,
pool,
prim_file,
prim_inet,
proc_lib,
property_lists,
queue,
ram_file,
random,
regexp,
rpc,
seq_trace,
sets,
shell,
shell_default,
slave,
socks5,
socks5_auth,
socks5_tcp,
socks5_udp,
sofs,
string,
supervisor,
supervisor_bridge,
sys,
timer,
unix,
user,
user_drv,
user_sup,
vector,
win32reg,
wrap_log_reader].
%-module(find).
%% Module: find.erl
%% Author: Joe Armstrong (joe.armstrong@telia.com)
%% Date: 2001-10-17
%% Purpose: Find all files. Find all out of date files
%% Finds all files relative to a given root directory.
%%
%% Examples:
%% find:files(".", "*.erl", false)
%% finds all files in the current directory.
%% Recursive scan of sub-directories is also allowed.
%%
%% out_of_date checks for "out of date files".
%% find:out_of_date(".",".erl",".jam")
%% finds all out of date Erlang files in the current directory.
%%
%% find:files(Dir, RegExp, Recursive, Fun/2, Acc0)
%% applies Fun(File, Acc) -> Acc. to each file
%% find(Dir, ReExpr, Recursive) -> [File]
%% Find regular files starting from Dir
%% Which match ReExpr
%% If Recursive is true do recursivly on all sub-directories
%% Example find(".", "*.erl", false) will find all erlang files in the
%% Current directory
%%
%% out_of_date(Dir, SrcExt, ObjExt) find all "out of date files" in
%% Dir.
%% Example:
%% out_of_date(".", ".erl", ".jam")
%% Finds all out of date files in the current directory
%%+type files(string(), string(), bool()) -> [string()].
files(Dir, Reg, Recursive, Fun, Acc) ->
case file:list_dir(Dir) of
{ok, Files} -> find_files(Files, Dir, Reg, Recursive, Fun, Acc);
{error, _} -> Acc
end.
find_files([File|T], Dir, Reg, Recursive, Fun, Acc0) ->
FullName = Dir ++ [$/|File],
case file_type(FullName) of
regular ->
case regexp:match(FullName, Reg) of
{match, _, _} ->
Acc = Fun(FullName, Acc0),
find_files(T, Dir, Reg, Recursive, Fun, Acc);
_ ->
find_files(T, Dir, Reg, Recursive, Fun, Acc0)
end;
directory ->
case Recursive of
true ->
Acc1 = files(FullName, Reg, Recursive, Fun, Acc0),
find_files(T, Dir, Reg, Recursive, Fun, Acc1);
false ->
find_files(T, Dir, Reg, Recursive, Fun, Acc0)
end;
error ->
find_files(T, Dir, Reg, Recursive, Fun, Acc0)
end;
find_files([], _, _, _, _, A) ->
A.
%% compatibility with earlier stuff
files(Dir, Re, Flag) ->
Re1 = regexp:sh_to_awk(Re),
reverse(files(Dir, Re1, Flag, fun(File, Acc) ->[File|Acc] end, [])).
-include_lib("kernel/include/file.hrl").
file_type(File) ->
case file:read_file_info(File) of
{ok, Facts} ->
case Facts#file_info.type of
regular -> regular;
directory -> directory;
_ -> error
end;
_ ->
error
end.