aboutsummaryrefslogtreecommitdiffstats
path: root/lib/appmon/src/appmon_lb.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/appmon/src/appmon_lb.erl')
-rw-r--r--lib/appmon/src/appmon_lb.erl689
1 files changed, 689 insertions, 0 deletions
diff --git a/lib/appmon/src/appmon_lb.erl b/lib/appmon/src/appmon_lb.erl
new file mode 100644
index 0000000000..4e433f37c5
--- /dev/null
+++ b/lib/appmon/src/appmon_lb.erl
@@ -0,0 +1,689 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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%
+
+%%% Due to the fact that the application buttons in the appmon window
+%%% gets too small to read when the number of applications increases,
+%%% this listbox window has been created.
+%%% Because of the limitations of GS a listbox was chosen to keep
+%%% the nodes and applications. When it's possible to scroll a frame I
+%%% think one should put in scrollbars in the appmon main window.
+%%% The listbox solution is too slow with lots of applications.
+%%%
+%%% In the listbox the nodes are shown with their applications beneith.
+%%% By double clicking on an application name, or a single click and
+%%% then pressing the load button, its application window is started.
+
+-module(appmon_lb).
+
+-export ([
+ start/1,
+ stop/1,
+ add_node/2,
+ remove_node/2,
+ add_apps/3,
+ remove_app/3,
+ open_win/2,
+ update_status/3
+ ]).
+
+-export ([init/1]).
+
+-define (LB_W, 200). % List box width
+-define (LB_H, 400). % List box height
+-define (BUTT_W, 100).
+-define (WIN_W, ?LB_W + ?BUTT_W + 25). % Window width
+-define (WIN_H, ?LB_H + 20). % Window height
+
+
+%%% #node{}
+%%%
+%%% The record 'node' contains the name of the node, its status and
+%%% the applications running on that node.
+%%%
+%%% node == atom ()
+%%% status == alive || dead
+%%% apps == [#app{}]
+%%%
+
+-record (node, {node, %% Name of the node
+ status = alive,
+ apps = []}).
+
+%%% #app{}
+%%%
+%%% The record 'app' contains the name of the application and its pid
+%%%
+%%% app == atom ()
+%%% pid == pid ()
+%%%
+
+-record (app, {app,
+ pid}).
+
+
+%%% #win{}
+%%%
+%%% The record 'win' contains the pid of the listbox window,
+%%% its x and y position, its width and height.
+%%%
+%%% pid == win_closed || pid ()
+%%% x == integer ()
+%%% y == integer ()
+%%% width == integer ()
+%%% height == integer ()
+%%%
+
+-record (win, {pid = win_closed,
+ x = 50,
+ y = 50,
+ width = ?WIN_W,
+ height = ?WIN_H}).
+
+
+
+%%% Every function in the interface is called with the pid
+%%% of this recieve loop, called 'LbPid'.
+%%%
+
+
+%%% start /1
+%%%
+%%% start returns the pid of the spawned receive loop or
+%%% it will call exit/2 after a timeout.
+%%%
+%%% Pre:
+%%% CallingPid == pid ()
+%%%
+%%% Def:
+%%% pid () || exit/2
+%%%
+
+start (CallingPid) ->
+ PidInit = spawn (?MODULE, init, [CallingPid]),
+
+ %% Wait for a initialization completion message from
+ %% the spawned process before returning its Pid.
+
+ receive
+ {initialization_complete, PidInit} ->
+ PidInit
+
+ %% (Conditional) Failure to start within the time limit
+ %% will result in termination (Timeout may be infinite).
+
+ after
+ 60000 ->
+ exit (PidInit, kill),
+ exit ({startup_timeout, ?MODULE})
+ end.
+
+
+
+%%% stop /1
+%%%
+%%% stop exits the receive loop
+%%%
+%%% Post:
+%%% exiting the receive loop
+%%%
+
+stop (LbPid) ->
+ call (LbPid, stop).
+
+
+
+%%% add_node /2
+%%%
+%%% add_node adds the given node to the DB list.
+%%%
+%%% Pre:
+%%% Node == atom ()
+%%%
+%%% Post:
+%%% Node is added to the DB list
+%%%
+
+add_node (LbPid, Node) ->
+ call (LbPid, {add_node, Node}).
+
+
+
+%%% remove_node /2
+%%%
+%%% remove_node removes the given node from the DB list.
+%%%
+%%% Pre:
+%%% Node == atom ()
+%%%
+%%% Post:
+%%% Node is removed from the DB list
+%%%
+
+remove_node (LbPid, Node) ->
+ call (LbPid, {remove_node, Node}).
+
+
+
+%%% add_apps /3
+%%%
+%%% add_apps add the given applications to the given
+%%% node in the DB list.
+%%%
+%%% Pre:
+%%% Apps == [App]
+%%% App == {Name, Pid}
+%%% Name == atom ()
+%%% Pid == pid ()
+%%% Node == atom ()
+%%%
+%%% Post:
+%%% Node#node{apps = Apps}
+%%%
+
+add_apps (LbPid, Apps, Node) ->
+ call (LbPid, {add_apps, Apps, Node}).
+
+
+
+%%% remove_app /3
+%%%
+%%% remove_app remove the given application from the
+%%% given node in the DB list.
+%%%
+%%% Pre:
+%%% App == atom ()
+%%% Node == atom ()
+%%%
+%%% Def:
+%%% Node#node{apps = OldApps - App}
+%%%
+
+remove_app (LbPid, App, Node) ->
+ call (LbPid, {remove_app, App, Node}).
+
+
+
+%%% open_win /3
+%%%
+%%% open_win opens the listbox window with the given nodes
+%%% and their applications.
+%%%
+%%% Pre:
+%%% Nodes_apps == [{Node, Status, Apps}]
+%%% Node == atom ()
+%%% Status == alive || dead
+%%% Apps == [App]
+%%% App == {AppName, AppPid}
+%%% AppName == atom ()
+%%% AppPid == pid ()
+%%%
+%%% Post:
+%%% Window with listbox
+%%%
+
+open_win (LbPid, Nodes_apps) ->
+ call (LbPid, {open_win, Nodes_apps}).
+
+
+
+%%% update_status /3
+%%%
+%%% update_status changes the status for the given node.
+%%%
+%%% Pre:
+%%% Node == atom ()
+%%% Status == alive || dead
+%%%
+%%% Def:
+%%% Node#node{status = Status}
+%%%
+
+update_status (LbPid, Node, Status) ->
+ call (LbPid, {update_status, Node, Status}).
+
+
+
+%%% call /2
+%%%
+%%% call sends the given action to the listbox receive loop.
+%%%
+%%% Pre:
+%%% Action == atom () || tuple ()
+%%%
+
+call (LbPid, Action) ->
+ LbPid ! Action.
+
+
+
+%%% init /1
+%%%
+
+init (CallingPid) ->
+ CallingPid ! {initialization_complete, self ()},
+ loop (#win{}, []).
+
+
+
+%%% loop /2
+%%%
+%%% loop is the recive loop for the listbox window process.
+%%%
+%%% Pre:
+%%% Win == #win{}
+%%% Data == [#node{}]
+%%%
+
+loop (Win, Data) ->
+ receive
+ {add_node, Node} ->
+ NewData = add_node_1 (Node, Data),
+ update (NewData, Win#win.pid),
+ loop (Win, NewData);
+
+ {remove_node, Node} ->
+ NewData = dead_node (Node, Data),
+ update (NewData, Win#win.pid),
+ loop (Win, NewData);
+
+ {add_apps, Apps, Node} ->
+ NewData = add_apps_1 (Apps, Node, Data),
+ update (NewData, Win#win.pid),
+ loop (Win, NewData);
+
+ {remove_app, App, Node} ->
+ NewData = remove_app_1 (App, Node, Data),
+ update (NewData, Win#win.pid),
+ loop (Win, NewData);
+
+ {open_win, Nodes_apps} ->
+ NewData = parse_data ([], Nodes_apps),
+ NewWin = Win#win{pid = init_win ({Win#win.x, Win#win.y})},
+ update (NewData, NewWin#win.pid),
+ loop (NewWin, NewData);
+
+ {update_status, Node, Status} ->
+ NewData = update_status_1 (Node, Status, Data),
+ update (NewData, Win#win.pid),
+ loop (Win, NewData);
+
+ stop ->
+ true;
+
+
+ {gs, _Id, destroy, _D, _Arg} ->
+ bye;
+
+ {gs, _Id, configure, _D, [W, H | _]} ->
+ NewWin = configure (Win#win.pid, W, H),
+ loop (NewWin, Data);
+
+ {gs, lb, doubleclick, _, _Txt} ->
+ load_app (gs:read (lb, selection), Data),
+ loop (Win, Data);
+
+ {gs, lb, click, _, _Txt} ->
+ loop (Win, Data);
+
+ {gs, close, click, _D, _Arg} ->
+ case Win#win.pid of
+ win_closed ->
+ true;
+
+ _opened ->
+ gs:destroy (Win#win.pid)
+ end,
+ loop (#win{}, Data);
+
+ {gs, load, click, _D, _Txt} ->
+ load_app (gs:read (lb, selection), Data),
+ loop (Win, Data);
+
+ {gs, clear, click, _D, _Txt} ->
+ gs:config (lb, {selection, clear}),
+ loop (Win, Data);
+
+
+ _ ->
+ loop (Win, Data)
+ end.
+
+
+
+%%% init_win /1
+%%%
+
+init_win ({X, Y}) ->
+ GS = gs:start (),
+
+ Win = gs:window (win, GS, [{x, X},
+ {y, Y},
+ {width, ?WIN_W},
+ {height, ?WIN_H},
+ {title,"Appmon: nodes and applications"},
+ {configure, true}]),
+
+ gs:listbox (lb, Win, [{x, 5},
+ {y, 10},
+ {width, ?LB_W},
+ {height, ?LB_H},
+ {vscroll, right},
+ {hscroll, bottom},
+ {selectmode, single},
+ {click, true},
+ {doubleclick, true}]),
+
+ gs:button (load, Win, [{x, ?WIN_W - ?BUTT_W - 10},
+ {y, ?WIN_H - 120},
+ {width, ?BUTT_W},
+ {label, {text, "Load"}}]),
+
+ gs:button (clear, Win, [{x, ?WIN_W - ?BUTT_W - 10},
+ {y, ?WIN_H - 80},
+ {width, ?BUTT_W},
+ {label, {text, "Clear"}}]),
+
+ gs:button (close, Win, [{x, ?WIN_W - ?BUTT_W - 10},
+ {y, ?WIN_H - 40},
+ {width, ?BUTT_W},
+ {label, {text, "Close"}}]),
+
+ gs:config (Win, {map, true}),
+ Win.
+
+
+
+%%% add_node_1 /2
+%%%
+%%% add_node adds the given node in the given window
+%%% with its appications in a listbox.
+%%%
+
+add_node_1 (Node, []) ->
+ [new_node (Node)];
+
+add_node_1 (Node, [H | T]) ->
+ T1 = lists:keysort (#node.node, [new_node (Node) | T]),
+ [H | T1].
+
+
+
+%%% dead_node /2
+%%%
+%%% dead_node returns a list with the given node's
+%%% status changed to dead.
+%%%
+
+dead_node (Node, Data) ->
+ case lists:keysearch (Node, #node.node, Data) of
+ {value, Node_rec} ->
+ L = Node_rec#node.apps,
+ lists:keyreplace (Node, #node.node,
+ Data, new_node (Node, dead, L));
+
+ _false ->
+ Data
+ end.
+
+
+
+
+
+%%% add_apps_1 /3
+%%%
+%%% add_apps_1 returns a list with the given application
+%%% into the old list inserted.
+%%%
+
+add_apps_1 (Apps, Node, Data) ->
+ case lists:keysearch (Node, #node.node, Data) of
+ {value, _Node_rec} ->
+ NewApps = parse_apps (Apps, []),
+ lists:keyreplace (Node, #node.node,
+ Data, new_node (Node, NewApps));
+
+ _false ->
+ Data
+ end.
+
+
+
+%%% remove_app_1 /3
+%%%
+%%% remove_app_1 returns a list with the given application
+%%% removed from the old list.
+%%%
+
+remove_app_1 (App, Node, Data) ->
+
+ case lists:keysearch (Node, #node.node, Data) of
+ {value, Node_rec} ->
+ L = Node_rec#node.apps,
+ L2 = lists:keydelete (App, #app.app, L),
+ lists:keyreplace(Node, #node.node, Data, new_node(Node,L2));
+
+ _false ->
+ Data
+ end.
+
+
+
+%%% configure /3
+%%%
+%%% configure returns a win record after the window has been
+%%% configured.
+%%%
+
+configure (WPid, W, H) ->
+ X = gs:read (WPid, x),
+ Y = gs:read (WPid, y),
+
+ gs:config (lb, [{width, W - ?BUTT_W - 25}, {height, H - 20}]),
+ gs:config (load, [{x, W - ?BUTT_W - 10}, {y, H - 120}]),
+ gs:config (clear, [{x, W - ?BUTT_W - 10}, {y, H - 80}]),
+ gs:config (close, [{x, W - ?BUTT_W - 10}, {y, H - 40}]),
+
+ #win{pid = WPid, x = X, y = Y, width = W, height = H}.
+
+
+
+
+
+%%% load_app /2
+%%%
+%%% load_app opens the application window by calling
+%%% the appmon_a module.
+%%%
+
+load_app ([], _Data) -> %% no application chosen
+ ok;
+
+load_app ([Index], Data) ->
+ App = gs:read (lb, {get, Index}),
+
+ case string:substr (App, 1, 3) of
+ " " ->
+ AppName = list_to_atom (string:substr (App, 4)),
+
+ case get_node (AppName, Index, Data) of
+ no_node ->
+ ok;
+
+ NodeName ->
+ appmon_a:start (NodeName, AppName)
+ end;
+
+ _ ->
+ ok
+ end.
+
+
+
+%%% update_status_1 /3
+%%%
+%%% update_status_1 returns a list with the given
+%%% node's status updated.
+%%%
+
+update_status_1 (Node, Status, Data) ->
+ case lists:keysearch (Node, #node.node, Data) of
+ {value, Node_rec} ->
+ lists:keyreplace (Node,
+ #node.node,
+ Data,
+ new_node(Node,Status,Node_rec#node.apps));
+
+ _not_found ->
+ Data
+ end.
+
+
+
+%%% update /2
+%%%
+%%% update updates the listbox with new data.
+%%%
+
+update (_Data, win_closed) ->
+ true;
+
+update (Data, _Win) ->
+ gs:config (lb, clear),
+ lb_print (Data).
+
+
+
+%%% lb_print /1
+%%%
+%%% lb_print prints the list into the listbox.
+%%%
+
+lb_print ([]) ->
+ ok;
+
+lb_print ([#node{node = Node, status = Status, apps = Apps} | T]) ->
+ Str = io_lib:format ("~p (~p)", [Node, Status]),
+ gs:config (lb, {add, Str}),
+
+ case Status of
+ alive ->
+ lb_print_apps (Apps);
+
+ _dead ->
+ gs:config (lb, {add, ""}),
+ ok
+ end,
+
+ lb_print (T).
+
+
+
+%%% lb_print_apps /1
+%%%
+%%% lb_print_apps prints the applications into the listbox.
+%%%
+
+lb_print_apps ([]) ->
+ ok;
+
+lb_print_apps ([#app{app = App} | T]) ->
+ Str = io_lib:format (" ~p", [App]),
+ gs:config (lb, {add, Str}),
+ lb_print_apps (T).
+
+
+
+%%% new_node /1, 2, 3
+%%%
+%%% new_node returna a new node record constructed
+%%% with the given data
+%%%
+
+new_node (Node) ->
+ #node{node = Node}.
+
+new_node (Node, Apps) ->
+ #node{node = Node, apps = Apps}.
+
+new_node (Node, Status, Apps) ->
+ #node{node = Node, status = Status, apps = Apps}.
+
+
+
+%%% new_app /2
+%%%
+%%% new_app returns a new application record
+%%% constructed with the given data.
+%%%
+
+new_app (App, Pid) ->
+ #app{app = App, pid = Pid}.
+
+
+
+%%% parse_apps /2
+%%%
+%%% parse_apps returns a list of application records.
+%%%
+
+parse_apps ([], [H | T]) ->
+ [H | lists:keysort (#app.app, T)];
+
+parse_apps ([App | T], L) ->
+ Pid = element (1, App),
+ Name = element (2, App),
+ parse_apps (T, [new_app (Name, Pid) | L]).
+
+
+
+%%% get_node /3
+%%%
+%%% get_node returns the node from the given list
+%%% or else no_node if it doesn't exists.
+%%%
+
+get_node (_App, _Index, []) ->
+ no_node;
+
+get_node (App, Index, [Node | T]) ->
+ Length = length (Node#node.apps) + 1,
+
+ case Length < Index of
+ true ->
+ get_node (App, Index - Length, T);
+
+ false ->
+ Node#node.node
+ end.
+
+
+
+%%% parse_data /2
+%%%
+%%% parse_data returns a list with node records.
+%%%
+
+parse_data (Data, []) ->
+ Data;
+
+parse_data (Data, [{Node, Status, Apps} | T]) ->
+ Apps_1 = parse_apps (Apps, []),
+ parse_data ([new_node (Node, Status, Apps_1) | Data], T).
+
+
+