%%
%% %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).
-compile([{nowarn_deprecated_function,{gs,config,2}},
{nowarn_deprecated_function,{gs,create,3}},
{nowarn_deprecated_function,{gs,create,4}},
{nowarn_deprecated_function,{gs,destroy,1}},
{nowarn_deprecated_function,{gs,read,2}},
{nowarn_deprecated_function,{gs,start,0}}]).
-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:".