├── LICENSE ├── README.md ├── ebin └── .gitignore ├── guiexample.png ├── include ├── vprof.hrl └── wx_compat.hrl └── src ├── vprof.app.src ├── vprof.erl ├── vprof_data.erl ├── vprof_function.erl ├── vprof_functions.erl ├── vprof_grid.erl ├── vprof_gui.erl ├── vprof_process.erl ├── vprof_processes.erl ├── vprof_profile.erl ├── vprof_q.erl └── vprof_wx_utils.erl /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Artplant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vprof 2 | Visual Erlang Profiler 3 | 4 | # Usage 5 | 6 | Check vprof.erl for public api details. 7 | 8 | ## Collect 9 | 10 | ```erlang 11 | %% Trace target node for 2 seconds 12 | %% By default output is written to vprof.trace file 13 | 1> vprof:trace(2). 14 | ok 15 | ``` 16 | 17 | ## Analyze 18 | 19 | ```erlang 20 | %% Process data - on any node. By default reads vprof.trace and writes to vprof.out 21 | 2> vprof:profile(). 22 | vprof: Profiling vprof.trace. This may take several minutes. 23 | <0.47.0> 24 | vprof: End of trace in 1 seconds. 25 | vprof: Done in 1 seconds. 26 | Check vprof.out to see results or run 27 | vprof:gui("vprof.out"). 28 | ``` 29 | 30 | ## View 31 | 32 | ``` 33 | 3> vprof:gui(). 34 | ``` 35 | 36 | ![GUI](/guiexample.png?raw=true) 37 | -------------------------------------------------------------------------------- /ebin/.gitignore: -------------------------------------------------------------------------------- 1 | *.app 2 | *.beam -------------------------------------------------------------------------------- /guiexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artplant/vprof/9dc58849b8ef26b32b05d877e938190d526b222c/guiexample.png -------------------------------------------------------------------------------- /include/vprof.hrl: -------------------------------------------------------------------------------- 1 | %% Model 2 | 3 | -record(totals, {acc, cnt, data}). 4 | 5 | -record(process, { 6 | pid :: string(), 7 | cnt :: pos_integer(), 8 | own :: float(), 9 | schedules :: non_neg_integer() 10 | }). 11 | 12 | -record(function, { 13 | mfa, 14 | pid, 15 | cnt, 16 | acc, 17 | own, 18 | parents, 19 | children 20 | }). 21 | 22 | -record(ref, { 23 | mfa, 24 | cnt, 25 | acc, 26 | own 27 | }). 28 | 29 | %% View 30 | 31 | -record(table_column, { 32 | binding, 33 | title, 34 | alignment, 35 | auto_size = false, 36 | default_width, 37 | default_sort = false, 38 | default_reversed = false, 39 | format 40 | }). 41 | 42 | -record(row, { 43 | mfa, 44 | pid, 45 | pid_count=1, 46 | cnt, 47 | acc, 48 | own, 49 | parents, 50 | children 51 | }). 52 | 53 | -------------------------------------------------------------------------------- /include/wx_compat.hrl: -------------------------------------------------------------------------------- 1 | -ifdef(OTP_RELEASE). 2 | -if(?OTP_RELEASE >= 24). 3 | -define(assignWindow, assignWindow). 4 | -else. 5 | -define(assignWindow, setWindow). 6 | -endif. 7 | -else. 8 | -define(assignWindow, setWindow). 9 | -endif. 10 | 11 | -------------------------------------------------------------------------------- /src/vprof.app.src: -------------------------------------------------------------------------------- 1 | {application,vprof, 2 | [{description,"Erlang Visual Profiler"}, 3 | {vsn,"1.0"}, 4 | {modules,[]}, 5 | {registered,[]}, 6 | {applications,[kernel,stdlib]}]}. 7 | -------------------------------------------------------------------------------- /src/vprof.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 01.12.2013 3 | 4 | -module(vprof). 5 | 6 | %% Include files 7 | -include("vprof.hrl"). 8 | 9 | %% Exported Functions 10 | 11 | -export([ 12 | trace/1, 13 | trace/2, 14 | trace_fun/1, 15 | start_trace/1, 16 | start_trace/2, 17 | stop_trace/0, 18 | profile/0, 19 | profile/2, 20 | cancel_profile/1, 21 | gui/0, 22 | gui/1 23 | ]). 24 | 25 | -type filename() :: string(). 26 | 27 | %%%=================================================================== 28 | %%% API 29 | %%%=================================================================== 30 | -spec trace(number()) -> 'ok'. 31 | 32 | %% @doc Trace all processes for X seconds to "vprof.trace" file. 33 | 34 | trace(Seconds) -> 35 | trace(Seconds, "vprof.trace"). 36 | 37 | -spec trace(number(), filename()) -> 'ok'. 38 | 39 | %% @doc Trace all processes for X seconds to the specified file. 40 | 41 | trace(Seconds, TraceFile) -> 42 | start_trace(all, TraceFile), 43 | timer:sleep(round(Seconds * 1000)), 44 | stop_trace(). 45 | 46 | trace_fun(Fun) -> 47 | start_trace(self()), 48 | Fun(), 49 | stop_trace(). 50 | 51 | start_trace(Process) -> 52 | start_trace(Process, "vprof.trace"). 53 | 54 | start_trace(Process, TraceFile) -> 55 | TraceFun = dbg:trace_port(file, TraceFile), 56 | {ok, _TracerPid} = dbg:tracer(port, TraceFun), 57 | dbg:tpl('_', []), 58 | dbg:p(Process, [call, timestamp, return_to, arity, running, procs]). 59 | 60 | -spec stop_trace() -> 'ok'. 61 | 62 | stop_trace() -> 63 | erlang:trace(all, false, [all]), 64 | dbg:stop_clear(). 65 | 66 | -spec profile() -> pid(). 67 | 68 | %% @doc Profile "vprof.trace", dump results to "vprof.out". 69 | %% Profile may take several minutes, pass the returned pid to cancel_profile to cancel it. 70 | 71 | profile() -> 72 | profile("vprof.trace", "vprof.out"). 73 | 74 | -spec profile(filename(), filename()) -> pid(). 75 | 76 | %% @doc Profile TraceFile, dump results to DataFile. 77 | %% Profile may take several minutes, pass the returned pid to cancel_profile to cancel it. 78 | 79 | profile(TraceFile, DataFile) -> 80 | vprof_profile:profile(TraceFile, DataFile). 81 | 82 | -spec cancel_profile(pid()) -> 'ok'. 83 | 84 | %% @doc Cancel profile. 85 | 86 | cancel_profile(Pid) -> 87 | dbg:stop_trace_client(Pid). 88 | 89 | -spec gui() -> 'ok'. 90 | 91 | %% @doc Show GUI with analysis for "vprof.out" file. 92 | 93 | gui() -> 94 | gui("vprof.out"). 95 | 96 | -spec gui(filename()) -> 'ok'. 97 | 98 | %% @doc Show GUI with analysis for the specified analysis file. 99 | 100 | gui(DataFile) -> 101 | {ok, Pid} = vprof_data:start_link(DataFile), 102 | vprof_gui:start(Pid), 103 | ok. 104 | -------------------------------------------------------------------------------- /src/vprof_data.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 29.11.2013 3 | 4 | -module(vprof_data). 5 | 6 | -behaviour(gen_server). 7 | 8 | -include("vprof.hrl"). 9 | 10 | -export([ 11 | start_link/1, 12 | start_link/2, 13 | info/2, 14 | filename/1 15 | ]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 19 | 20 | -record(state, { 21 | filename, 22 | funs, 23 | procs 24 | }). 25 | 26 | %%%=================================================================== 27 | %%% API 28 | %%%=================================================================== 29 | start_link(Name, FileName) -> 30 | gen_server:start_link({local, Name}, ?MODULE, FileName, []). 31 | 32 | start_link(FileName) -> 33 | gen_server:start_link(?MODULE, FileName, []). 34 | 35 | filename(Ref) -> 36 | info(Ref, filename). 37 | 38 | info(Ref, Cmd) -> 39 | gen_server:call(Ref, Cmd, 10000). 40 | 41 | %%%=================================================================== 42 | %%% gen_server callbacks 43 | %%%=================================================================== 44 | 45 | init(FileName) -> 46 | State = load(FileName), 47 | {ok, State}. 48 | 49 | handle_call(Info, _From, State) -> 50 | Reply = get_info(Info, State), 51 | {reply, Reply, State}. 52 | 53 | handle_cast(stop, State) -> 54 | {stop, normal, State}. 55 | 56 | handle_info({load, FileName}, _) -> 57 | State = load(FileName), 58 | io:format("~p loaded~n", [FileName]), 59 | {noreply, State}. 60 | 61 | terminate(_Reason, _State) -> 62 | ok. 63 | 64 | code_change(_OldVsn, State, _Extra) -> 65 | {ok, State}. 66 | 67 | %%%=================================================================== 68 | %%% Internal functions 69 | %%%=================================================================== 70 | load(FileName) -> 71 | {ok, [{Procs,Funs}]} = file:consult(FileName), 72 | #state{ 73 | filename = FileName, 74 | funs = Funs, 75 | procs = Procs}. 76 | 77 | get_info(filename, State) -> 78 | State#state.filename; 79 | get_info(pids, State) -> 80 | ordsets:from_list([ R#function.pid || R <- State#state.funs ]); 81 | get_info(funs, State) -> 82 | ordsets:from_list([ R#function.mfa || R <- State#state.funs ]); 83 | get_info({calls, Pid}, State) -> 84 | [ fun_to_row(Fun) || Fun <- State#state.funs, Fun#function.pid =:= Pid ]; 85 | get_info(total_calls, State) -> 86 | Groups = vprof_q:group(#function.mfa, State#state.funs), 87 | [ #row{mfa = MFA, pid_count = vprof_q:qq(Funs, [ [#function.pid], ordset, length ]), 88 | cnt = vprof_q:keysum(#function.cnt, Funs), 89 | acc = vprof_q:keysum(#function.acc, Funs), 90 | own = vprof_q:keysum(#function.own, Funs)} || {MFA, Funs} <- Groups ]; 91 | get_info(processes, State) -> 92 | State#state.procs; 93 | get_info(processes_summary, State) -> 94 | Procs = State#state.procs, 95 | [ #process{pid = length(Procs), 96 | cnt = vprof_q:keysum(#process.cnt, Procs), 97 | own = vprof_q:keysum(#process.own, Procs), 98 | schedules = vprof_q:keysum(#process.schedules, Procs)} ]; 99 | get_info({processes, MFA}, State) -> 100 | [ fun_to_row(Fun) || Fun <- State#state.funs, Fun#function.mfa =:= MFA ]; 101 | get_info({called_by, Pid, MFA}, State) -> 102 | case [ Fun || Fun <- State#state.funs, Fun#function.mfa =:= MFA, Fun#function.pid =:= Pid ] of 103 | [F | _] -> 104 | [ ref_to_row(Ref, Pid) || Ref <- F#function.parents ]; 105 | [] -> 106 | [] 107 | end; 108 | get_info({called_by, MFA}, State) -> 109 | Groups = vprof_q:group(#function.mfa, [ #function{mfa = Ref#ref.mfa, acc = Ref#ref.acc, cnt = Ref#ref.cnt, own = Ref#ref.own, pid = Fun#function.pid} || Fun <- State#state.funs, Fun#function.mfa =:= MFA, Ref <- Fun#function.parents ]), 110 | [ #row{mfa = CallingMFA, pid_count = vprof_q:qq(Funs, [ [#function.pid], ordset, length ]), 111 | cnt = vprof_q:keysum(#function.cnt, Funs), 112 | acc = vprof_q:keysum(#function.acc, Funs), 113 | own = vprof_q:keysum(#function.own, Funs)} || {CallingMFA, Funs} <- Groups ]; 114 | get_info({mfa, Pid, MFA}, State) -> 115 | case [ Fun || Fun <- State#state.funs, Fun#function.mfa =:= MFA, Fun#function.pid =:= Pid ] of 116 | [F | _] -> 117 | [ fun_to_row(F) ]; 118 | [] -> 119 | [] 120 | end; 121 | get_info({mfa, MFA}, State) -> 122 | Funs = [ Fun || Fun <- State#state.funs, Fun#function.mfa =:= MFA ], 123 | [ #row{mfa = MFA, pid_count = vprof_q:qq(Funs, [ [#function.pid], ordset, length ]), 124 | cnt = vprof_q:keysum(#function.cnt, Funs), 125 | acc = vprof_q:keysum(#function.acc, Funs), 126 | own = vprof_q:keysum(#function.own, Funs)} ]; 127 | get_info({calling, Pid, MFA}, State) -> 128 | case [ Fun || Fun <- State#state.funs, Fun#function.mfa =:= MFA, Fun#function.pid =:= Pid ] of 129 | [F | _] -> 130 | [ ref_to_row(Ref, Pid) || Ref <- F#function.children ]; 131 | [] -> 132 | [] 133 | end; 134 | get_info({calling, MFA}, State) -> 135 | Groups = vprof_q:group(#function.mfa, [ #function{mfa = Ref#ref.mfa, acc = Ref#ref.acc, cnt = Ref#ref.cnt, own = Ref#ref.own, pid = Fun#function.pid} || Fun <- State#state.funs, Fun#function.mfa =:= MFA, Ref <- Fun#function.children ]), 136 | [ #row{mfa = CalledMFA, pid_count = vprof_q:qq(Funs, [ [#function.pid], ordset, length ]), 137 | cnt = vprof_q:keysum(#function.cnt, Funs), 138 | acc = vprof_q:keysum(#function.acc, Funs), 139 | own = vprof_q:keysum(#function.own, Funs)} || {CalledMFA, Funs} <- Groups ]; 140 | get_info(Unknown, _State) -> 141 | io:format("unknown data tag: ~p~n", [Unknown]). 142 | 143 | fun_to_row(Fun) -> 144 | #function{cnt = Cnt, acc = Acc, own = Own, pid = Pid, mfa = MFA} = Fun, 145 | #row{cnt = Cnt, acc = Acc, own = Own, pid = Pid, mfa = MFA}. 146 | 147 | ref_to_row(Ref, Pid) -> 148 | #ref{cnt = Cnt, acc = Acc, own = Own, mfa = MFA} = Ref, 149 | #row{cnt = Cnt, acc = Acc, own = Own, mfa = MFA, pid = Pid}. 150 | -------------------------------------------------------------------------------- /src/vprof_function.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 26.01.2016 3 | 4 | -module(vprof_function). 5 | 6 | -behaviour(wx_object). 7 | 8 | -include_lib("wx/include/wx.hrl"). 9 | -include_lib("wx_compat.hrl"). 10 | -include("vprof.hrl"). 11 | 12 | -export([ 13 | start_link/4 14 | ]). 15 | 16 | %% wx_object callbacks 17 | -export([ 18 | init/1, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3, 22 | handle_call/3, 23 | handle_event/2, 24 | handle_sync_event/3, 25 | handle_cast/2]). 26 | 27 | -record(state, { 28 | parent, 29 | mfa, 30 | called_by_grid, 31 | selected_grid, 32 | calling_grid 33 | }). 34 | 35 | start_link(Notebook, Data, Parent, MFA) -> 36 | wx_object:start_link(?MODULE, {Notebook, Data, Parent, MFA}, []). 37 | 38 | init({Notebook, Data, Parent, MFA}) -> 39 | Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), 40 | 41 | Splitter = vprof_wx_utils:splitter(Panel), 42 | GridLeft = processes_grid(Splitter, Data, Parent, MFA), 43 | PanelRight = wxPanel:new(Splitter, [{size, wxWindow:getClientSize(Splitter)}]), 44 | vprof_wx_utils:split_vertical(Splitter, 0.5, GridLeft, PanelRight), 45 | 46 | CalledByGrid = called_by_grid(PanelRight, Data, MFA, unknown), 47 | SelectedGrid = selected_grid(PanelRight, Data, MFA, unknown), 48 | CallingGrid = calling_grid(PanelRight, Data, MFA, unknown), 49 | 50 | Box = wxBoxSizer:new(?wxVERTICAL), 51 | wxBoxSizer:add(Box, CalledByGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 52 | Item = wxBoxSizer:add(Box, 100, 50, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), 53 | wxSizerItem:?assignWindow(Item, SelectedGrid), 54 | wxSizerItem:setMinSize(Item, 50, 50), 55 | wxBoxSizer:add(Box, CallingGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 56 | wxWindow:setSizer(PanelRight, Box), 57 | 58 | State = #state{parent = Parent, mfa = MFA, called_by_grid = CalledByGrid, calling_grid = CallingGrid, selected_grid = SelectedGrid}, 59 | 60 | {Panel, State}. 61 | 62 | processes_grid(Panel, Data, Parent, MFA) -> 63 | GridColumns = [ 64 | #table_column{title = "PID", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 65 | format = string, default_width = 70, binding = #row.pid}, 66 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 67 | binding = #row.cnt, default_reversed = true}, 68 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 69 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 70 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 71 | format = {float, 3}, binding = #row.own, default_reversed = true} 72 | ], 73 | Self = self(), 74 | OnSelect = fun(DataRow) -> Self ! {select_pid, DataRow} end, 75 | OnActivate = fun(DataRow) -> Parent ! {open_page, DataRow#row.pid, DataRow#row.pid, vprof_process} end, 76 | vprof_grid:start_link(Panel, self(), Data, {processes, MFA}, GridColumns, OnSelect, OnActivate). 77 | 78 | called_by_grid(Panel, Data, Pid, MFA) -> 79 | Columns = [ 80 | #table_column{title = "Called By", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 81 | format = mfa, default_width = 200, binding = #row.mfa}, 82 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 83 | binding = #row.cnt, default_reversed = true}, 84 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 85 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 86 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 87 | format = {float, 3}, binding = #row.own, default_reversed = true} 88 | ], 89 | Self = self(), 90 | OnActivate = fun(DataRow) -> Self ! {select_function, DataRow} end, 91 | vprof_grid:start_link(Panel, self(), Data, {called_by, Pid, MFA}, Columns, undefined, OnActivate). 92 | 93 | selected_grid(Panel, Data, Pid, MFA) -> 94 | Columns = [ 95 | #table_column{title = "Function", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 96 | format = mfa, default_width = 200, binding = #row.mfa}, 97 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 98 | binding = #row.cnt, default_reversed = true}, 99 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 100 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 101 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 102 | format = {float, 3}, binding = #row.own, default_reversed = true} 103 | ], 104 | vprof_grid:start_link(Panel, self(), Data, {mfa, Pid, MFA}, Columns, undefined, undefined). 105 | 106 | calling_grid(Panel, Data, Pid, MFA) -> 107 | Columns = [ 108 | #table_column{title = "Calling", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 109 | format = mfa, default_width = 200, binding = #row.mfa}, 110 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 111 | binding = #row.cnt, default_reversed = true}, 112 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 113 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 114 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 115 | format = {float, 3}, binding = #row.own, default_reversed = true} 116 | ], 117 | Self = self(), 118 | OnActivate = fun(DataRow) -> Self ! {select_function, DataRow} end, 119 | vprof_grid:start_link(Panel, self(), Data, {calling, Pid, MFA}, Columns, undefined, OnActivate). 120 | 121 | handle_event(Event, _State) -> 122 | error({unhandled_event, Event}). 123 | 124 | handle_sync_event(_Event, _Obj, _State) -> 125 | ok. 126 | 127 | handle_call(Event, From, _State) -> 128 | error({unhandled_call, Event, From}). 129 | 130 | handle_cast(Event, _State) -> 131 | error({unhandled_cast, Event}). 132 | 133 | handle_info({select_pid, DataRow}, State) -> 134 | vprof_grid:set_data(State#state.called_by_grid, {called_by, DataRow#row.pid, State#state.mfa}), 135 | vprof_grid:set_data(State#state.calling_grid, {calling, DataRow#row.pid, State#state.mfa}), 136 | vprof_grid:set_data(State#state.selected_grid, {mfa, DataRow#row.pid, State#state.mfa}), 137 | {noreply, State}; 138 | handle_info({error, Error}, State) -> 139 | handle_error(Error), 140 | {noreply, State}; 141 | handle_info(_Event, State) -> 142 | {noreply, State}. 143 | 144 | terminate(_Event, _State) -> 145 | ok. 146 | 147 | code_change(_, _, State) -> 148 | State. 149 | 150 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 151 | 152 | handle_error(Foo) -> 153 | Str = io_lib:format("ERROR: ~s~n",[Foo]), 154 | observer_lib:display_info_dialog(Str). 155 | 156 | -------------------------------------------------------------------------------- /src/vprof_functions.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 26.01.2016 3 | 4 | -module(vprof_functions). 5 | 6 | -behaviour(wx_object). 7 | 8 | -include_lib("wx/include/wx.hrl"). 9 | -include("wx_compat.hrl"). 10 | -include("vprof.hrl"). 11 | 12 | -export([ 13 | start_link/3 14 | ]). 15 | 16 | %% wx_object callbacks 17 | -export([ 18 | init/1, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3, 22 | handle_call/3, 23 | handle_event/2, 24 | handle_sync_event/3, 25 | handle_cast/2]). 26 | 27 | 28 | -record(state, { 29 | parent, 30 | processes_grid, 31 | called_by_grid, 32 | selected_grid, 33 | calling_grid 34 | }). 35 | 36 | start_link(Notebook, Data, Parent) -> 37 | wx_object:start_link(?MODULE, {Notebook, Data, Parent}, []). 38 | 39 | init({Notebook, Data, Parent}) -> 40 | Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), 41 | 42 | Splitter = vprof_wx_utils:splitter(Panel), 43 | GridLeft = totals_grid(Splitter, Data, Parent), 44 | PanelRight = wxPanel:new(Splitter, [{size, wxWindow:getClientSize(Splitter)}]), 45 | vprof_wx_utils:split_vertical(Splitter, 0.5, GridLeft, PanelRight), 46 | 47 | ProcessesGrid = processes_grid(PanelRight, Data, undefined, Parent), 48 | CalledByGrid = called_by_grid(PanelRight, Data, unknown), 49 | SelectedGrid = selected_grid(PanelRight, Data, unknown), 50 | CallingGrid = calling_grid(PanelRight, Data, unknown), 51 | 52 | Box = wxBoxSizer:new(?wxVERTICAL), 53 | wxBoxSizer:add(Box, ProcessesGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 2}]), 54 | wxBoxSizer:add(Box, CalledByGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 2}]), 55 | Item = wxBoxSizer:add(Box, 100, 50, [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}]), 56 | wxSizerItem:?assignWindow(Item, SelectedGrid), 57 | wxSizerItem:setMinSize(Item, 50, 50), 58 | wxBoxSizer:add(Box, CallingGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 2}]), 59 | wxWindow:setSizer(PanelRight, Box), 60 | 61 | State = #state{parent = Parent, processes_grid = ProcessesGrid, called_by_grid = CalledByGrid, calling_grid = CallingGrid, selected_grid = SelectedGrid}, 62 | 63 | {Panel, State}. 64 | 65 | totals_grid(Panel, Data, Parent) -> 66 | GridColumns = [ 67 | #table_column{title = "Function", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 68 | format = mfa, default_width = 200, binding = #row.mfa}, 69 | #table_column{title = "Processes", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 70 | binding = #row.pid_count, default_reversed = true}, 71 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 72 | binding = #row.cnt, default_reversed = true}, 73 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 74 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 75 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 76 | format = {float, 3}, binding = #row.own, default_reversed = true} 77 | ], 78 | Self = self(), 79 | OnSelect = fun(DataRow) -> Self ! {select_function, DataRow} end, 80 | OnActivate = fun(DataRow) -> Parent ! {open_page, DataRow#row.mfa, format_mfa(DataRow#row.mfa), vprof_function} end, 81 | vprof_grid:start_link(Panel, self(), Data, total_calls, GridColumns, OnSelect, OnActivate). 82 | 83 | processes_grid(Panel, Data, MFA, Parent) -> 84 | Columns = [ 85 | #table_column{title = "PID", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 86 | format = string, default_width = 70, binding = #row.pid}, 87 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 88 | binding = #row.cnt, default_reversed = true}, 89 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 90 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 91 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 92 | format = {float, 3}, binding = #row.own, default_reversed = true} 93 | ], 94 | OnActivate = fun(DataRow) -> Parent ! {open_page, DataRow#row.pid, DataRow#row.pid, vprof_process} end, 95 | vprof_grid:start_link(Panel, self(), Data, {processes, MFA}, Columns, undefined, OnActivate). 96 | 97 | called_by_grid(Panel, Data, MFA) -> 98 | Columns = [ 99 | #table_column{title = "Called By", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 100 | format = mfa, default_width = 200, binding = #row.mfa}, 101 | #table_column{title = "Processes", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 102 | binding = #row.pid_count, default_reversed = true}, 103 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 104 | binding = #row.cnt, default_reversed = true}, 105 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 106 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 107 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 108 | format = {float, 3}, binding = #row.own, default_reversed = true} 109 | ], 110 | Self = self(), 111 | OnActivate = fun(DataRow) -> Self ! {select_function, DataRow} end, 112 | vprof_grid:start_link(Panel, self(), Data, {called_by, MFA}, Columns, undefined, OnActivate). 113 | 114 | selected_grid(Panel, Data, MFA) -> 115 | Columns = [ 116 | #table_column{title = "Function", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 117 | format = mfa, default_width = 200, binding = #row.mfa}, 118 | #table_column{title = "Processes", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 119 | binding = #row.pid_count, default_reversed = true}, 120 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 121 | binding = #row.cnt, default_reversed = true}, 122 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 123 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 124 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 125 | format = {float, 3}, binding = #row.own, default_reversed = true} 126 | ], 127 | vprof_grid:start_link(Panel, self(), Data, {mfa, MFA}, Columns, undefined, undefined). 128 | 129 | calling_grid(Panel, Data, MFA) -> 130 | Columns = [ 131 | #table_column{title = "Calling", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 132 | format = mfa, default_width = 200, binding = #row.mfa}, 133 | #table_column{title = "Processes", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 134 | binding = #row.pid_count, default_reversed = true}, 135 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 136 | binding = #row.cnt, default_reversed = true}, 137 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 138 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 139 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 140 | format = {float, 3}, binding = #row.own, default_reversed = true} 141 | ], 142 | Self = self(), 143 | OnActivate = fun(DataRow) -> Self ! {select_function, DataRow} end, 144 | vprof_grid:start_link(Panel, self(), Data, {calling, MFA}, Columns, undefined, OnActivate). 145 | 146 | handle_event(Event, _State) -> 147 | error({unhandled_event, Event}). 148 | 149 | handle_sync_event(_Event, _Obj, _State) -> 150 | ok. 151 | 152 | handle_call(Event, From, _State) -> 153 | error({unhandled_call, Event, From}). 154 | 155 | handle_cast(Event, _State) -> 156 | error({unhandled_cast, Event}). 157 | 158 | handle_info({select_function, DataRow}, State) -> 159 | vprof_grid:set_data(State#state.processes_grid, {processes, DataRow#row.mfa}), 160 | vprof_grid:set_data(State#state.called_by_grid, {called_by,DataRow#row.mfa}), 161 | vprof_grid:set_data(State#state.calling_grid, {calling, DataRow#row.mfa}), 162 | vprof_grid:set_data(State#state.selected_grid, {mfa, DataRow#row.mfa}), 163 | {noreply, State}; 164 | handle_info({error, Error}, State) -> 165 | handle_error(Error), 166 | {noreply, State}; 167 | handle_info(_Event, State) -> 168 | {noreply, State}. 169 | 170 | terminate(_Event, _State) -> 171 | ok. 172 | 173 | code_change(_, _, State) -> 174 | State. 175 | 176 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 177 | 178 | handle_error(Foo) -> 179 | Str = io_lib:format("ERROR: ~s~n",[Foo]), 180 | observer_lib:display_info_dialog(Str). 181 | 182 | format_mfa({Mod,Fun,A}) -> 183 | io_lib:format("~p:~p/~p", [Mod,Fun,A]). 184 | -------------------------------------------------------------------------------- /src/vprof_grid.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 13.08.2014 3 | 4 | -module(vprof_grid). 5 | 6 | -behaviour(wx_object). 7 | 8 | -include_lib("wx/include/wx.hrl"). 9 | -include("vprof.hrl"). 10 | 11 | -export([ 12 | start_link/7, 13 | set_data/2 14 | ]). 15 | 16 | %% wx_object callbacks 17 | -export([ 18 | init/1, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3, 22 | handle_call/3, 23 | handle_event/2, 24 | handle_sync_event/3, 25 | handle_cast/2]). 26 | 27 | 28 | -define(GRID, 500). 29 | 30 | -record(state, { 31 | parent, 32 | grid, 33 | selected, 34 | tabs, 35 | data_ref, 36 | data, 37 | columns, 38 | sort_key, 39 | sort_reversed = false, 40 | on_select, 41 | on_activate, 42 | even_colour, 43 | odd_colour 44 | }). 45 | 46 | start_link(Panel, Parent, DataRef, DataTag, Columns, OnSelect, OnActivate) -> 47 | wx_object:start_link(?MODULE, {Panel, Parent, DataRef, DataTag, Columns, OnSelect, OnActivate}, []). 48 | 49 | set_data(Grid, DataTag) -> 50 | wx_object:cast(Grid, {set_data, DataTag}). 51 | 52 | init({Panel, Parent, DataRef, DataTag, Columns, OnSelect, OnActivate}) -> 53 | Data = vprof_data:info(DataRef, DataTag), 54 | 55 | Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, 56 | Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), 57 | Li = wxListItem:new(), 58 | AddListEntry = 59 | fun(#table_column{title = Name, alignment = Align, default_width = DefSize}, Col) -> 60 | wxListItem:setText(Li, Name), 61 | wxListItem:setAlign(Li, Align), 62 | wxListCtrl:insertColumn(Grid, Col, Li), 63 | wxListCtrl:setColumnWidth(Grid, Col, DefSize), 64 | Col + 1 65 | end, 66 | lists:foldl(AddListEntry, 0, Columns), 67 | wxListItem:destroy(Li), 68 | 69 | wxListCtrl:connect(Grid, command_list_item_activated), 70 | wxListCtrl:connect(Grid, command_list_item_selected), 71 | wxListCtrl:connect(Grid, command_list_col_click), 72 | wxListCtrl:connect(Grid, size, [{skip, true}]), 73 | 74 | {SortKey, SortReversed} = 75 | case lists:keyfind(true, #table_column.default_sort, Columns) of 76 | #table_column{binding = Binding, default_reversed = DefaultReversed} -> {Binding, DefaultReversed}; 77 | false -> {(hd(Columns))#table_column.binding, (hd(Columns))#table_column.default_reversed} 78 | end, 79 | 80 | EvenColour = wxSystemSettings:getColour(?wxSYS_COLOUR_BTNFACE), 81 | OddColour = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW), 82 | 83 | State = #state{grid = Grid, parent = Parent, data = Data, data_ref = DataRef, columns = Columns, sort_key = SortKey, sort_reversed = SortReversed, 84 | on_select = OnSelect, on_activate = OnActivate, even_colour = EvenColour, odd_colour = OddColour}, 85 | State1 = update_grid(State), 86 | 87 | wxWindow:setFocus(Grid), 88 | {Grid, State1}. 89 | 90 | handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, State) -> 91 | #state{grid = Grid, columns = Columns, sort_key = SortKey, sort_reversed = SortReversed} = State, 92 | Column = lists:nth(Col + 1, Columns), 93 | State1 = 94 | case Column#table_column.binding of 95 | SortKey -> State#state{sort_reversed = not SortReversed}; 96 | NewKey -> State#state{sort_key = NewKey, sort_reversed = Column#table_column.default_reversed} 97 | end, 98 | State2 = update_grid(State1), 99 | wxWindow:setFocus(Grid), 100 | {noreply, State2}; 101 | handle_event(#wx{event=#wxSize{size={_,_}}}, State) -> 102 | auto_size(State), 103 | {noreply, State}; 104 | handle_event(#wx{event=#wxList{type=command_list_item_activated,itemIndex=Index}}, State) -> 105 | #state{data = Data, on_activate = OnActivate} = State, 106 | case OnActivate of 107 | undefined -> ignore; 108 | _ -> 109 | DataRow = lists:nth(Index + 1, Data), 110 | OnActivate(DataRow) 111 | end, 112 | {noreply, State#state{selected=Index}}; 113 | handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, State) -> 114 | #state{data = Data, on_select = OnSelect} = State, 115 | case OnSelect of 116 | undefined -> ignore; 117 | _ -> 118 | DataRow = lists:nth(Index + 1, Data), 119 | OnSelect(DataRow) 120 | end, 121 | {noreply, State#state{selected=Index}}; 122 | 123 | handle_event(Event, _State) -> 124 | error({unhandled_event, Event}). 125 | 126 | handle_sync_event(_Event, _Obj, _State) -> 127 | ok. 128 | 129 | handle_call(Event, From, _State) -> 130 | error({unhandled_call, Event, From}). 131 | 132 | handle_cast({set_data, DataTag}, State) -> 133 | Data = vprof_data:info(State#state.data_ref, DataTag), 134 | State1 = update_grid(State#state{data = Data}), 135 | {noreply, State1}; 136 | handle_cast(Event, _State) -> 137 | error({unhandled_cast, Event}). 138 | 139 | handle_info({error, Error}, State) -> 140 | handle_error(Error), 141 | {noreply, State}; 142 | handle_info(_Event, State) -> 143 | {noreply, State}. 144 | 145 | terminate(_Event, _State) -> 146 | ok. 147 | 148 | code_change(_, _, State) -> 149 | State. 150 | 151 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 152 | 153 | handle_error(Foo) -> 154 | Str = io_lib:format("ERROR: ~s~n",[Foo]), 155 | observer_lib:display_info_dialog(Str). 156 | 157 | update_grid(State) -> 158 | wx:batch(fun() -> do_update_grid(State) end). 159 | 160 | do_update_grid(State) -> 161 | #state{grid = Grid, columns = Columns, sort_key = SortKey, sort_reversed = Reversed, data = Data, 162 | even_colour = EvenColour, odd_colour = OddColour} = State, 163 | Data1 = lists:keysort(SortKey, Data), 164 | Data2 = 165 | case Reversed of 166 | true -> lists:reverse(Data1); 167 | false -> Data1 168 | end, 169 | wxListCtrl:deleteAllItems(Grid), 170 | Update = 171 | fun(DataRow, RowIndex) -> 172 | _Item = wxListCtrl:insertItem(Grid, RowIndex, ""), 173 | if RowIndex rem 2 =:= 0 -> 174 | wxListCtrl:setItemBackgroundColour(Grid, RowIndex, EvenColour); 175 | true -> 176 | wxListCtrl:setItemBackgroundColour(Grid, RowIndex, OddColour) 177 | end, 178 | 179 | lists:foreach( 180 | fun({ColIndex, Column}) -> 181 | Val = element(Column#table_column.binding, DataRow), 182 | wxListCtrl:setItem(Grid, RowIndex, ColIndex, format(Column#table_column.format, Val)) 183 | end, 184 | vprof_q:seq_zip(Columns, 0)), 185 | 186 | RowIndex + 1 187 | end, 188 | lists:foldl(Update, 0, Data2), 189 | State#state{data = Data2}. 190 | 191 | auto_size(State) -> 192 | wx:batch(fun() -> do_auto_size(State) end). 193 | 194 | do_auto_size(State) -> 195 | #state{grid = Grid, columns = Columns} = State, 196 | TotalSize = element(1, wxWindow:getClientSize(Grid)) - scroll_size(Grid), 197 | {_, LeftSize, AutoSizeColumns} = lists:foldl( 198 | fun(Column, {I, Sz, Cs}) -> 199 | case Column#table_column.auto_size of 200 | true -> 201 | {I + 1, Sz, [I|Cs]}; 202 | false -> 203 | {I + 1, Sz - wxListCtrl:getColumnWidth(Grid, I), Cs} 204 | end 205 | end, {0, TotalSize, []}, Columns), 206 | case AutoSizeColumns of 207 | [] -> ignore; 208 | _ -> 209 | AutoSize = max(LeftSize div length(AutoSizeColumns), 50), 210 | [ wxListCtrl:setColumnWidth(Grid, I, AutoSize) || I <- AutoSizeColumns ], 211 | ok 212 | end. 213 | 214 | scroll_size(LCtrl) -> 215 | case os:type() of 216 | {win32, nt} -> 0; 217 | {unix, darwin} -> 218 | wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); 219 | _ -> 220 | case wxWindow:hasScrollbar(LCtrl, ?wxVERTICAL) of 221 | true -> wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); 222 | false -> 0 223 | end 224 | end. 225 | 226 | format(float, Value) when is_number(Value) -> 227 | float_to_list(float(Value)); 228 | format({float, P}, Value) when is_number(Value) -> 229 | float_to_list(float(Value), [{decimals, P}]); 230 | format(mfa, {Mod,Fun,A}) -> 231 | io_lib:format("~p:~p/~p", [Mod,Fun,A]); 232 | format(string, Value) when is_list(Value) -> 233 | io_lib:format("~s", [Value]); 234 | format(_, Value) -> 235 | io_lib:format("~p", [Value]). 236 | -------------------------------------------------------------------------------- /src/vprof_gui.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 29.11.2013 3 | 4 | -module(vprof_gui). 5 | 6 | -behaviour(wx_object). 7 | 8 | %% Include files 9 | -include_lib("wx/include/wx.hrl"). 10 | -include("vprof.hrl"). 11 | 12 | %% Exported Functions 13 | 14 | -export([ 15 | start/1, 16 | start_link/1, 17 | init/1, 18 | handle_event/2, 19 | handle_cast/2, 20 | handle_call/3, 21 | handle_info/2, 22 | terminate/2, 23 | code_change/3 24 | ]). 25 | 26 | -record(state, { 27 | data, 28 | frame, 29 | menubar, 30 | menus = [], 31 | status_bar, 32 | notebook, 33 | main_panel, 34 | active_tab, 35 | pages = [] 36 | }). 37 | 38 | -record(page, { 39 | id, 40 | module, 41 | content 42 | }). 43 | 44 | -define(ID_NOTEBOOK, 3). 45 | 46 | %%%=================================================================== 47 | %%% API 48 | %%%=================================================================== 49 | start(Data) -> 50 | wx_object:start(?MODULE, Data, []). 51 | 52 | start_link(Data) -> 53 | wx_object:start_link(?MODULE, Data, []). 54 | 55 | init(Data) -> 56 | wx:new(), 57 | Frame = wxFrame:new(wx:null(), ?wxID_ANY, "vprof analysis visualizer", [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]), 58 | State = #state{frame = Frame, data = Data}, 59 | UpdState = setup(State), 60 | process_flag(trap_exit, true), 61 | {Frame, UpdState}. 62 | 63 | handle_event(#wx{event = #wxClose{}}, State) -> 64 | {stop, normal, State}; 65 | handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) -> 66 | {stop, normal, State}; 67 | handle_event(#wx{id = ?wxID_ABOUT, event = #wxCommand{type = command_menu_selected}}, 68 | State = #state{frame=Frame}) -> 69 | AboutString = "vprof analysis visualizer", 70 | Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP}, {caption, "About"}], 71 | wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)), 72 | {noreply, State}; 73 | handle_event(_Event, State) -> 74 | {noreply, State}. 75 | 76 | handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) -> 77 | wxStatusBar:setStatusText(SB, Msg), 78 | {noreply, State}; 79 | handle_cast(_Cast, State) -> 80 | {noreply, State}. 81 | 82 | handle_call(_Msg, _From, State) -> 83 | {reply, ok, State}. 84 | 85 | handle_info({'EXIT', Pid, _Reason}, State) -> 86 | io:format("Child crashed exiting: ~p ~p~n", [Pid,_Reason]), 87 | {stop, normal, State}; 88 | handle_info({open_page, Content, Title, Module}, State) -> 89 | State1 = open_page(State, Content, Title, Module), 90 | {noreply, State1}; 91 | handle_info(_Info, State) -> 92 | {noreply, State}. 93 | 94 | terminate(_Reason, #state{frame = Frame}) -> 95 | wxFrame:destroy(Frame), 96 | ok. 97 | 98 | code_change(_, _, State) -> 99 | {ok, State}. 100 | 101 | %%%=================================================================== 102 | %%% Internal functions 103 | %%%=================================================================== 104 | setup(#state{frame = Frame} = State) -> 105 | %% Setup Menubar & Menus 106 | MenuBar = wxMenuBar:new(), 107 | 108 | DefMenus = default_menus(), 109 | create_menus(DefMenus, MenuBar, default), 110 | 111 | wxFrame:setMenuBar(Frame, MenuBar), 112 | StatusBar = wxFrame:createStatusBar(Frame, []), 113 | Filename = vprof_data:filename(State#state.data), 114 | wxFrame:setTitle(Frame, Filename), 115 | wxStatusBar:setStatusText(StatusBar, Filename), 116 | 117 | %% Setup panels 118 | Panel = wxPanel:new(Frame, []), 119 | Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), 120 | 121 | Data = State#state.data, 122 | 123 | FunsPanel = vprof_functions:start_link(Notebook, Data, self()), 124 | wxNotebook:addPage(Notebook, FunsPanel, "Functions", []), 125 | 126 | ProcessesPanel = vprof_processes:start_link(Notebook, Data, self()), 127 | wxNotebook:addPage(Notebook, ProcessesPanel, "Processes", []), 128 | 129 | %% Setup sizer create early to get it when window shows 130 | MainSizer = wxBoxSizer:new(?wxVERTICAL), 131 | 132 | wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), 133 | wxPanel:setSizer(Panel, MainSizer), 134 | 135 | wxNotebook:connect(Notebook, command_notebook_page_changing), 136 | wxFrame:connect(Frame, close_window, [{skip, true}]), 137 | wxMenu:connect(Frame, command_menu_selected), 138 | wxFrame:show(Frame), 139 | 140 | %% Force redraw (window needs it) 141 | wxWindow:refresh(Panel), 142 | wxWindow:setFocus(Panel), 143 | 144 | SysPid = wx_object:get_pid(FunsPanel), 145 | SysPid ! {active, node()}, 146 | UpdState = State#state{main_panel = Panel, 147 | notebook = Notebook, 148 | menubar = MenuBar, 149 | status_bar = StatusBar, 150 | active_tab = SysPid}, 151 | UpdState. 152 | 153 | -record(create_menu, { 154 | id, 155 | text, 156 | help = [], 157 | type = append, 158 | check = false 159 | }). 160 | 161 | default_menus() -> 162 | Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"}, 163 | About = #create_menu{id = ?wxID_ABOUT, text = "About"}, 164 | [{"&File", [Quit]}, {"&Help", [About]}]. 165 | 166 | create_menus([], _MenuBar, _Type) -> ok; 167 | create_menus(Menus, MenuBar, Type) -> 168 | Add = fun({Tag, Ms}, Index) -> 169 | create_menu(Tag, Ms, Index, MenuBar, Type) 170 | end, 171 | wx:foldl(Add, 0, Menus), 172 | ok. 173 | 174 | create_menu(Name, MenuItems, Index, MenuBar, _Type) -> 175 | Menu = wxMenu:new(), 176 | lists:foldl(fun(Record, N) -> 177 | create_menu_item(Record, Menu, N) 178 | end, 0, MenuItems), 179 | wxMenuBar:insert(MenuBar, Index, Menu, Name), 180 | Index+1. 181 | 182 | create_menu_item(#create_menu{id = ?wxID_HELP=Id}, Menu, Index) -> 183 | wxMenu:insert(Menu, Index, Id), 184 | Index+1; 185 | create_menu_item(#create_menu{id=Id, text=Text, help=Help, type=Type, check=Check}, 186 | Menu, Index) -> 187 | Opts = case Help of 188 | [] -> []; 189 | _ -> [{help, Help}] 190 | end, 191 | case Type of 192 | append -> 193 | wxMenu:insert(Menu, Index, Id, 194 | [{text, Text}|Opts]); 195 | check -> 196 | wxMenu:insertCheckItem(Menu, Index, Id, Text, Opts), 197 | wxMenu:check(Menu, Id, Check); 198 | radio -> 199 | wxMenu:insertRadioItem(Menu, Index, Id, Text, Opts), 200 | wxMenu:check(Menu, Id, Check); 201 | separator -> 202 | wxMenu:insertSeparator(Menu, Index) 203 | end, 204 | Index+1; 205 | create_menu_item(separator, Menu, Index) -> 206 | wxMenu:insertSeparator(Menu, Index), 207 | Index+1. 208 | 209 | find_page(Notebook, Id) -> 210 | PageCount = wxNotebook:getPageCount(Notebook), 211 | hd([ N || N <- lists:seq(0, PageCount - 1), wxWindow:getId(wxNotebook:getPage(Notebook, N)) =:= Id ]). 212 | 213 | open_page(State, Content, Title, Module) -> 214 | #state{pages = Pages, notebook = Notebook, data = Data} = State, 215 | case lists:keyfind(Content, #page.content, Pages) of 216 | #page{id = Id} -> 217 | PageIndex = find_page(Notebook, Id), 218 | wxNotebook:setSelection(Notebook, PageIndex), 219 | State; 220 | false -> 221 | Panel = Module:start_link(Notebook, Data, self(), Content), 222 | wxNotebook:addPage(Notebook, Panel, Title, [{bSelect, true}]), 223 | Id = wxWindow:getId(Panel), 224 | NewPages = [ #page{id = Id, module = Module, content = Content} | Pages ], 225 | State#state{pages = NewPages} 226 | end. 227 | -------------------------------------------------------------------------------- /src/vprof_process.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 13.08.2014 3 | 4 | -module(vprof_process). 5 | 6 | -behaviour(wx_object). 7 | 8 | -include_lib("wx/include/wx.hrl"). 9 | -include("vprof.hrl"). 10 | -include("wx_compat.hrl"). 11 | 12 | -export([ 13 | start_link/4 14 | ]). 15 | 16 | %% wx_object callbacks 17 | -export([ 18 | init/1, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3, 22 | handle_call/3, 23 | handle_event/2, 24 | handle_sync_event/3, 25 | handle_cast/2]). 26 | 27 | 28 | -record(state, { 29 | parent, 30 | pid, 31 | called_by_grid, 32 | selected_grid, 33 | calling_grid 34 | }). 35 | 36 | start_link(Notebook, Data, Parent, Pid) -> 37 | wx_object:start_link(?MODULE, {Notebook, Data, Parent, Pid}, []). 38 | 39 | init({Notebook, Data, Parent, Pid}) -> 40 | Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), 41 | 42 | Splitter = vprof_wx_utils:splitter(Panel), 43 | GridLeft = functions_grid(Splitter, Data, Pid), 44 | PanelRight = wxPanel:new(Splitter, [{size, wxWindow:getClientSize(Splitter)}]), 45 | vprof_wx_utils:split_vertical(Splitter, 0.5, GridLeft, PanelRight), 46 | 47 | CalledByGrid = called_by_grid(PanelRight, Data, Pid, unknown), 48 | SelectedGrid = selected_grid(PanelRight, Data, Pid, unknown), 49 | CallingGrid = calling_grid(PanelRight, Data, Pid, unknown), 50 | 51 | Box = wxBoxSizer:new(?wxVERTICAL), 52 | wxBoxSizer:add(Box, CalledByGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 53 | Item = wxBoxSizer:add(Box, 100, 50, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), 54 | wxSizerItem:?assignWindow(Item, SelectedGrid), 55 | wxSizerItem:setMinSize(Item, 50, 50), 56 | wxBoxSizer:add(Box, CallingGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 57 | wxWindow:setSizer(PanelRight, Box), 58 | 59 | State = #state{parent = Parent, pid = Pid, called_by_grid = CalledByGrid, calling_grid = CallingGrid, selected_grid = SelectedGrid}, 60 | 61 | {Panel, State}. 62 | 63 | functions_grid(Panel, Data, Pid) -> 64 | GridColumns = [ 65 | #table_column{title = "Function", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 66 | format = mfa, default_width = 200, binding = #row.mfa}, 67 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 68 | binding = #row.cnt, default_reversed = true}, 69 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 70 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 71 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 72 | format = {float, 3}, binding = #row.own, default_reversed = true} 73 | ], 74 | Self = self(), 75 | OnSelect = fun(DataRow) -> Self ! {select_function, DataRow} end, 76 | vprof_grid:start_link(Panel, self(), Data, {calls, Pid}, GridColumns, OnSelect, undefined). 77 | 78 | called_by_grid(Panel, Data, Pid, MFA) -> 79 | Columns = [ 80 | #table_column{title = "Called By", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 81 | format = mfa, default_width = 200, binding = #row.mfa}, 82 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 83 | binding = #row.cnt, default_reversed = true}, 84 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 85 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 86 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 87 | format = {float, 3}, binding = #row.own, default_reversed = true} 88 | ], 89 | Self = self(), 90 | OnActivate = fun(DataRow) -> Self ! {select_function, DataRow} end, 91 | vprof_grid:start_link(Panel, self(), Data, {called_by, Pid, MFA}, Columns, undefined, OnActivate). 92 | 93 | selected_grid(Panel, Data, Pid, MFA) -> 94 | Columns = [ 95 | #table_column{title = "Function", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 96 | format = mfa, default_width = 200, binding = #row.mfa}, 97 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 98 | binding = #row.cnt, default_reversed = true}, 99 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 100 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 101 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 102 | format = {float, 3}, binding = #row.own, default_reversed = true} 103 | ], 104 | vprof_grid:start_link(Panel, self(), Data, {mfa, Pid, MFA}, Columns, undefined, undefined). 105 | 106 | calling_grid(Panel, Data, Pid, MFA) -> 107 | Columns = [ 108 | #table_column{title = "Calling", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 109 | format = mfa, default_width = 200, binding = #row.mfa}, 110 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 111 | binding = #row.cnt, default_reversed = true}, 112 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 113 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 114 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 115 | format = {float, 3}, binding = #row.own, default_reversed = true} 116 | ], 117 | Self = self(), 118 | OnActivate = fun(DataRow) -> Self ! {select_function, DataRow} end, 119 | vprof_grid:start_link(Panel, self(), Data, {calling, Pid, MFA}, Columns, undefined, OnActivate). 120 | 121 | handle_event(Event, _State) -> 122 | error({unhandled_event, Event}). 123 | 124 | handle_sync_event(_Event, _Obj, _State) -> 125 | ok. 126 | 127 | handle_call(Event, From, _State) -> 128 | error({unhandled_call, Event, From}). 129 | 130 | handle_cast(Event, _State) -> 131 | error({unhandled_cast, Event}). 132 | 133 | handle_info({select_function, DataRow}, State) -> 134 | vprof_grid:set_data(State#state.called_by_grid, {called_by, State#state.pid, DataRow#row.mfa}), 135 | vprof_grid:set_data(State#state.calling_grid, {calling, State#state.pid, DataRow#row.mfa}), 136 | vprof_grid:set_data(State#state.selected_grid, {mfa, State#state.pid, DataRow#row.mfa}), 137 | {noreply, State}; 138 | handle_info({error, Error}, State) -> 139 | handle_error(Error), 140 | {noreply, State}; 141 | handle_info(_Event, State) -> 142 | {noreply, State}. 143 | 144 | terminate(_Event, _State) -> 145 | ok. 146 | 147 | code_change(_, _, State) -> 148 | State. 149 | 150 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 151 | 152 | handle_error(Foo) -> 153 | Str = io_lib:format("ERROR: ~s~n",[Foo]), 154 | observer_lib:display_info_dialog(Str). 155 | 156 | -------------------------------------------------------------------------------- /src/vprof_processes.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 13.08.2014 3 | 4 | -module(vprof_processes). 5 | 6 | -behaviour(wx_object). 7 | 8 | -include_lib("wx/include/wx.hrl"). 9 | -include("vprof.hrl"). 10 | -include("wx_compat.hrl"). 11 | 12 | -export([ 13 | start_link/3 14 | ]). 15 | 16 | %% wx_object callbacks 17 | -export([ 18 | init/1, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3, 22 | handle_call/3, 23 | handle_event/2, 24 | handle_sync_event/3, 25 | handle_cast/2]). 26 | 27 | 28 | -record(state, { 29 | parent, 30 | processes_grid 31 | }). 32 | 33 | start_link(Notebook, Data, Parent) -> 34 | wx_object:start_link(?MODULE, {Notebook, Data, Parent}, []). 35 | 36 | init({Notebook, Data, Parent}) -> 37 | Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), 38 | 39 | Splitter = vprof_wx_utils:splitter(Panel), 40 | LeftPanel = wxPanel:new(Splitter, [{size, wxWindow:getClientSize(Splitter)}]), 41 | ProcessGrid = process_grid(Splitter, Data, undefined), 42 | vprof_wx_utils:split_vertical(Splitter, 0.3, LeftPanel, ProcessGrid), 43 | 44 | TotalsGrid = totals_grid(LeftPanel, Data, Parent), 45 | SummaryGrid = summary_grid(LeftPanel, Data), 46 | Box = wxBoxSizer:new(?wxVERTICAL), 47 | wxBoxSizer:add(Box, TotalsGrid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 48 | Item = wxBoxSizer:add(Box, 100, 50, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), 49 | wxSizerItem:?assignWindow(Item, SummaryGrid), 50 | wxSizerItem:setMinSize(Item, 60, 60), 51 | wxWindow:setSizer(LeftPanel, Box), 52 | 53 | State = #state{parent = Parent, processes_grid = ProcessGrid}, 54 | 55 | {Panel, State}. 56 | 57 | summary_grid(Panel, Data) -> 58 | GridColumns = [ 59 | #table_column{title = "Total", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 60 | format = string, default_width = 200, binding = #process.pid}, 61 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 62 | binding = #process.cnt, default_reversed = true}, 63 | #table_column{title = "Schedules", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 64 | binding = #process.schedules, default_reversed = true}, 65 | #table_column{title = "Time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 66 | format = {float, 3}, binding = #process.own, default_reversed = true, default_sort = true} 67 | ], 68 | vprof_grid:start_link(Panel, self(), Data, processes_summary, GridColumns, undefined, undefined). 69 | 70 | totals_grid(Panel, Data, Parent) -> 71 | GridColumns = [ 72 | #table_column{title = "PID", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 73 | format = string, default_width = 200, binding = #process.pid}, 74 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 75 | binding = #process.cnt, default_reversed = true}, 76 | #table_column{title = "Schedules", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 77 | binding = #process.schedules, default_reversed = true}, 78 | #table_column{title = "Time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 79 | format = {float, 3}, binding = #process.own, default_reversed = true, default_sort = true} 80 | ], 81 | Self = self(), 82 | OnSelect = fun(DataRow) -> Self ! {select_process, DataRow} end, 83 | OnActivate = fun(DataRow) -> Parent ! {open_page, DataRow#process.pid, DataRow#process.pid, vprof_process} end, 84 | vprof_grid:start_link(Panel, self(), Data, processes, GridColumns, OnSelect, OnActivate). 85 | 86 | process_grid(Panel, Data, Process) -> 87 | Columns = [ 88 | #table_column{title = "Function", auto_size = true, alignment = ?wxLIST_FORMAT_LEFT, 89 | format = mfa, default_width = 70, binding = #row.mfa}, 90 | #table_column{title = "Count", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 91 | binding = #row.cnt, default_reversed = true}, 92 | #table_column{title = "Acc time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 93 | format = {float, 3}, binding = #row.acc, default_reversed = true, default_sort = true}, 94 | #table_column{title = "Own time", alignment = ?wxLIST_FORMAT_RIGHT, default_width = 100, 95 | format = {float, 3}, binding = #row.own, default_reversed = true} 96 | ], 97 | vprof_grid:start_link(Panel, self(), Data, {calls, Process}, Columns, undefined, undefined). 98 | 99 | handle_event(Event, _State) -> 100 | error({unhandled_event, Event}). 101 | 102 | handle_sync_event(_Event, _Obj, _State) -> 103 | ok. 104 | 105 | handle_call(Event, From, _State) -> 106 | error({unhandled_call, Event, From}). 107 | 108 | handle_cast(Event, _State) -> 109 | error({unhandled_cast, Event}). 110 | 111 | handle_info({select_process, DataRow}, State) -> 112 | vprof_grid:set_data(State#state.processes_grid, {calls, DataRow#process.pid}), 113 | {noreply, State}; 114 | handle_info(_Event, State) -> 115 | {noreply, State}. 116 | 117 | terminate(_Event, _State) -> 118 | ok. 119 | 120 | code_change(_, _, State) -> 121 | State. 122 | 123 | -------------------------------------------------------------------------------- /src/vprof_profile.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 01.12.2013 3 | 4 | -module(vprof_profile). 5 | 6 | %% Include files 7 | -include("vprof.hrl"). 8 | 9 | %% Exported Functions 10 | 11 | -export([ 12 | profile/2 13 | ]). 14 | 15 | -record(proc, { 16 | id = 0, 17 | pid, 18 | root_id = 0, 19 | own = 0, 20 | own_start_ts, 21 | schedule_out_ts, 22 | ts_shift = 0, 23 | stack = [], 24 | calls = array:new(), 25 | mfas = dict:new(), 26 | schedules = 1 27 | }). 28 | 29 | -record(call, { 30 | id, 31 | mfa, 32 | own = 0, 33 | acc = 0, 34 | own_start_ts, 35 | acc_start_ts, 36 | parent, 37 | children = [], 38 | recursion 39 | }). 40 | 41 | %%%=================================================================== 42 | %%% API 43 | %%%=================================================================== 44 | profile(InFile, OutFile) -> 45 | catch file:delete(OutFile), 46 | io:format("vprof: Profiling ~s. This may take several minutes.~n", [InFile]), 47 | dbg:trace_client(file, InFile, {fun collect/2, {undefined, [], OutFile, os:timestamp()}}). 48 | 49 | get_process_and_timestamp(#proc{pid = Pid, ts_shift = TsShift} = Process, Pid, Timestamp, Pids) -> 50 | {Process, Timestamp - TsShift, Pids}; 51 | get_process_and_timestamp(PrevProcess, Pid, Timestamp, Pids) -> 52 | if PrevProcess =:= undefined -> ok; 53 | true -> 54 | put(PrevProcess#proc.pid, PrevProcess) 55 | end, 56 | case get(Pid) of 57 | undefined -> 58 | {#proc{own_start_ts = Timestamp, pid = Pid}, Timestamp, [Pid|Pids]}; 59 | Process -> 60 | {Process, Timestamp - Process#proc.ts_shift, Pids} 61 | end. 62 | 63 | collect(Trace, {PrevProcess, Pids, OutFile, StartTime}) when element(1, Trace) =:= trace_ts -> 64 | Pid = element(2, Trace), 65 | Type = element(3, Trace), 66 | MFA = element(4, Trace), 67 | AbsTimestamp = ts(element(size(Trace), Trace)), 68 | {Process, Timestamp, Pids1} = get_process_and_timestamp(PrevProcess, Pid, AbsTimestamp, Pids), 69 | NewProcess = 70 | case Type of 71 | call -> 72 | call(Process, Timestamp, MFA); 73 | return_to -> 74 | case Process#proc.stack of 75 | [] -> 76 | call(Process, Timestamp, MFA); 77 | _ -> 78 | return_to(Process, Timestamp, MFA) 79 | end; 80 | in -> 81 | in(Process, Timestamp, MFA); 82 | out -> 83 | out(Process, Timestamp, MFA); 84 | _ -> 85 | Process 86 | end, 87 | {NewProcess, Pids1, OutFile, StartTime}; 88 | collect(end_of_trace, {PrevProcess, Pids, OutFile, StartTime}) -> 89 | put(PrevProcess#proc.pid, PrevProcess), 90 | TimeDiff1 = timer:now_diff(os:timestamp(), StartTime) div 1000000, 91 | io:format("vprof: End of trace in ~p seconds.~n", [TimeDiff1]), 92 | Res = lists:foldl( 93 | fun(Pid, Acc) -> 94 | Process = get(Pid), 95 | close(Pid, Process, Acc) 96 | end, {[], []}, Pids), 97 | write(OutFile, Res), 98 | TimeDiff = timer:now_diff(os:timestamp(), StartTime) div 1000000, 99 | io:format("vprof: Done in ~p seconds.~n", [TimeDiff]), 100 | io:format("Check ~s to see results or run~nvprof:gui(\"~s\").~n", [OutFile, OutFile]), 101 | ok. 102 | 103 | write(FileName, Term) -> 104 | {ok, File} = file:open(FileName, [write]), 105 | io:format(File, "~p.~n", [Term]), 106 | file:close(File). 107 | 108 | call(#proc{stack = [#call{mfa = MFA}|_]} = Process, _Timestamp, MFA) -> 109 | Process; 110 | call(#proc{stack = [], id = NewId} = Process, Timestamp, MFA) -> 111 | New = #call{mfa = MFA, own_start_ts = Timestamp, acc_start_ts = Timestamp, id = NewId}, 112 | Process#proc{stack = [New], id = NewId + 1, root_id = NewId}; 113 | call(#proc{stack = [Cur|Stack], id = NewId} = Process, Timestamp, MFA) -> 114 | #call{own_start_ts = OwnStart, own = Own, id = CurId, children = CurChildren} = Cur, 115 | Cur1 = Cur#call{own = Own + Timestamp - OwnStart, own_start_ts = undefined, children = [NewId|CurChildren]}, 116 | ParentRecursion = 117 | case lists:keyfind(MFA, #call.mfa, Stack) of 118 | false -> undefined; 119 | #call{id = RecId} -> RecId 120 | end, 121 | New = #call{mfa = MFA, own_start_ts = Timestamp, acc_start_ts = Timestamp, id = NewId, parent = CurId, recursion = ParentRecursion}, 122 | Process#proc{stack = [New,Cur1|Stack], id = NewId + 1}. 123 | 124 | out(Process, Timestamp, MFA) -> 125 | Process1 = call(Process, Timestamp, MFA), 126 | Process1#proc{schedule_out_ts = Timestamp}. 127 | 128 | in(#proc{stack = [], schedule_out_ts = undefined} = Process, Timestamp, MFA) -> 129 | call(Process, Timestamp, MFA); 130 | in(#proc{ts_shift = TsShift, schedule_out_ts = ScheduledOutTs, schedules = Schedules} = Process, Timestamp, MFA) -> 131 | TsShiftDelta = Timestamp - ScheduledOutTs, 132 | TsShift1 = TsShift + TsShiftDelta, 133 | Process1 = Process#proc{ts_shift = TsShift1, schedule_out_ts = undefined, schedules = Schedules + 1}, 134 | return_to(Process1, Timestamp - TsShiftDelta, MFA). 135 | 136 | return(#proc{stack = [Cur|Stack], calls = Calls, mfas = MFAs} = Process, Timestamp) -> 137 | #call{mfa = MFA, own_start_ts = OwnStart, own = Own, acc = Acc, acc_start_ts = AccStart, id = Id, children = Children} = Cur, 138 | Acc1 = 139 | case AccStart of 140 | undefined -> Acc; 141 | _ -> Acc + Timestamp - AccStart 142 | end, 143 | Own1 = 144 | case OwnStart of 145 | undefined -> Own; 146 | _ -> Own + Timestamp - OwnStart 147 | end, 148 | Cur1 = Cur#call{own = Own1, acc = Acc1, own_start_ts = undefined, acc_start_ts = undefined, children = Children}, 149 | Calls1 = array:set(Id, Cur1, Calls), 150 | MFAs1 = dict:append(MFA, Id, MFAs), 151 | Process#proc{stack = Stack, calls = Calls1, mfas = MFAs1}. 152 | 153 | insert_head(#proc{stack = [Cur], own_start_ts = StartTs, id = NewId, calls = Calls, mfas = MFAs} = Process, _Timestamp, ToMFA) -> 154 | #call{id = Id} = Cur, 155 | Cur1 = Cur#call{parent = NewId}, 156 | Calls1 = 157 | case dict:find(ToMFA, MFAs) of 158 | {ok, Ids} -> 159 | lists:foldl( 160 | fun(I, Cs) -> 161 | C = array:get(I, Cs), 162 | C1 = 163 | if C#call.recursion =:= undefined -> C#call{recursion = NewId}; 164 | true -> C 165 | end, 166 | array:set(I, C1, Cs) 167 | end, Calls, Ids); 168 | error -> 169 | Calls 170 | end, 171 | New = #call{mfa = ToMFA, acc_start_ts = StartTs, id = NewId, children=[Id]}, 172 | Process#proc{stack = [Cur1,New], id = NewId + 1, root_id = NewId, calls = Calls1}. 173 | 174 | return_to(#proc{stack = [#call{mfa = MFA, own_start_ts = OwnStartTs} = Cur|Stack]} = Process, Timestamp, MFA) -> 175 | StartTs = if OwnStartTs =:= undefined -> Timestamp; true -> OwnStartTs end, 176 | Cur1 = Cur#call{own_start_ts = StartTs}, 177 | Process#proc{stack = [Cur1|Stack]}; 178 | return_to(#proc{stack = [_]} = Process, Timestamp, unknown) -> 179 | P0 = insert_head(Process, Timestamp, unknown), 180 | P1 = return(P0, Timestamp), 181 | P2 = return_to(P1, Timestamp, unknown), 182 | return(P2, Timestamp); 183 | return_to(Process, _Timestamp, {array,_,_}) -> 184 | Process; 185 | return_to(#proc{stack = [_]} = Process, Timestamp, MFA) -> 186 | P0 = insert_head(Process, Timestamp, MFA), 187 | P1 = return(P0, Timestamp), 188 | return_to(P1, Timestamp, MFA); 189 | return_to(#proc{stack = [_|_]} = Process, Timestamp, MFA) -> 190 | P1 = return(Process, Timestamp), 191 | return_to(P1, Timestamp, MFA); 192 | return_to(#proc{stack = []} = Process, _Timestamp, _MFA) -> 193 | Process. 194 | 195 | 196 | %%%=================================================================== 197 | %%% Internal functions 198 | %%%=================================================================== 199 | ts({Mega, Secs, Micro}) -> (Mega * 1000000000000) + (Secs * 1000000) + Micro. 200 | 201 | last_ts(#proc{schedule_out_ts = ScheduleOutTs}) when ScheduleOutTs =/= undefined -> 202 | ScheduleOutTs; 203 | last_ts(#proc{schedule_out_ts = undefined, stack = [#call{own_start_ts = LastTs}|_]}) -> 204 | LastTs; 205 | last_ts(#proc{own_start_ts = Ts}) -> 206 | Ts. 207 | 208 | process_own(#proc{own_start_ts = OwnStartTs} = Process, LastTs) -> 209 | Process#proc{own = LastTs - OwnStartTs}. 210 | 211 | close(Pid, Process, Result) -> 212 | LastTs = last_ts(Process), 213 | Process1 = process_own(Process, LastTs), 214 | Process2 = return_to(Process1, LastTs, unknown), 215 | result(Pid, Process2, Result). 216 | 217 | result(Pid, Process, {Procs, Rows}) -> 218 | #proc{calls = Calls, own = Own, mfas = MFAs, schedules = Schedules} = Process, 219 | SPid = convert_pid(Pid), 220 | Rows1 = lists:reverse(lists:keysort(#function.acc, dict:fold( 221 | fun(MFA, CallIds, Acc) -> 222 | Cs = [ array:get(Id, Calls) || Id <- CallIds ], 223 | FetchCall = fun(Id) -> array:get(Id, Calls) end, 224 | Parents = 225 | [ 226 | #ref{mfa = MFA1, 227 | cnt = length(Css), 228 | own = ms_to_s(vprof_q:keysum(#call.own, Css)), 229 | acc = ms_to_s(vprof_q:keysum(#call.acc, without_recursion(Css)))} 230 | || {MFA1, Css} <- vprof_q:qq(Cs, [ [#call.parent], ordset, fun(Cc) -> Cc -- [undefined] end, [FetchCall], {group, #call.mfa} ] ) ], 231 | Children = 232 | [ 233 | #ref{mfa = MFA1, 234 | cnt = length(Css), 235 | own = ms_to_s(vprof_q:keysum(#call.own, Css)), 236 | acc = ms_to_s(vprof_q:keysum(#call.acc, without_recursion(Css)))} 237 | || {MFA1, Css} <- vprof_q:qq(Cs, [ [#call.children], '++', [FetchCall], {group, #call.mfa} ] ) ], 238 | Row = #function{pid = SPid, cnt = length(CallIds), mfa = MFA, 239 | own = ms_to_s(vprof_q:keysum(#call.own, Cs)), 240 | acc = ms_to_s(vprof_q:keysum(#call.acc, [C || C <- Cs, C#call.recursion =:= undefined])), 241 | parents = Parents, 242 | children = Children 243 | }, 244 | [Row|Acc] 245 | end, [], MFAs))), 246 | Procs1 = [#process{pid = SPid, own = ms_to_s(Own), cnt = array:size(Calls), schedules = Schedules}|Procs], 247 | {Procs1, Rows1 ++ Rows}. 248 | 249 | without_recursion(Cs) -> 250 | CallIds = [ CallId || #call{id = CallId} <- Cs ], 251 | [ C || C <- Cs, not lists:member(C#call.recursion, CallIds) ]. 252 | 253 | ms_to_s(MS) -> 254 | MS / 1000000. 255 | 256 | convert_pid(Pid) -> 257 | List = pid_to_list(Pid), 258 | [_A,B,C] = string:tokens(List, "."), 259 | string:join(["<0",B,C], "."). -------------------------------------------------------------------------------- /src/vprof_q.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 13.08.2014 3 | 4 | -module(vprof_q). 5 | 6 | %% Include files 7 | 8 | %% Exported Functions 9 | 10 | -export([ 11 | group/2, 12 | q/2, 13 | qq/2, 14 | keysum/2, 15 | seq_zip/2 16 | ]). 17 | 18 | %%%=================================================================== 19 | %%% API 20 | %%%=================================================================== 21 | group(K, List) -> 22 | lists:foldl( 23 | fun(Item, Acc) -> 24 | Key = q(K, Item), 25 | orddict:update(Key, fun(L) -> [Item|L] end, [Item], Acc) 26 | end, orddict:new(), List). 27 | 28 | q(N, V) when is_integer(N) -> 29 | element(N, V); 30 | q(id, V) -> 31 | V; 32 | q(max, L) -> 33 | lists:max(L); 34 | q(min, L) -> 35 | lists:min(L); 36 | q(sum, L) -> 37 | lists:sum(L); 38 | q(length, L) -> 39 | length(L); 40 | q('++', L) -> 41 | lists:append(L); 42 | q(ordset, L) -> 43 | ordsets:from_list(L); 44 | q({group, K}, L) -> 45 | group(K, L); 46 | q([Q], V) -> 47 | [ q(Q, I) || I <- V]; 48 | q(T, Item) when is_tuple(T) -> 49 | list_to_tuple([q(Q,Item) || Q <- tuple_to_list(T)]); 50 | q(F, Item) when is_function(F, 1) -> 51 | F(Item). 52 | 53 | qq(Source, [Q|Qs]) -> 54 | qq(q(Q, Source), Qs); 55 | qq(Source, []) -> 56 | Source. 57 | 58 | keysum(I, List) -> 59 | qq(List, [[I], sum]). 60 | 61 | seq_zip(List1, StartN) -> 62 | lists:zip(lists:seq(StartN, StartN + length(List1) - 1), List1). 63 | -------------------------------------------------------------------------------- /src/vprof_wx_utils.erl: -------------------------------------------------------------------------------- 1 | %% @author: Andrey Tsirulev 2 | %% @date: 13.08.2014 3 | 4 | -module(vprof_wx_utils). 5 | 6 | %% Include files 7 | -include_lib("wx/include/wx.hrl"). 8 | 9 | %% Exported Functions 10 | 11 | -export([ 12 | splitter/1, 13 | split_vertical/4, 14 | split_horizontal/4 15 | ]). 16 | 17 | %%%=================================================================== 18 | %%% API 19 | %%%=================================================================== 20 | 21 | splitter(Parent) -> 22 | wxSplitterWindow:new(Parent, [{size, wxWindow:getClientSize(Parent)}]). 23 | 24 | split_vertical(Splitter, Gravity, Child1, Child2) -> 25 | wxSplitterWindow:setSashGravity(Splitter, Gravity), 26 | wxSplitterWindow:setMinimumPaneSize(Splitter, 50), 27 | wxSplitterWindow:splitVertically(Splitter, Child1, Child2), 28 | 29 | Sizer = wxBoxSizer:new(?wxVERTICAL), 30 | wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 31 | wxWindow:setSizer(wxWindow:getParent(Splitter), Sizer). 32 | 33 | split_horizontal(Splitter, Gravity, Child1, Child2) -> 34 | wxSplitterWindow:setSashGravity(Splitter, Gravity), 35 | wxSplitterWindow:setMinimumPaneSize(Splitter, 50), 36 | wxSplitterWindow:splitHorizontally(Splitter, Child1, Child2), 37 | 38 | Sizer = wxBoxSizer:new(?wxHORIZONTAL), 39 | wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 40 | wxWindow:setSizer(wxWindow:getParent(Splitter), Sizer). 41 | 42 | %%%=================================================================== 43 | %%% Internal functions 44 | %%%=================================================================== 45 | 46 | 47 | 48 | 49 | 50 | --------------------------------------------------------------------------------