PageRenderTime 124ms CodeModel.GetById 19ms app.highlight 95ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/mibs/src/event_mib.erl

http://github.com/gebi/jungerl
Erlang | 1108 lines | 865 code | 80 blank | 163 comment | 64 complexity | b34303cc68f8b99706e3580686bd6821 MD5 | raw file
   1%%%-------------------------------------------------------------------
   2%%% File    : event_mib.erl
   3%%% Created : 12 May 2004 by  <mbj@bluetail.com>
   4%%%
   5%%% I'm using edoc with two changes: No html tags and no @hidden.
   6%%% It clutters down the code.  This makes it work fine with fdoc/distel :)
   7%%%
   8%%% @author Martin Bj´┐Żrklund <mbj@bluetail.com>
   9%%% @reference  <a href="http://www.ietf.org/rfc/rfc2981.txt">rfc2981</a>
  10%%% @version 1.0
  11%%% @doc Implements DISMAN-EVENT-MIB (rfc2981).
  12%%%
  13%%% The configuration data is persistently stored in mnesia.
  14%%% The caller can choose to use local_content to have all
  15%%% configuration per node, instead of globally in the cluster.
  16%%% Statistics is kept per host.  Events are triggered per
  17%%% host, since they very well can sample host specific data.
  18%%% If they sample cluster global data, all hosts will trigger
  19%%% the same event.
  20%%%
  21%%% The implementation relies on mnesia's snmp capability.
  22%%%
  23%%% There is one process which implements the triggers, and sends
  24%%% notifications as necessary.  This process subscribes to mnesia
  25%%% events for the mteTriggerTable in order to create/delete triggers.
  26%%% This means that regardless how these tables change (through SNMP
  27%%% or any other way), the server will be notified.  The
  28%%% instrumentation functions rely on this - they just write data to
  29%%% mnesia, and the server is notified thourgh the subscription
  30%%% mechanism.  NOTE: this means that an application that wants to
  31%%% modify the mnesia tables directly, must use mnesia transactions,
  32%%% since dirty writes to mnesia does not generate mnesia_events.
  33%%%
  34%%% Local sampling only is implemented, thus mteSetTable is
  35%%% not implemented,  which is fine for an agent.
  36%%%
  37%%% If a user creates entries directly in mnesia (i.e. w/o going
  38%%% through SNMP or functions in this module), the user MUST make sure
  39%%% rows are created consistently (e.g. that a row in
  40%%% mteTriggerExistenceTable is created if the 'existence' bit is set
  41%%% in mteTriggerTest).
  42%%%
  43%%% The user must create the mnesia tables once by calling
  44%%% {@link create_tables/2}.  The loading and unloading of DISMAN-EVENT-MIB
  45%%% is handled by the server.
  46%%% 
  47%%% The implementation's compliance to the mib is formally defined
  48%%% in the ERL-DISMAN-EVENT-CAP.txt AGENT-CAPABILITIES template file.
  49%%% This template can be modified and distributed to end users.  If used,
  50%%% the user must add the capability to the sysORTable by calling
  51%%% snmpa:add_agent_caps/2.
  52%%%
  53%%% @end
  54%%%-------------------------------------------------------------------
  55
  56%% TODO
  57%%   o  handle overloaded system, i.e. drop timeout events if overloaded
  58%%   o  enforce security, i.e. make sure a user cannot sample objects
  59%%      he doesn't have access to.  same check should be done to the
  60%%      objects in the notifications.  this may require changes in the
  61%%      OTP snmp agent.
  62
  63-module(event_mib).
  64-behaviour(gen_server).
  65
  66%% external exports
  67-export([start_link/0, start_link/1, start_link/2]).
  68
  69-export([db_tables/0, create_tables/1, create_tables/3]).
  70
  71-export([mte_variable/2, mte_variable/3,
  72	 mte_stat_variables/2,
  73	 mteTriggerTable/3, mteEventTable/3]).
  74-export([table_func/4,
  75	 unimplemented_table/3,
  76	 unimplemented_variable/1, unimplemented_variable/2]).
  77-export([op2str/1]).
  78
  79%% gen_server callbacks
  80-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
  81	 code_change/3]).
  82
  83%% include files
  84-include("event_mib.hrl").
  85-include("../include/DISMAN-EVENT-MIB.hrl").
  86-include_lib("snmp/include/SNMPv2-TC.hrl").
  87
  88%% records
  89-record(state, {triggers = []}).
  90
  91%% constants
  92-define(SERVER, ?MODULE).
  93
  94
  95-ifdef(debug).
  96-define(dbg(X,Y), error_logger:info_msg("*dbg ~p:~p: " X,
  97                                        [?MODULE, ?LINE | Y])).
  98-compile(export_all).
  99-else.
 100-define(dbg(X,Y), ok).
 101-endif.
 102
 103%%====================================================================
 104%% External functions
 105%%====================================================================
 106%% @spec start_link() -> {ok, pid()}
 107%% @equiv start_link(mibs, snmp_master_agent)
 108start_link() ->
 109    start_link(mibs, snmp_master_agent).
 110%% @spec start_link(App) -> {ok, pid()}
 111%% @equiv start_link(App, snmp_master_agent)
 112start_link(App) ->
 113    start_link(App, snmp_master_agent).
 114%% @spec start_link(App, Agent) -> {ok, pid()}
 115%%       App = atom()
 116%%       Agent = pid() | atom()
 117%% @doc Starts the event_mib server and loads the mib in Agent.
 118%% The mib is unloaded when the server terminates.  Notifications
 119%% which are generated when triggers fire are sent to Agent.
 120%% App is the name of the application which contains event_mib.
 121start_link(App, Agent) ->
 122    gen_server:start_link({local, ?SERVER}, ?MODULE, [App, Agent], []).
 123
 124%% @spec db_tables() -> [TableName]
 125%%       TableName = atom()
 126%% @doc Returns a list of all mnesia tables used by this module.
 127db_tables() ->
 128    [mteTriggerTable, mteTriggerDeltaTable, mteTriggerExistenceTable,
 129     mteTriggerBooleanTable, mteTriggerThresholdTable, mteObjectsTable,
 130     mteEventTable, mteEventNotificationTable, mteVariables].
 131
 132-define(table(TAB, SNMP), {TAB, record_info(fields, TAB), SNMP}).
 133
 134%% @spec create_tables(Nodes) -> ok | {error, string()}
 135%% @equiv create_tables(event_mib:db_tables(), Nodes, [])
 136create_tables(Nodes) ->
 137    create_tables(db_tables(), Nodes, [{disc_copies, Nodes}]).
 138%% @spec create_tables(Tabs, Nodes, MnesiaOptions::list()) ->
 139%%    ok | {error, string()}
 140%%       Tabs = list()
 141%%       String = list()
 142create_tables(Tabs, Nodes, MnesiaOpts) ->
 143    Specs = [?table(mteTriggerTable, {string, fix_string}),
 144	     ?table(mteTriggerDeltaTable, {string, fix_string}),
 145	     ?table(mteTriggerExistenceTable, {string, fix_string}),
 146	     ?table(mteTriggerBooleanTable, {string, fix_string}),
 147	     ?table(mteTriggerThresholdTable, {string, fix_string}),
 148	     ?table(mteObjectsTable, {string, string, integer}),
 149	     ?table(mteEventTable, {string, fix_string}),
 150	     ?table(mteEventNotificationTable, {string, fix_string}),
 151	     {mteVariables, record_info(fields, mteVariables), []}],
 152    DoTabs = [Spec || Spec <- Specs, lists:member(element(1, Spec), Tabs)],
 153    create_tables2(Nodes, DoTabs, MnesiaOpts).
 154    
 155create_tables2(Nodes, [{Name, RecordInfo, SNMP} | Rest], Opts) ->
 156    SnmpS = if SNMP == [] -> [];
 157	       true -> [{snmp, [{key, SNMP}]}]
 158	    end,
 159    case mnesia:create_table(Name, [{attributes, RecordInfo}]++SnmpS++Opts) of
 160	{atomic, ok} ->
 161	    create_tables2(Nodes, Rest, Opts);
 162	{aborted, Reason} ->
 163	    {error, Reason}
 164    end;
 165create_tables2(_Nodes, [], _Opts) ->
 166    F = fun() ->
 167		create_variables_t([{mteResourceSampleMinimum, 1},
 168				    {mteResourceSampleInstanceMaximum, 0}])
 169	end,
 170    case mnesia:transaction(F) of
 171	{atomic, ok} ->
 172	    ok;
 173	{aborted, Reason} ->
 174	    {error, Reason}
 175    end.
 176
 177create_variables_t([{Name, Val} | T]) ->
 178    mnesia:write(#mteVariables{name = Name, val = Val}),
 179    create_variables_t(T);
 180create_variables_t([]) ->
 181    ok.
 182
 183
 184%%====================================================================
 185%% Instrumentation functions
 186%%====================================================================
 187mte_variable(get, Var) ->
 188    [Obj] = mnesia:dirty_read({mteVariables, Var}),
 189    {value, Obj#mteVariables.val}.
 190mte_variable(set, Name, Val) ->
 191    mnesia_write(#mteVariables{name = Name, val = Val}),
 192    noError;
 193mte_variable(undo, _, _) ->
 194    undoFailed.
 195
 196mte_stat_variables(get, Var) ->
 197    Val = gen_server:call(event_mib, {get_stat, Var}, infinity),
 198    {value, Val}.
 199
 200unimplemented_table(get, _, _) ->
 201    {noValue, noSuchInstance};
 202unimplemented_table(get_next, _Row, Cols) ->
 203    lists:duplicate(length(Cols), endOfTable);
 204unimplemented_table(is_set_ok, _, Cols) ->
 205    lists:duplicate(length(Cols), noCreation).
 206
 207unimplemented_variable(get) ->
 208    {noValue, noSuchInstance}.
 209unimplemented_variable(is_set_ok, _) ->
 210    noCreation.
 211
 212table_func(Op, RowIndex, Cols, Tab) ->
 213    snmp_generic:table_func(Op, RowIndex, Cols, {Tab, mnesia}).
 214
 215%% This is an enhanced version of snmp_generic_mnesia:table_func(set,...)
 216%% This version takes an extra fun() which is executed within the transaction.
 217table_func_set(RowIndex, Cols, Tab, F) ->
 218    case mnesia:transaction(
 219	   fun() ->
 220		   F(),
 221		   snmp_generic:table_set_row(
 222		     {Tab, mnesia}, nofunc,
 223		     {snmp_generic_mnesia, table_try_make_consistent},
 224		     RowIndex, Cols)
 225	   end) of
 226	{atomic, Value} ->
 227	    Value;
 228	{aborted, Reason} ->
 229	    snmpa_error:user_err("set transaction aborted. Tab ~w, RowIndex"
 230				 " ~w, Cols ~w. Reason ~w",
 231				 [Tab, RowIndex, Cols, Reason]),
 232	    {Col, _Val} = hd(Cols),
 233	    {commitFailed, Col}
 234    end.
 235
 236
 237mteTriggerTable(is_set_ok, RowIndex, Cols) ->
 238    case chk_trigger_cols(Cols) of
 239	ok ->
 240	    table_func(is_set_ok, RowIndex, Cols, mteTriggerTable);
 241	Else ->
 242	    Else
 243    end;
 244mteTriggerTable(set, RowIndex, Cols) ->
 245    %% If rowstatus is modified, possibly create/delete rows in the
 246    %% other mteTrigger* tables.
 247    case find_col(?mteTriggerEntryStatus, Cols, undefined) of
 248	?RowStatus_createAndGo ->
 249            %% all vals either present in Cols or default
 250	    Type = create,
 251	    Test = find_col(?mteTriggerTest, Cols, ?boolean),
 252	    SampleType = find_col(?mteTriggerSampleType, Cols, ?absoluteValue);
 253        ?RowStatus_active ->
 254            %% all values are either present in Cols or already in mnesia
 255	    [T] = mnesia:dirty_read({mteTriggerTable, RowIndex}),
 256	    Type = create,
 257	    Test = find_col(?mteTriggerTest, Cols, T#mteTriggerTable.test),
 258	    SampleType = find_col(?mteTriggerSampleType, Cols,
 259				  T#mteTriggerTable.sampleType);
 260	?RowStatus_destroy ->
 261	    Type = delete,
 262	    Test = 0,
 263	    SampleType = 0;
 264	 _ ->
 265	    Type = noop,
 266	    Test = 0,
 267	    SampleType = 0
 268    end,
 269    Key = trig_row_index2key(RowIndex),
 270    F =	fun() ->
 271		case Type of
 272		    create ->
 273			if ?bit_is_set(Test, ?existence) ->
 274				mnesia:write(#mteTriggerExistenceTable{
 275				                key = Key});
 276			   true ->
 277				ok
 278			end,
 279			if ?bit_is_set(Test, ?boolean) ->
 280				mnesia:write(#mteTriggerBooleanTable{
 281                                                key = Key});
 282			   true ->
 283				ok
 284			end,
 285			if ?bit_is_set(Test, ?threshold) ->
 286				mnesia:write(#mteTriggerThresholdTable{
 287                                                key = Key});
 288			   true ->
 289				ok
 290			end,
 291			if SampleType == ?deltaValue ->
 292				mnesia:write(#mteTriggerDeltaTable{
 293                                                key = Key});
 294			   true ->
 295				ok
 296			end;
 297		    delete ->
 298			mnesia:delete({mteTriggerExistenceTable, Key}),
 299			mnesia:delete({mteTriggerBooleanTable, Key}),
 300			mnesia:delete({mteTriggerThresholdTable, Key}),
 301			mnesia:delete({mteTriggerDeltaTable, Key});
 302		    noop ->
 303			ok
 304		end
 305	end,
 306    table_func_set(RowIndex, Cols, mteTriggerTable, F);
 307mteTriggerTable(Op, RowIndex, Cols) ->
 308    table_func(Op, RowIndex, Cols, mteTriggerTable).
 309
 310trig_row_index2key([N | T]) ->
 311    lists:split(N, T).
 312
 313chk_trigger_cols([{?mteTriggerTargetTag, _} | _]) ->
 314    %% we don't support write to this object (i.e. no remote system)
 315    {notWritable, ?mteTriggerTargetTag};
 316chk_trigger_cols([{?mteTriggerValueID, Val} | T]) ->
 317    %% FIXME check that the user has access to the object to sample
 318    chk_trigger_cols(T);
 319chk_trigger_cols([{?mteTriggerContextNameWildcard, ?TruthValue_true} | _]) ->
 320    %% we don't support context wildcarding - we don't have the list of
 321    %% available context
 322    {wrongValue, ?mteTriggerContextNameWildcard};
 323chk_trigger_cols([{?mteTriggerFrequency, Val} | T]) ->
 324    case mnesia:dirty_read({mteVariables, mteResourceSampleMinimum}) of
 325	[#mteVariables{val = Min}] when Val < Min ->
 326	    {wrongValue, ?mteTriggerFrequency};
 327	_ ->
 328	    chk_trigger_cols(T)
 329    end;
 330chk_trigger_cols([_ |T]) ->
 331    chk_trigger_cols(T);
 332chk_trigger_cols([]) ->
 333    ok.
 334
 335
 336mteEventTable(is_set_ok, RowIndex, Cols) ->
 337    case chk_event_cols(Cols) of
 338	ok ->
 339	    table_func(is_set_ok, RowIndex, Cols, mteTriggerTable);
 340	Else ->
 341	    Else
 342    end;
 343mteEventTable(set, RowIndex, Cols) ->
 344    %% If rowstatus is modified, possibly create/delete rows in
 345    %% mteEventNotificationTable
 346    case find_col(?mteEventEntryStatus, Cols, undefined) of
 347	?RowStatus_createAndGo ->
 348	    %% all values are either present in Cols or default
 349	    Type = create,
 350	    Actions = find_col(?mteEventActions, Cols, 0);
 351        ?RowStatus_active ->
 352	    %% all values are either present in Cols or already in mnesia
 353	    [E] = mnesia:dirty_read({mteEventTable, RowIndex}),
 354	    Type = create,
 355	    Actions = find_col(?mteEventActions, Cols, E#mteEventTable.actions);
 356	?RowStatus_destroy ->
 357	    Type = delete,
 358	    Actions = 0;
 359	undefined ->
 360	    %% modifing an existing row
 361	    Actions = find_col(?mteEventActions, Cols, undefined),
 362	    case mnesia:dirty_read({mteEventTable, RowIndex}) of
 363		[E] when E#mteEventTable.actions /= Actions ->
 364		    if ?bit_is_set(Actions, ?notification) ->
 365			    Type = create;
 366		       true ->
 367			    Type = delete
 368		    end;
 369		_ ->
 370		    Type = noop
 371	    end;
 372	 _ ->
 373	    Type = noop,
 374	    Actions = 0
 375    end,
 376    F = fun() ->
 377		case Type of
 378		    create ->
 379			if ?bit_is_set(Actions, ?notification) ->
 380				mnesia:write(#mteEventNotificationTable{
 381                                               key = RowIndex});
 382			   true ->
 383				ok
 384			end;
 385		    delete ->
 386			mnesia:delete({mteEventNotificationTable, RowIndex});
 387		    _ ->
 388			ok
 389		end
 390	end,
 391    table_func_set(RowIndex, Cols, mteEventTable, F);
 392mteEventTable(Op, RowIndex, Cols) ->
 393    table_func(Op, RowIndex, Cols, mteEventTable).
 394    
 395chk_event_cols([{?mteEventActions, Val} | _]) when ?bit_is_set(Val, ?set) ->
 396    %% we don't support remote set
 397    {wrongValue, ?mteEventActions};
 398chk_event_cols([_ | T]) ->
 399    chk_event_cols(T);
 400chk_event_cols([]) ->
 401    ok.
 402
 403%%====================================================================
 404%% Server functions
 405%%====================================================================
 406init([App, Agent]) ->
 407    process_flag(trap_exit, true),
 408    put(agent, Agent),
 409    put(instances, 0),
 410    put(instancesHigh, 0),
 411    put(instanceLacks, 0),
 412    put(failures, 0),
 413    mnesia:subscribe({table, mteTriggerTable}),
 414    mnesia:subscribe({table, mteTriggerDeltaTable}),
 415    mnesia:subscribe({table, mteTriggerExistenceTable}),
 416    mnesia:subscribe({table, mteTriggerBooleanTable}),
 417    mnesia:subscribe({table, mteTriggerThresholdTable}),
 418    EventMib = filename:join(code:priv_dir(App), "DISMAN-EVENT-MIB.bin"),
 419    ?dbg("loading ~p\n", [EventMib]),
 420    snmpa:load_mibs(Agent, [EventMib]),
 421    {ok, #state{triggers = start_triggers()}}.
 422
 423handle_call({get_stat, Var}, _From, S) ->
 424    {reply, get(Var), S}.
 425
 426handle_cast(_Msg, S) ->
 427    {noreply, S}.
 428
 429handle_info({mnesia_table_event, MnesiaEvent}, S) ->
 430    %% any one of the trigger specification tables has changed;
 431    %% they all have the same key.
 432    case MnesiaEvent of
 433	{write, T, _} when element(1, T) == schema ->
 434	    %% why do we get this message??
 435	    {noreply, S};
 436	{write, T, _} ->
 437	    %% the oid, frequency, values to compare with... may have been
 438	    %% modified; delete and recreate the trigger
 439	    Key = element(2, T),
 440	    Triggers0 = del_trigger(Key, S#state.triggers),
 441	    Triggers1 = if record(T, mteTriggerTable) ->
 442				start_trigger(T, Triggers0);
 443			   true ->
 444				start_trigger_by_key(Key, Triggers0)
 445			end,
 446	    {noreply, S#state{triggers = Triggers1}};
 447	{delete, {_, Key}, _} ->
 448	    Triggers0 = del_trigger(Key, S#state.triggers),
 449	    {noreply, S#state{triggers = Triggers0}};
 450	{delete_object, T, _} ->
 451	    Key = element(2, T),
 452	    Triggers0 = del_trigger(Key, S#state.triggers),
 453	    {noreply, S#state{triggers = Triggers0}}
 454    end;
 455handle_info({trigger_timeout, Key}, S) ->
 456    {noreply, trigger_timeout(Key, S)};
 457handle_info(_Info, S) ->
 458    {noreply, S}.
 459
 460terminate(_Reason, _S) ->
 461    snmpa:unload_mibs(get(agent), ["DISMAN-EVENT-MIB"]).
 462
 463code_change(_OldVsn, S, _Extra) ->
 464    {ok, S}.
 465
 466%%====================================================================
 467%% Internal functions
 468%%====================================================================
 469mnesia_write(Obj) ->
 470    {atomic, _} = mnesia:transaction(fun() -> mnesia:write(Obj) end).
 471			       
 472%% start all triggers at startup
 473start_triggers() ->
 474    {atomic, Trigs} =
 475	mnesia:transaction(
 476	  fun() -> mnesia:foldl(fun mk_trig_t/2, [], mteTriggerTable) end),
 477    %% start the timers and do init check outside the transaction
 478    lists:zf(fun(Trig) -> start_trigger(Trig) end, Trigs).
 479
 480mk_trig_t(T,Trigs) when T#mteTriggerTable.entryStatus == ?RowStatus_active,
 481                        T#mteTriggerTable.enabled == ?TruthValue_true ->
 482    [mk_trig_t(T) | Trigs];
 483mk_trig_t(_T, Trigs) ->
 484    ?dbg("ignoring inactive/disabled trigger ~p\n", [_T#mteTriggerTable.key]),
 485    Trigs.
 486
 487
 488start_trigger({Key, Freq, Obj}) ->
 489    case check_trigger_startup(Key, Obj) of
 490	{true, ChkState} ->
 491	    {ok, TimerRef} = timer:send_interval(Freq*1000,
 492						 {trigger_timeout, Key}),
 493	    {true, {Key, TimerRef, Obj, ChkState}};
 494	false ->
 495	    false
 496    end.
 497
 498%% start a single trigger b/c it has been created or modified
 499start_trigger(T, Triggers) ->
 500    case mk_trig(T) of
 501	[Trig] -> [start_trigger(Trig) | Triggers];
 502	[] -> Triggers
 503    end.
 504
 505%% start a single trigger b/c the mteTrigger<X>Table has been modified
 506start_trigger_by_key(Key, Triggers) ->
 507    case mk_trig_by_key(Key) of
 508	[Trig] -> [start_trigger(Trig) | Triggers];
 509	[] -> Triggers
 510    end.
 511
 512mk_trig(T) ->
 513    {atomic, Trigs} = mnesia:transaction(fun() -> mk_trig_t(T, []) end),
 514    Trigs.
 515
 516mk_trig_by_key(Key) ->
 517    {atomic, Trigs} = 
 518	mnesia:transaction(fun() ->
 519				   [T] = mnesia:read({mteTriggerTable, Key}),
 520				   mk_trig_t(T, [])
 521			   end),
 522    Trigs.
 523
 524mk_trig_t(T) ->
 525    Key = T#mteTriggerTable.key,
 526    Freq = T#mteTriggerTable.frequency,
 527    Obj = {T#mteTriggerTable.valueID,
 528	   T#mteTriggerTable.valueIDWildcard == ?TruthValue_true,
 529	   T#mteTriggerTable.contextName,
 530	   mk_sample_data_t(T),
 531	   mk_test_data_t(T)},
 532    ?dbg("create trigger key: ~p freq: ~p\nobj: ~p\n", [Key, Freq, Obj]),
 533    {Key, Freq, Obj}.
 534
 535mk_sample_data_t(T) ->
 536    case T#mteTriggerTable.sampleType of
 537	?absoluteValue ->
 538	    [];
 539	?deltaValue ->
 540	    [Delta] = mnesia:read({mteTriggerDeltaTable,T#mteTriggerTable.key}),
 541	    if Delta#mteTriggerDeltaTable.discontinuityID==?sysUpTimeInstance ->
 542		    %% sysUptime changes => we must restart => no need to
 543		    %% check for discontinuity in sysUpTime
 544		    [];
 545	       true ->
 546		    Delta
 547	    end
 548    end.
 549
 550mk_test_data_t(T) ->
 551    Key = T#mteTriggerTable.key,
 552    Test = T#mteTriggerTable.test,
 553    if ?bit_is_set(Test, ?existence) ->
 554	    mnesia:read({mteTriggerExistenceTable, Key});
 555       true -> []
 556    end ++
 557    if ?bit_is_set(Test, ?boolean) ->
 558	    mnesia:read({mteTriggerBooleanTable, Key});
 559       true -> []
 560    end ++
 561    if ?bit_is_set(Test, ?threshold) ->
 562	    mnesia:read({mteTriggerThresholdTable, Key});
 563       true -> []
 564    end.
 565
 566del_trigger(Key, Triggers) ->
 567    case lists:keysearch(Key, 1, Triggers) of
 568	{value, {_Key, TimerRef, _Obj, ChkState}} ->
 569	    ?dbg("delete trigger ~p\n", [Key]),
 570	    put(instances, get(instances) - length(ChkState)),
 571	    timer:cancel(TimerRef),
 572	    lists:keydelete(Key, 1, Triggers);
 573	_ ->
 574	    ?dbg("could not delete trigger ~p\n", [Key]),
 575	    Triggers
 576    end.
 577
 578trigger_timeout(Key, S) ->
 579    case lists:keysearch(Key, 1, S#state.triggers) of
 580	{value, {_Key, TimerRef, Obj, CheckState0}} ->
 581	    ?dbg("trigger ~p trigged\n", [Key]),
 582	    case check_trigger(Key, Obj, CheckState0) of
 583		CheckState0 -> % optmimization - nothing changed
 584		    S;
 585		CheckState1 ->
 586		    NewT = {Key, TimerRef, Obj, CheckState1},
 587		    Ts = lists:keyreplace(Key, 1, S#state.triggers, NewT),
 588		    S#state{triggers = Ts}
 589	    end;
 590	_ ->
 591	    ?dbg("unknown trigger ~p trigged\n", [Key]),
 592	    S
 593    end.
 594		    
 595%% Returns: ChkState.
 596%%   ChkState contains the old value for each instance sampled (might be several
 597%%   if wildcarding is used).
 598%% ChkState() = [{Instance, Value, PrevValue, DiscoData, Fire}]
 599check_trigger_startup(_Key, {OID, GetNextP, Context, SampleData, TestData}) ->
 600    NewValues = sample(OID, GetNextP, Context, SampleData),
 601    NewInstances = length(NewValues) + get(instances),
 602    case mnesia:dirty_read({mteVariables, mteResourceSampleInstanceMaximum}) of
 603	[#mteVariables{val = Max}] when Max > 0, NewInstances > Max ->
 604	    put(instanceLacks, get(instanceLacks) + length(NewValues)),
 605	    false;
 606	_ ->
 607	    High = get(instancesHigh),
 608	    if NewInstances > High ->
 609		    put(instancesHigh, NewInstances);
 610	       true ->
 611		    ok
 612	    end,
 613	    put(instances, NewInstances),
 614	    ?dbg("trigger ~p read at startup oid(s):~p val(s): ~p\n",
 615		 [_Key, OID, NewValues]),
 616	    {true, check_values(NewValues, [], TestData, true)}
 617    end.
 618    
 619%% Returns: ChkState.
 620check_trigger(_Key, {OID, GetNextP, Context, SampleData, TestData},OldValues) ->
 621    NewValues = sample(OID, GetNextP, Context, SampleData),
 622    ?dbg("trigger ~p read oid(s):~p val(s): ~p\n", [_Key, OID, NewValues]),
 623    check_values(NewValues, OldValues, TestData, false).
 624
 625%% Returns: ChkState.
 626check_values([{Inst, Val0, Dis0} | T0],  [{Inst, Val1, Val2, Dis1, Fire1} | T1],
 627	     TestData, IsNew) ->
 628    %% same instance as before
 629    case discontinuity_occured(Dis0, Dis1) of
 630	true -> % discontinuity found, reset sampled values
 631	    [{Inst, Val0, '$undefined', Dis0, 0} |
 632	     check_values(T0, T1, TestData, IsNew)];
 633	false ->
 634	    Fire = check_test(TestData, Inst, Val0, Val1, Val2, false, Fire1),
 635	    [{Inst, Val0, Val1, Dis0, Fire} |
 636	     check_values(T0, T1, TestData, IsNew)]
 637    end;
 638check_values([{Inst0, Val0, Dis0} | T0], [{Inst1, _, _, _, _} | _] = T1,
 639	     TestData, IsNew) when Inst0 < Inst1 ->
 640    %% new instance found
 641    Fire = check_test(TestData, Inst0, Val0, '$undefined', '$undefined',
 642		      IsNew, 0),
 643    [{Inst0, Val0, '$undefined', Dis0, Fire} |
 644     check_values(T0, T1, TestData, IsNew)];
 645check_values(T0, [{Inst1, Val1, Val2, _Dis, Fire1} | T1], TestData, IsNew) ->
 646    %% old instance deleted
 647    check_test(TestData, Inst1, '$undefined', Val1, Val2, false, Fire1),
 648    check_values(T0, T1, TestData, IsNew);
 649check_values([{Inst0, Val0, Dis0} | T0], [], TestData, IsNew) ->
 650    %% new instance found
 651    Fire = check_test(TestData, Inst0, Val0, '$undefined', '$undefined',
 652		      IsNew, 0),
 653    [{Inst0, Val0, '$undefined', Dis0, Fire} |
 654     check_values(T0, [], TestData, IsNew)];
 655check_values([], [], _TestData, _IsNew) ->
 656    [].
 657	    
 658%% Ret: [{Instance, Value, {DiscoType, DiscoValue} | undefined}]
 659sample(OID, false, Context, SampleData) ->
 660    case get_values([OID], Context) of
 661	[Value] when SampleData == [] ->
 662	    [{[], Value, undefined}];
 663	[Value] ->
 664	    DiscoData = get_disco_data(SampleData, [], Context),
 665	    [{[], Value, DiscoData}];
 666	_ ->
 667	    []
 668    end;
 669sample(OID, true, Context, SampleData) ->
 670    samples(OID, OID, Context, SampleData).
 671
 672samples(OID, Prefix, Context, SampleData) ->
 673    case get_next_values([OID], Context) of
 674	[{NextOID, Value}] ->
 675	    case prefix(Prefix, NextOID) of
 676		{true, Instance} when SampleData == [] ->
 677		    [{Instance, Value, undefined} 
 678		     | samples(NextOID, Prefix, Context, SampleData)];
 679		{true, Instance} ->
 680		    DiscoData = get_disco_data(SampleData, Instance, Context),
 681		    [{Instance, Value, DiscoData}
 682		     | samples(NextOID, Prefix, Context, SampleData)];
 683		false ->
 684		    []
 685	    end;
 686	_ ->
 687	    []
 688   end.
 689
 690get_disco_data(Delta, Instance, Context) ->
 691    OID = Delta#mteTriggerDeltaTable.discontinuityID ++ Instance,
 692    case get_values([OID], Context) of
 693	[Val] ->
 694	    {Delta#mteTriggerDeltaTable.discontinuityIDType, Val};
 695	_ ->
 696	    %% FIXME; report failure?
 697	    undefined
 698    end.
 699
 700%% This is not obvious from the rfc, and this is not how net-snmp
 701%% does it.  But it seems reasonable to say that a discontinuity
 702%% has occured if type is TimeTicks and New < Old, or for
 703%% TimeStamp and DateAndTime if New != Old.
 704discontinuity_occured(undefined, _) -> false;
 705discontinuity_occured(_, undefined) -> false;
 706discontinuity_occured({Type, NewVal}, {_, OldVal}) ->
 707    if Type == ?timeTicks -> % discontinuity if new < old
 708	    NewVal < OldVal;
 709       true -> % otherwise, discontinuity if new != old
 710	    NewVal /= OldVal
 711    end.
 712	    
 713
 714-define(mask_boolean, (1 bsl 0)).
 715-define(mask_rising, (1 bsl 1)).
 716-define(mask_falling, (1 bsl 2)).
 717-define(mask_delta_rising, (1 bsl 1)).
 718-define(mask_delta_falling, (1 bsl 2)).
 719
 720%% Ret: FireMask (bitmask with ?mask_* - which triggers are fired)
 721check_test([#mteTriggerExistenceTable{test = Test, startup = StartUp} = H | T],
 722	   Instance, NewValue, OldValue, OldValue2, IsNew, OldFireMask) ->
 723    if IsNew == true, ?bit_is_set(StartUp, ?present),
 724       ?bit_is_set(Test, ?present),
 725       NewValue /= '$undefined' ->
 726	    %% instance exists at activation
 727	    fire_existence(H, Instance);
 728       IsNew == true, ?bit_is_set(StartUp, ?absent),
 729       ?bit_is_set(Test, ?absent),
 730       NewValue == '$undefined' ->
 731	    %% instance doesn't exist at activation
 732	    fire_existence(H, Instance);
 733       IsNew == true ->
 734	    %% should not fire at activation b/c no bits are set
 735	    no_fire;
 736       ?bit_is_set(Test, ?present),
 737       OldValue == '$undefined',
 738       NewValue /= '$undefined' ->
 739	    fire_existence(H, Instance);
 740       ?bit_is_set(Test, ?absent),
 741       OldValue /= '$undefined',
 742       NewValue == '$undefined' ->
 743	    fire_existence(H, Instance);
 744       ?bit_is_set(Test, ?changed),
 745       OldValue /= NewValue ->
 746	    fire_existence(H, Instance);
 747       true ->
 748	    no_fire
 749    end,
 750    check_test(T, Instance, NewValue, OldValue, OldValue2, IsNew, OldFireMask);
 751check_test([#mteTriggerBooleanTable{comparison = Op, value = Val} = H | T],
 752	   Instance, NewValue, OldValue, OldValue2, IsNew, OldFireMask) ->
 753    NewFireMask =
 754	if NewValue == '$undefined' ->
 755		%% the instance does no longer exist
 756		%% FIXME: rfc is unclear; should we generate a failure here?
 757		?bit_clr(OldFireMask, ?mask_boolean);
 758	   OldValue == '$undefined',
 759	   H#mteTriggerBooleanTable.startup == ?TruthValue_false ->
 760		%% a new instance, but we should not do the test the first time
 761		?bit_clr(OldFireMask, ?mask_boolean);
 762	   is_integer(NewValue) == false ->
 763		fail(badType, H#mteTriggerBooleanTable.key, Instance),
 764		?bit_clr(OldFireMask, ?mask_boolean);
 765	   Op == ?unequal, NewValue /= Val ->
 766		fire_boolean(H, Instance, NewValue, Op, Val, OldFireMask);
 767	   Op == ?equal, NewValue == Val ->
 768		fire_boolean(H, Instance, NewValue, Op, Val, OldFireMask);
 769	   Op == ?less, NewValue < Val ->
 770		fire_boolean(H, Instance, NewValue, Op, Val, OldFireMask);
 771	   Op == ?lessOrEqual, NewValue =< Val ->
 772		fire_boolean(H, Instance, NewValue, Op, Val, OldFireMask);
 773	   Op == ?greater, NewValue > Val ->
 774		fire_boolean(H, Instance, NewValue, Op, Val, OldFireMask);
 775	   Op == ?greaterOrEqual, NewValue >= Val ->
 776		fire_boolean(H, Instance, NewValue, Op, Val, OldFireMask);
 777	   true ->
 778		?dbg("no fire b/c NOT(~p ~s ~p)\n", [NewValue,op2str(Op),Val]),
 779		?bit_clr(OldFireMask, ?mask_boolean)
 780	end,
 781    check_test(T, Instance, NewValue, OldValue, OldValue2, IsNew, NewFireMask);
 782check_test([#mteTriggerThresholdTable{startup = StartUp,
 783				      rising = Rising, falling = Falling,
 784				      deltaRising = DeltaRising,
 785				      deltaFalling = DeltaFalling} = H | T],
 786	   Instance, NewValue, OldValue, OldValue2, IsNew, OldFireMask) ->
 787    ?dbg("new: ~p, old: ~p, old2: ~p, ris: ~p, fal: ~p\n",
 788	 [NewValue, OldValue, OldValue2, Rising, Falling]),
 789    NewFireMask =
 790	if NewValue == '$undefined' ->
 791		%% the instance does no longer exist
 792		%% FIXME: rfc is unclear; should we generate a failure here?
 793		OldFireMask;
 794	   is_integer(NewValue) == false ->
 795		fail(badType, H#mteTriggerThresholdTable.key, Instance),
 796		OldFireMask;
 797	   OldValue == '$undefined' ->
 798		%% this is the first sampling, check startup
 799		%% FIXME: rfc is unclear; should we also check that New == new,
 800		%% i.e. only when the entry becomes active?  Now we treat
 801		%% startup for new instances the same as when the entry becomes
 802		%% active.
 803		if StartUp /= ?mteTriggerThresholdStartup_falling,
 804		   NewValue >= Rising ->
 805			fire_threshold_rising(H, Instance, NewValue),
 806			TmpFireMask = ?bit_clr(OldFireMask, ?mask_falling),
 807			?bit_set(TmpFireMask, ?mask_rising);
 808		   StartUp /= ?mteTriggerThresholdStartup_rising,
 809		   NewValue < Falling ->
 810			fire_threshold_falling(H, Instance, NewValue),
 811			TmpFireMask = ?bit_clr(OldFireMask, ?mask_rising),
 812			?bit_set(TmpFireMask, ?mask_falling);
 813		   true ->
 814			OldFireMask
 815		end;
 816	   NewValue >= Rising, OldValue < Rising ->
 817		if ?bit_is_set(OldFireMask, ?mask_rising) -> no_fire;
 818		   true -> fire_threshold_rising(H, Instance, NewValue)
 819		end,
 820		TmpFireMask = ?bit_clr(OldFireMask, ?mask_falling),
 821		?bit_set(TmpFireMask, ?mask_rising);
 822	   NewValue =< Falling, OldValue > Falling ->
 823		if ?bit_is_set(OldFireMask, ?mask_falling) -> no_fire;
 824		   true -> fire_threshold_falling(H, Instance, NewValue)
 825		end,
 826		TmpFireMask = ?bit_clr(OldFireMask, ?mask_rising),
 827		?bit_set(TmpFireMask, ?mask_falling);
 828	   OldValue2 == '$undefined' ->
 829		%% second sampling; no delta
 830		OldFireMask;
 831	   DeltaRising /= 0, DeltaFalling /= 0 ->
 832		Delta1 = NewValue - OldValue,
 833		Delta2 = OldValue - OldValue2,
 834		if Delta1 >= DeltaRising, Delta2 < DeltaRising ->
 835			if ?bit_is_set(OldFireMask, ?mask_delta_rising) ->
 836				no_fire;
 837			   true ->
 838				fire_threshold_delta_rising(H, Instance,
 839							    NewValue)
 840			end,
 841			TmpFireMask = ?bit_clr(OldFireMask,?mask_delta_falling),
 842			?bit_set(TmpFireMask, ?mask_delta_rising);
 843		   Delta1 =< DeltaFalling, Delta2 > DeltaFalling ->
 844			if ?bit_is_set(OldFireMask, ?mask_delta_falling) ->
 845				no_fire;
 846			   true ->
 847				fire_threshold_delta_falling(H, Instance,
 848							     NewValue)
 849			end,
 850			TmpFireMask = ?bit_clr(OldFireMask, ?mask_delta_rising),
 851			?bit_set(TmpFireMask, ?mask_delta_falling);
 852		   true ->
 853			OldFireMask
 854		end;
 855	   true ->
 856		OldFireMask
 857	end,
 858    check_test(T, Instance, NewValue, OldValue, OldValue2, IsNew, NewFireMask);
 859check_test([], _Instance, _NewValue, _OldValue, _OldValue2, _IsNew, FireMask) ->
 860    FireMask.
 861
 862fire_existence(#mteTriggerExistenceTable{key = Key,
 863					 objectsOwner = ObjOwner,
 864					 objects = Obj,
 865					 eventOwner = EventOwner,
 866					 event = Event},
 867	       Instance) ->
 868    fire_event(Key, Instance, undefined, EventOwner, Event, ObjOwner, Obj).
 869
 870fire_boolean(#mteTriggerBooleanTable{key = Key,
 871				     objectsOwner = ObjOwner,
 872				     objects = Obj,
 873				     eventOwner = EventOwner,
 874				     event = Event},
 875	    Instance, Value, _Op, _OldVal, OldFireMask) ->
 876    ?dbg("boolean trigger fire (~p ~s ~p)\n", [Value, op2str(_Op), _OldVal]),
 877    if ?bit_is_set(OldFireMask, ?mask_boolean) ->
 878	    ?dbg("not fired b/c already fired\n", []),
 879	    OldFireMask;
 880       true ->
 881	    fire_event(Key, Instance, Value, EventOwner, Event, ObjOwner, Obj),
 882	    ?bit_set(OldFireMask, ?mask_boolean)
 883    end.
 884
 885fire_threshold_rising(
 886  #mteTriggerThresholdTable{key = Key,
 887			    objectsOwner = ObjOwner,
 888			    objects = Obj,
 889			    risingEventOwner = EventOwner,
 890			    risingEvent = Event},
 891	    Instance, Value) ->
 892    fire_event(Key, Instance, Value, EventOwner, Event, ObjOwner, Obj).
 893
 894fire_threshold_falling(
 895  #mteTriggerThresholdTable{key = Key,
 896			    objectsOwner = ObjOwner,
 897			    objects = Obj,
 898			    fallingEventOwner = EventOwner,
 899			    fallingEvent = Event},
 900	    Instance, Value) ->
 901    fire_event(Key, Instance, Value, EventOwner, Event, ObjOwner, Obj).
 902
 903fire_threshold_delta_rising(
 904  #mteTriggerThresholdTable{key = Key,
 905			    objectsOwner = ObjOwner,
 906			    objects = Obj,
 907			    deltaRisingEventOwner = EventOwner,
 908			    deltaRisingEvent = Event},
 909	    Instance, Value) ->
 910    fire_event(Key, Instance, Value, EventOwner, Event, ObjOwner, Obj).
 911
 912fire_threshold_delta_falling(
 913  #mteTriggerThresholdTable{key = Key,
 914			    objectsOwner = ObjOwner,
 915			    objects = Obj,
 916			    deltaFallingEventOwner = EventOwner,
 917			    deltaFallingEvent = Event},
 918	    Instance, Value) ->
 919    fire_event(Key, Instance, Value, EventOwner, Event, ObjOwner, Obj).
 920
 921fire_event(_Key, _Instance, _Value, _EventOwner, "", _ObjOwner, _Obj) ->
 922    ?dbg("trigger ~p fired (~p = ~p), but does not have an "
 923	 "event configured\n", [_Key, _Instance, _Value]),
 924    fire;
 925fire_event(Key, Instance, Value, EventOwner, Event, ObjOwner, Obj) ->
 926    EventKey = {EventOwner, Event},
 927    case mnesia:dirty_read({mteEventTable, EventKey}) of
 928	[E] when E#mteEventTable.entryStatus == ?RowStatus_active,
 929	         E#mteEventTable.enabled == ?TruthValue_true,
 930                 ?bit_is_set(E#mteEventTable.actions, ?notification) ->
 931	    %% Only notification is supported, the test above is strictly
 932            %% speaking unnecessary
 933	    ?dbg("trigger ~p fired (~p = ~p)\n", [Key, Instance, Value]),
 934	    [T] = mnesia:dirty_read({mteTriggerTable, Key}),
 935	    Context = T#mteTriggerTable.contextName,
 936	    Objs0 = objects(T#mteTriggerTable.objectsOwner,
 937			    T#mteTriggerTable.objects,
 938			    Context, Instance),
 939	    Objs1 = objects(ObjOwner, Obj, Context, Instance),
 940	    fire_notification(EventKey, T, Instance, Value, Objs0 ++ Objs1),
 941	    fire;
 942	_ ->
 943	    ?dbg("trigger ~p fired (~p = ~p), but the event ~p "
 944		 "does not exist\n", [Key, Instance, Value, EventKey]),
 945	    fire
 946    end.
 947
 948fire_notification(EventKey, T, Instance, Value, Objs) ->
 949    case mnesia:dirty_read({mteEventNotificationTable, EventKey}) of
 950	[N] ->
 951	    Objs3 = objects(N#mteEventNotificationTable.objectsOwner,
 952			    N#mteEventNotificationTable.objects,
 953			    T#mteTriggerTable.contextName,
 954			    Instance),
 955	    Notif = N#mteEventNotificationTable.notification,
 956	    case snmpa_symbolic_store:oid_to_aliasname(Notif) of
 957		{value, Alias} ->
 958		    if Alias == mteTriggerFired;
 959		       Alias == mteTriggerRising;
 960		       Alias == mteTriggerFalling ->
 961			    Objs0 = hot_objs(T, Instance, Value);
 962		       true ->
 963			    Objs0 = []
 964		    end,
 965		    Objs4 = Objs0 ++ Objs ++ Objs3,
 966		    ?dbg("sending notification ~p ~p\n", [Alias, Objs4]),
 967		    snmpa:send_notification(get(agent), Alias,
 968					    no_receiver, Objs4);
 969		_ ->
 970		    ?dbg("the notification ~p is not defined\n", [Notif]),
 971		    no_fire
 972	    end;
 973	_ ->
 974	    ?dbg("the eventNotification ~p does not exist\n", [EventKey]),
 975	    no_fire
 976    end.
 977
 978%% FIXME: Document in Agent caps that mteHotTargetName is not included,
 979%% and that mteHotValue is not present for existence triggers (the value
 980%% may be absent and/or not an integer)
 981hot_objs(#mteTriggerTable{key = {_,Name}, contextName = Context, valueID = ID},
 982	 Instance, Value) ->
 983    [{?mteHotTrigger_instance, Name},
 984     {?mteHotContextName_instance, Context},
 985     {?mteHotOID_instance, ID ++ Instance} |
 986     if integer(Value) ->
 987	     [{?mteHotValue_instance, Value}];
 988	true ->
 989	     []
 990     end
 991     ].
 992
 993objects(Owner, Name, Context, Instance) ->
 994    Prefix = [length(Owner) | Owner] ++ [length(Name) | Name],
 995    get_objects(Prefix, Prefix, Context, Instance).
 996
 997get_objects(RowIndex0, Prefix, Context, Instance) ->
 998    case mnesia:snmp_get_next_index(mteObjectsTable, RowIndex0) of
 999	{ok, RowIndex1} ->
1000	    case lists:prefix(Prefix, RowIndex1) of
1001		true -> % add this object
1002		    case mnesia:snmp_get_row(mteObjectsTable, RowIndex1) of
1003			{ok,
1004			 #mteObjectsTable{iD = ID, iDWildcard = Wild,
1005					  entryStatus = ?RowStatus_active}} ->
1006			    if Wild == ?TruthValue_true ->
1007				    OID = ID ++ Instance;
1008			       true ->
1009				    OID = ID
1010			    end,
1011			    case get_values([OID], Context) of
1012				[Val] ->
1013				    [{OID, Val} | 
1014				     get_objects(RowIndex1, Prefix,
1015						 Context, Instance)];
1016				_ ->
1017				    %% FIXME: error, increment counter??
1018				    get_objects(RowIndex1, Prefix,
1019						Context, Instance)
1020			    end;
1021			_ ->
1022			    get_objects(RowIndex1, Prefix, Context, Instance)
1023		    end;
1024		false ->
1025		    []
1026	    end;
1027	_ ->
1028	    []
1029    end.
1030
1031get_values(OIDs, Context) ->
1032    snmpa:get(get(agent), OIDs, Context).
1033
1034get_next_values(OIDs, Context) ->
1035    snmpa:get_next(get(agent), OIDs, Context).
1036    
1037
1038find_col(Col, [{Col, Val} | _], _) ->
1039    Val;
1040find_col(Col, [_ | T], Def) ->
1041    find_col(Col, T, Def);
1042find_col(_, [], Def) ->
1043    Def.
1044
1045fail(Reason, Key, Instance) ->
1046    ?dbg("fail: ~p ~p\n", [Reason, Key]),
1047    put(failures, get(failures) + 1),
1048    maybe_send_fail_notification(Reason, Key, Instance).
1049
1050-ifdef(debug).
1051maybe_send_fail_notification(Reason, Key, Instance) ->
1052    %% FIXME: send mteTriggerFailure notification here.  NOTE we should have
1053    %% a mechanism for enabling/disabling this event before implementing it.
1054    ReasonN =
1055	case Reason of
1056	    inconsistentName -> ?FailureReason_inconsistentName;
1057	    notWritable -> ?FailureReason_notWritable;
1058	    authorizationError -> ?FailureReason_authorizationError;
1059	    undoFailed -> ?FailureReason_undoFailed;
1060	    commitFailed -> ?FailureReason_commitFailed;
1061	    resourceUnavailable -> ?FailureReason_resourceUnavailable;
1062	    inconsistentValue -> ?FailureReason_inconsistentValue;
1063	    noCreation -> ?FailureReason_noCreation;
1064	    wrongValue -> ?FailureReason_wrongValue;
1065	    wrongEncoding -> ?FailureReason_wrongEncoding;
1066	    wrongLength -> ?FailureReason_wrongLength;
1067	    wrongType -> ?FailureReason_wrongType;
1068	    noAccess -> ?FailureReason_noAccess;
1069	    genErr -> ?FailureReason_genErr;
1070	    readOnly -> ?FailureReason_readOnly;
1071	    badValue -> ?FailureReason_badValue;
1072	    noSuchName -> ?FailureReason_noSuchName;
1073	    tooBig -> ?FailureReason_tooBig;
1074	    noError -> ?FailureReason_noError;
1075	    sampleOverrun -> ?FailureReason_sampleOverrun;
1076	    badType -> ?FailureReason_badType;
1077	    noResponse -> ?FailureReason_noResponse;
1078	    destinationUnreachable -> ?FailureReason_destinationUnreachable;
1079	    badDestination -> ?FailureReason_badDestination;
1080	    localResourceLack -> ?FailureReason_localResourceLack
1081	end,
1082    [#mteTriggerTable{key = {_,Name}, contextName = Context, valueID = ID}] = 
1083	mnesia:dirty_read({mteTriggerTable, Key}),
1084    Objs = [{?mteHotTrigger_instance, Name},
1085	    {?mteHotContextName_instance, Context},
1086	    {?mteHotOID_instance, ID ++ Instance},
1087	    {?mteFailedReason, ReasonN}],
1088    snmpa:send_notification(get(agent), mteTriggerFailure,
1089			    no_receiver, Objs).
1090-else.
1091maybe_send_fail_notification(_Reason, _Key, _Instance) ->
1092    ok.
1093-endif.
1094
1095prefix([H|T1], [H|T2]) ->
1096    prefix(T1, T2);
1097prefix([], T) ->
1098    {true, T};
1099prefix(_, _) ->
1100    false.
1101
1102op2str(?unequal)        -> "/=";
1103op2str(?equal)          -> "==";
1104op2str(?less)           -> "<";
1105op2str(?lessOrEqual)    -> "=<";
1106op2str(?greater)        -> ">";
1107op2str(?greaterOrEqual) -> ">=";
1108op2str(_)               -> "?".