From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/observer/src/etop_gui.erl | 362 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 lib/observer/src/etop_gui.erl (limited to 'lib/observer/src/etop_gui.erl') 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('siri@erix.ericsson.se'). + +-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:". -- cgit v1.2.3