-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:", "

", "

",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", "

", "

", 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.