aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src/etop_gui.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer/src/etop_gui.erl')
-rw-r--r--lib/observer/src/etop_gui.erl362
1 files changed, 362 insertions, 0 deletions
diff --git a/lib/observer/src/etop_gui.erl b/lib/observer/src/etop_gui.erl
new file mode 100644
index 0000000000..ff1b8078ad
--- /dev/null
+++ b/lib/observer/src/etop_gui.erl
@@ -0,0 +1,362 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(etop_gui).
+-author('[email protected]').
+
+-export([init/1,stop/1]).
+-export([formatmfa/1,to_list/1]).% For etop_txt
+
+-include("etop.hrl").
+-include("etop_defs.hrl").
+
+-import(etop, [loadinfo/1, meminfo/2, getopt/2]).
+
+%% Heights
+-define(BarH, 28). % height of menubar
+-define(LabelH, 90). % height of label with system info
+-define(GridLineH, 21). % height of one line in the table (grid)
+
+%% Column numbers for grid - click to sort
+-define(TimeCol, 3).
+-define(RedsCol, 4).
+-define(MemCol, 5).
+-define(MsgQCol, 6).
+
+%% Font
+-define(Normal, {screen,12}).
+-define(Bold, {screen,bold,12}).
+
+
+%% -----------------------------------------------------------------------------
+stop(_) -> ok.
+
+init(Config) ->
+ S = gs:start(),
+ Width = getopt(width, Config),
+ TotLines = getopt(lines,Config)+1,
+
+ %% Max number of processes shown in window at startup is 10
+ %% If less than 10 lines is specified, window size fits number of lines
+ WinH = if TotLines > 11 -> 11*?GridLineH + ?BarH + ?LabelH;
+ true -> TotLines*?GridLineH + ?BarH + ?LabelH
+ end,
+ Win = gs:create(window, S,
+ [{title, "Erlang Top"},
+ {map, true}, %% While debugging
+ {configure, true},
+ {width, Width}, {height, WinH}]),
+ Bar = gs:create(menubar, Win, []),
+
+ FileButt = gs:create(menubutton, Bar, [{label,{text, " File "}}]),
+ OptionsButt = gs:create(menubutton, Bar, [{label,{text, " Options "}}]),
+ File = gs:create(menu, FileButt, []),
+ Options = gs:create(menu, OptionsButt, []),
+ gse:named_menuitem(refresh, File,
+ [{label,{text," Refresh "}}]),
+ gse:named_menuitem(dump, File,
+ [{label,{text," Dump to file "}}]),
+ gse:named_menuitem(exit, File,
+ [{label,{text," Exit "}}]),
+
+ gse:named_menuitem(accum, Options,
+ [{label,{text, " Accumulate "}},
+ {itemtype, check}]),
+ gse:named_menuitem(intv, Options,
+ [{label,{text, " Update Interval "}}]),
+ gse:named_menuitem(lines, Options,
+ [{label,{text, " Number of Lines "}}]),
+ Sort = gse:named_menuitem(sort, Options,
+ [{label,{text, " Sort "}},
+ {itemtype,cascade}]),
+ SortMenu = gse:create(menu, Sort, []),
+ gse:named_menuitem(runtime, SortMenu,
+ [{label,{text, " Time "}},
+ {itemtype,radio},{group,gr1}]),
+ gse:named_menuitem(memory, SortMenu,
+ [{label,{text, " Memory "}},
+ {itemtype,radio},{group,gr1}]),
+ gse:named_menuitem(reductions, SortMenu,
+ [{label,{text, " Reductions "}},
+ {itemtype,radio},{group,gr1}]),
+ gse:named_menuitem(msg_q, SortMenu,
+ [{label,{text, " Message Queue "}},
+ {itemtype,radio},{group,gr1}]),
+
+ SysInfo = gs:create(label,Win,[{x, 0}, {y, ?BarH},{align,sw},
+ {width, Width},{height,?LabelH}]),
+
+ {GridH,VScroll} = calc_grid_h(WinH,TotLines),
+ Grid = gse:grid(Win,
+ [{x, 0}, {y, ?BarH+?LabelH},
+ {width, Width},
+ {height, GridH},
+ {hscroll, false},
+ {vscroll, VScroll},
+ {columnwidths, calc_column_w(Width)},
+ {rows, {1, TotLines}},
+ {font,?Normal}]),
+
+ %% Header line
+ GL1 = gse:gridline(Grid, [{{text, 1}, "PID"},
+ {{text, 2}, "Name or Initial Function"},
+ {{text, ?TimeCol}, "Time(us)"},
+ {{text, ?RedsCol}, "Reds"},
+ {{text, ?MemCol}, "Memory"},
+ {{text, ?MsgQCol}, "MsgQ"},
+ {{text, 7}, "Current Function"},
+ {bg, lightblue},
+ {row, 1},
+ {click, true}]),
+
+ config_sort(GL1,getopt(sort,Config)),
+ Info = do_update(Grid, SysInfo, Config),
+
+ get_event(Info, Win, Grid, GL1, SysInfo, Config).
+
+calc_column_w(W) ->
+ %% W = [2x, 3x, 1x, 1x, 1x, 1x, 3x] = 12x
+ RW = W-9, % just to make nice small margins on each side of grid
+ X = RW div 12,
+ [2*X, 3*X, X, X, X, X, 3*X + (RW - 12*X)].
+
+config_sort(GL1,Sort) ->
+ gs:config(Sort,[{select,true}]),
+ lists:foreach(fun(S) ->
+ gs:config(GL1,[{{font,S},?Normal}])
+ end,
+ [?TimeCol,?MemCol,?RedsCol,?MsgQCol]),
+ case Sort of
+ runtime -> gs:config(GL1,{{font,?TimeCol},?Bold});
+ memory -> gs:config(GL1,{{font,?MemCol},?Bold});
+ reductions -> gs:config(GL1,{{font,?RedsCol},?Bold});
+ msg_q -> gs:config(GL1,{{font,?MsgQCol},?Bold})
+ end.
+
+config_lines(Win,Grid,TotLines) ->
+ OldGridH = gs:read(Grid,height),
+ NewLinesH = TotLines*?GridLineH,
+ if NewLinesH =< OldGridH ->
+ gs:config(Win,[{height,NewLinesH+?BarH+?LabelH}]),
+ gs:config(Grid,[{rows,{1,TotLines}},
+ {height,NewLinesH},
+ {vscroll,false}]);
+ true ->
+ gs:config(Grid,[{rows,{1,TotLines}},{vscroll,right}])
+ end.
+
+calc_grid_h(WinH,TotLines) ->
+ LeftInWin = WinH - ?BarH - ?LabelH,
+ TotGrid = TotLines * ?GridLineH,
+ if LeftInWin >= TotGrid ->
+ {TotGrid,false};
+ true ->
+ {LeftInWin,right}
+ end.
+
+set_win_h(Win,OrigH,TotLines) ->
+ TotH = TotLines*?GridLineH + ?BarH + ?LabelH,
+ if TotH >= OrigH -> OrigH;
+ true -> gs:config(Win,[{height,TotH}]),
+ TotH
+ end.
+
+get_event(Info, Win, Grid, GL1, SysInfo, Config) ->
+ receive
+ {gs, Win, configure,[],[W,H,_,_]} ->
+ TotLines = getopt(lines,Config)+1,
+ %% Will not make window higher than total number of lines
+ RealWinH = set_win_h(Win,H,TotLines),
+ {GridH,VScroll} = calc_grid_h(RealWinH,TotLines),
+ gs:config(Grid, [{width, W},
+ {columnwidths, calc_column_w(W)},
+ {height,GridH}, {vscroll,VScroll}]),
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {gs, refresh, _, _, _} ->
+ Info1 = do_update(Grid, SysInfo, Config),
+ get_event(Info1, Win, Grid, GL1, SysInfo, Config);
+ {gs, dump, _, _, _} ->
+ case pop(Win,dump) of
+ {ok,File} -> etop:dump(File);
+ {error,cancel} -> ok
+ end,
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {gs, Win, destroy, _, _} ->
+ normal;
+ {gs, exit, _, _, _} ->
+ ok;
+ {gs, accum, _, _, _} ->
+ Old = getopt(accum,Config),
+ etop:config(accumulate,not Old),
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {gs,intv,_,_,_} ->
+ case pop(Win,interval) of
+ {ok,Intv} -> etop:config(interval,list_to_integer(Intv));
+ {error,cancel} -> ok
+ end,
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {gs,lines,_,_,_} ->
+ case pop(Win,lines) of
+ {ok,Lines} -> etop:config(lines,list_to_integer(Lines));
+ {error,cancel} -> ok
+ end,
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {gs,Sort,_,_,_} when Sort=:=runtime;
+ Sort=:=memory;
+ Sort=:=reductions;
+ Sort=:=msg_q ->
+ etop:config(sort,Sort),
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {gs,GL1,click,_,[Col,1,_]} ->
+ case Col of
+ ?TimeCol -> etop:config(sort, runtime);
+ ?MemCol -> etop:config(sort, memory);
+ ?RedsCol -> etop:config(sort, reductions);
+ ?MsgQCol -> etop:config(sort, msg_q);
+ _other -> ignore
+ end,
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ {config,{Key,Value},Config1} ->
+ case Key of
+ lines -> config_lines(Win,Grid,Value+1);
+ sort -> config_sort(GL1,Value);
+ accumulate -> gs:config(accum,[{select,Value}]);
+ _ -> ok
+ end,
+ Info1 = do_update(Grid, SysInfo, Config1),
+ get_event(Info1, Win, Grid, GL1, SysInfo, Config1);
+ {dump,Fd} ->
+ etop_txt:do_update(Fd,Info,Config),
+ get_event(Info, Win, Grid, GL1, SysInfo, Config);
+ Msg ->
+ io:format("~p got unexpected msg ~p~n", [?MODULE, Msg]),
+ get_event(Info, Win, Grid, GL1, SysInfo, Config)
+ after getopt(intv,Config) ->
+ Info1 = do_update(Grid, SysInfo, Config),
+ get_event(Info1, Win, Grid, GL1, SysInfo, Config)
+ end.
+
+do_update(Grid, SysInfo, Config) ->
+ Info = etop:update(Config),
+ Lines = makegridlines(Info#etop_info.procinfo, Grid, 2),
+ clear_lines(Lines, getopt(lines,Config) + 1, Grid),
+ makesysinfo(getopt(node,Config),Info,SysInfo),
+ Info.
+
+%clear_lines(From, To, _Grid) when From > To -> ok;
+clear_lines(From, To, Grid) ->
+ case gs:read(Grid, {obj_at_row, From}) of
+ undefined ->
+ ok;
+ GridLine ->
+ gs:destroy(GridLine),
+ clear_lines(From + 1, To, Grid)
+ end.
+
+formatmfa({M, F, A}) ->
+ io_lib:format("~w:~w/~w",[M, F, A]).
+
+makegridlines([#etop_proc_info{pid=Pid,
+ mem=Mem,
+ reds=Reds,
+ name=Name,
+ runtime=Time,
+ cf=MFA,
+ mq=MQ}
+ |T], Grid, Count) ->
+ update_gl(Grid, Count, [{{text, 1}, pid_to_list(Pid)},
+ {{text, 2}, to_list(Name)},
+ {{text, ?TimeCol},
+ if is_integer(Time)->integer_to_list(Time);
+ true -> Time
+ end},
+ {{text, ?RedsCol}, integer_to_list(Reds)},
+ {{text, ?MemCol}, integer_to_list(Mem)},
+ {{text, ?MsgQCol}, integer_to_list(MQ)},
+ {{text, 7}, formatmfa(MFA)},
+ {row, Count}, {click, false}]),
+ makegridlines(T, Grid, Count + 1);
+makegridlines([],_Grid,Count) ->
+ Count.
+
+update_gl(Grid, Row, GL) ->
+ case gs:read(Grid, {obj_at_row, Row}) of
+ undefined ->
+ gse:gridline(Grid,[{row, Row}|GL]);
+ GridLine ->
+ gs:config(GridLine,GL)
+ end.
+
+to_list(Name) when is_atom(Name) -> atom_to_list(Name);
+to_list({_M,_F,_A}=MFA) -> formatmfa(MFA).
+
+
+makesysinfo(Node,Info,SysInfo) ->
+ {Cpu,NProcs,RQ,Clock} = loadinfo(Info),
+ case Info#etop_info.memi of
+ undefined ->
+ Str = "No memory information is available.";
+ Memi ->
+ [Tot,Procs,Atom,Bin,Code,Ets] =
+ meminfo(Memi, [total,processes,atom,binary,code,ets]),
+ Str = io_lib:fwrite(?SYSFORM,
+ [Node,Clock,
+ Cpu,Tot,Bin,
+ NProcs,Procs,Code,
+ RQ,Atom,Ets])
+ end,
+ gs:config(SysInfo,[{label,{text,Str}},{font,?Normal}]).
+
+
+pop(Win,Key) ->
+ Pop = gs:create(window,Win,[{title,"Config"},
+ {width,160},{height,100}]),
+ gs:create(label,Pop,[{label,{text,txt(Key)}},
+ {width,160}]),
+ gs:create(entry,entry,Pop,[{x,10},{y,30},{width,130},
+ {keypress,true}]),
+ gs:create(button,ok,Pop,[{width,45},{y,60},{x,10},
+ {label,{text,"Ok"}}]),
+ gs:create(button,cancel,Pop,[{width,60},{y,60},{x,80},
+ {label,{text,"Cancel"}}]),
+ gs:config(Pop,{map,true}),
+ pop_loop(Pop).
+
+pop_loop(Pop) ->
+ receive
+ {gs,entry,keypress,_,['Return'|_]} ->
+ Str = gs:read(entry,text),
+ gs:destroy(Pop),
+ {ok,Str};
+ {gs,entry,keypress,_,_} -> % all other keypresses
+ pop_loop(Pop);
+ {gs,ok,click,_,_} ->
+ Str = gs:read(entry,text),
+ gs:destroy(Pop),
+ {ok,Str};
+ {gs,cancel,click,_,_} ->
+ gs:destroy(Pop),
+ {error,cancel};
+ X ->
+ io:format("Got X=~w~n",[X]),
+ pop_loop(Pop)
+ end.
+
+txt(interval) -> "Enter new interval:";
+txt(lines) -> "Enter number of lines:";
+txt(dump) -> "Enter file name:".