PageRenderTime 62ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/mibs/src/event_mib.erl

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