%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2011. 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(otp_mib).
%%%-----------------------------------------------------------------
%%% Description: This module implements the OTP-MIB.
%%% The tables are implemented as shadow tables with the module
%%% snmp_shadow_table. 
%%%-----------------------------------------------------------------

%% API
-export([load/1, unload/1]).

%% SNMP instrumentation
-export([erl_node_table/1, erl_node_table/3, appl_table/1, appl_table/3]).

%% SNMP shadow functions
-export([update_erl_node_table/0, update_appl_table/0]).

%% Exported for internal use via rpc
-export([get_erl_node/1, get_appls/1]).

%% Shadow tables  
-record(erlNodeTable,
	{erlNodeId, erlNodeName, erlNodeMachine, erlNodeVersion, 
	 erlNodeRunQueue,
         erlNodeRunTime, erlNodeWallClock, erlNodeReductions,
	 erlNodeProcesses, erlNodeInBytes, erlNodeOutBytes}).

-record(applTable, {key = '_', applName = '_', applDescr = '_',
		    applVsn = '_'}).

%% Shadow argument macros 
-define(erlNodeShadowArgs, 
	{erlNodeTable, integer, record_info(fields, erlNodeTable), 5000,
	 fun otp_mib:update_erl_node_table/0}).

-define(applShadowArgs,
	{applTable, {integer, integer}, record_info(fields, applTable), 
	 5000, fun otp_mib:update_appl_table/0}).

%% Misc
-record(erlNodeAlloc, {nodeName, nodeId}).

%%%=========================================================================
%%%  API
%%%=========================================================================

%%-------------------------------------------------------------------------
%% load(Agent) ->  ok | {error, Reason}
%% Agent - pid() | atom()
%% Reason - term()
%% Description: Loads the OTP-MIB
%%-------------------------------------------------------------------------
load(Agent) ->
    MibDir = code:priv_dir(otp_mibs) ++ "/mibs",
    snmpa:load_mibs(Agent, [MibDir ++ "/OTP-MIB"]).

%%-------------------------------------------------------------------------
%% unload(Agent) ->  ok | {error, Reason}
%% Agent - pid() | atom()
%% Reason - term()
%% Description: Loads the OTP-MIB
%%-------------------------------------------------------------------------
unload(Agent) ->
    snmpa:unload_mibs(Agent, ["OTP-MIB"]).
    

%%%=========================================================================
%%%  SNMP instrumentation
%%%=========================================================================
erl_node_table(new) ->
    Tab = erlNodeAlloc,
    Storage = ram_copies, 
    case lists:member(Tab, mnesia:system_info(tables)) of
	true ->
	    case mnesia:table_info(Tab, storage_type) of
		unknown ->
		    {atomic, ok} = mnesia:add_table_copy(Tab, node(), Storage);
		Storage ->
		    catch delete_all(Tab)
	    end;
	false ->
	    Nodes = [node()],
	    Props = [{type, set},
		     {attributes, record_info(fields, erlNodeAlloc)},
		     {local_content, true},
		     {Storage, Nodes}],
	    {atomic, ok} = mnesia:create_table(Tab, Props)    
    end,
    ok = mnesia:dirty_write({erlNodeAlloc, next_index, 1}),
    update_node_alloc([node() | nodes()]),
    snmp_shadow_table:table_func(new, ?erlNodeShadowArgs).
    
erl_node_table(Op, RowIndex, Cols) ->
    snmp_shadow_table:table_func(Op, RowIndex, Cols, ?erlNodeShadowArgs).


appl_table(Op) ->
    snmp_shadow_table:table_func(Op, ?applShadowArgs).
appl_table(Op, RowIndex, Cols) ->
    snmp_shadow_table:table_func(Op, RowIndex, Cols, ?applShadowArgs).


%%%=========================================================================
%%%  SNMP shadow functions
%%%=========================================================================
update_erl_node_table() ->
    delete_all(erlNodeTable),
    Nodes = [node() | nodes()],
    update_node_alloc(Nodes),
    lists:foreach(
      fun(Node) ->
	      [{_,_,Idx}] = mnesia:dirty_read({erlNodeAlloc, Node}),
	      ErlNode = rpc:call(Node, otp_mib, get_erl_node, [Idx]),
	      ok = mnesia:dirty_write(ErlNode)
      end, Nodes).

update_appl_table() ->
    delete_all(applTable),
    Nodes = [node() | nodes()],
    update_node_alloc(Nodes),
    lists:foreach(
      fun(Node) ->
	      [{_,_,Idx}] = mnesia:dirty_read({erlNodeAlloc, Node}),
	      Appls = rpc:call(Node, otp_mib, get_appls, [Idx]),
	      lists:foreach(fun(Appl) ->
				    ok = mnesia:dirty_write(Appl)
			    end, Appls)
      end, Nodes).

%%%========================================================================
%%% Exported for internal use via rpc
%%%========================================================================
get_erl_node(Id) ->
    IO = erlang:statistics(io),
    #erlNodeTable{erlNodeId = Id,
		  erlNodeName = atom_to_list(node()),
		  erlNodeVersion = erlang:system_info(version),
		  erlNodeMachine = erlang:system_info(machine),
		  erlNodeRunQueue = erlang:statistics(run_queue),
		  erlNodeRunTime = element(1, erlang:statistics(runtime)),
		  erlNodeWallClock = 
		  element(1, erlang:statistics(wall_clock)),
		  erlNodeReductions = 
		  element(1, erlang:statistics(reductions)),
		  erlNodeProcesses = length(processes()),
		  erlNodeInBytes = element(2, element(1, IO)),
		  erlNodeOutBytes = element(2, element(2, IO))}.
    
get_appls(NodeId) ->
    element(1,
	    lists:mapfoldl(
	      fun({ApplName, ApplDescr, ApplVsn}, ApplId) ->
		      {#applTable{key = {NodeId, ApplId},
				  applName = atom_to_list(ApplName),
				  applDescr = ApplDescr,
				  applVsn = ApplVsn},
		       ApplId + 1}
	      end, 1, application:which_applications())).

%%%========================================================================
%%% Internal functions
%%%========================================================================
update_node_alloc([Node | T]) ->
    case mnesia:dirty_read({erlNodeAlloc, Node}) of
	[] ->
	    [{_, _, Idx}] = mnesia:dirty_read({erlNodeAlloc, next_index}),
	    ok = mnesia:dirty_write(#erlNodeAlloc{nodeName = Node, 
						  nodeId = Idx}),
	    ok = mnesia:dirty_write({erlNodeAlloc, next_index, Idx + 1});
	_ ->
	    ok
    end,
    update_node_alloc(T);
update_node_alloc([]) -> ok.

delete_all(Name) -> delete_all(mnesia:dirty_first(Name), Name).
delete_all('$end_of_table', _Name) -> done;
delete_all(Key, Name) ->
    Next = mnesia:dirty_next(Name, Key),
    ok = mnesia:dirty_delete({Name, Key}),
    delete_all(Next, Name).
