diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/src/misc/snmp_note_store.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/snmp/src/misc/snmp_note_store.erl')
-rw-r--r-- | lib/snmp/src/misc/snmp_note_store.erl | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/lib/snmp/src/misc/snmp_note_store.erl b/lib/snmp/src/misc/snmp_note_store.erl new file mode 100644 index 0000000000..a21a6209f1 --- /dev/null +++ b/lib/snmp/src/misc/snmp_note_store.erl @@ -0,0 +1,450 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmp_note_store). + +-behaviour(gen_server). + +-include_lib("snmp/src/app/snmp_internal.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + +%% External exports +-export([start_link/3, stop/1, + get_note/2, + set_note/3, set_note/4, + info/1, verbosity/2]). + +%% Internal exports +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-export([timer/3]). + +-define(timeout, 30000). % Perform gc twice in a minute. + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-ifdef(snmp_debug). +-define(GS_START_LINK(Args), + gen_server:start_link(?MODULE, Args, [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Args), + gen_server:start_link(?MODULE, Args, [])). +-endif. + + +-record(state, {mod, notes, timer, timeout, active = false}). + + +%%%----------------------------------------------------------------- +%%% Implements a database for notes with a lifetime. Once in a +%%% while, the database will be gc:ed, to get rid of old notes. +%%% This database will not contain much data. +%%% Options is a list of Option, where Option is +%%% {verbosity, silence|log|debug|trace} % undocumented feature +%%%----------------------------------------------------------------- +start_link(Prio, Mod, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Mod: ~p" + "~n Opts: ~p", [Prio, Mod, Opts]), + ?GS_START_LINK([Prio, Mod, Opts]). + + +%%----------------------------------------------------------------- +%% Interface functions. +%%----------------------------------------------------------------- + +stop(Pid) -> + call(Pid, stop). + +get_note(Pid, Key) -> + call(Pid, {get_note, Key}). + +%% Lifetime is in 1/10 sec or infinity +set_note(Pid, Key, Value) -> + set_note(Pid, infinity, Key, Value). +set_note(Pid, Lifetime, Key, Value) -> + call(Pid, {set_note, Lifetime, Key, Value}). + +info(Pid) -> + call(Pid, info). + +verbosity(Pid, Verbosity) -> + cast(Pid, {verbosity, Verbosity}). + + +init([Prio, Mod, Opts]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Mod: ~p" + "~n Opts: ~p", [Prio, Mod, Opts]), + case (catch do_init(Prio, Mod, Opts)) of + {ok, State} -> + {ok, State}; + E -> + error_msg("failed starting note-store: ~n~p", [E]), + {stop, E} + end. + +do_init(Prio, Mod, Opts) -> + process_flag(trap_exit, true), + process_flag(priority, Prio), + put(sname, get_sname(Opts)), + put(verbosity, get_verbosity(Opts)), + put(snmp_component, get_component(Mod)), + ?vlog("starting",[]), + Notes = ets:new(snmp_note_store, [set, protected]), + Timeout = get_timeout(Opts), + State = #state{mod = Mod, + notes = Notes, + timeout = Timeout, + timer = start_timer(Timeout)}, + ?vdebug("started",[]), + {ok, State}. + + +%%----------------------------------------------------------------- +%% A note is any internal information that has to be +%% stored for some time (the Lifetime). +%% A note is stored in ets as {Key, {BestBefore, Value}}, +%% where BestBefore is currentTime + Lifetime. +%% A GC-op can destroy any notes with CurTime > BestBore. +%% Lifetime is in centiseconds or infinity, in which case +%% the note is eternal. +%%----------------------------------------------------------------- +handle_call({set_note, Lifetime, Key, Value}, _From, + #state{mod = Mod, notes = Notes} = State) + when is_integer(Lifetime) -> + ?vlog("set note <~p,~p> with life time ~p", [Key,Value,Lifetime]), + case (catch Mod:system_start_time()) of + SysStartTime when is_integer(SysStartTime) -> + ?vtrace("handle_call(set_note) -> SysStartTime: ~p", + [SysStartTime]), + Now = snmp_misc:now(cs), + ?vtrace("handle_call(set_note) -> Now: ~p", [Now]), + RealUpTime = Now - SysStartTime, + ?vtrace("handle_call(set_note) -> RealUpTime: ~p", [RealUpTime]), + BestBefore = RealUpTime + Lifetime, + ?vtrace("handle_call(set_note) -> BestBefore: ~p", [BestBefore]), + Val = ets:insert(Notes, {Key, {BestBefore, Value}}), + NState = activate_timer(State), + {reply, Val, NState}; + _Crap -> + ?vinfo("handle_call(set_note) -> " + "failed retreiving system start time from ~w: " + "~n ~p", [Mod, _Crap]), + {reply, {error, failed_retreive_system_start_time}, State} + end; + +handle_call({set_note, infinity, Key, Value}, _From, + #state{notes = Notes} = State) -> + ?vlog("set note <~p,~p>",[Key,Value]), + Val = ets:insert(Notes, {Key, {infinity, Value}}), + ?vdebug("set note; old value: ~p",[Val]), + {reply, Val, State}; + +handle_call({get_note, Key}, _From, + #state{mod = Mod, notes = Notes} = State) -> + ?vlog("get note ~p",[Key]), + Val = handle_get_note(Notes, Mod, Key), + ?vdebug("get note: ~p",[Val]), + {reply, Val, State}; + +handle_call(info, _From, #state{timer = Pid, notes = Notes} = State) -> + ?vlog("info",[]), + Info = get_info(Pid, Notes), + {reply, Info, State}; + +handle_call(stop, _From, State) -> + ?vlog("stop",[]), + {stop, normal, ok, State}; + +handle_call(Req, From, State) -> + warning_msg("received unexpected request from ~p: ~n~p",[From, Req]), + {reply, {error, {unknown_request, Req}}, State}. + + +handle_cast({verbosity,Verbosity}, State) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,snmp_verbosity:validate(Verbosity)), + {noreply, State}; + +handle_cast(Msg, State) -> + warning_msg("received unexpected message: ~n~p",[Msg]), + {noreply, State}. + + +%%----------------------------------------------------------------- +%% If there are no possible garbage left, we don't +%% have to wait for timeout, and perform another +%% gc, because we won't do anything. So +%% we switch the timeout off in that case. +%% It will be switched on as soon as we get some +%% other message. +%%----------------------------------------------------------------- +handle_info(timeout, State) -> + ?vdebug("timeout",[]), + case gc(State) of + nothing_left -> + NState = deactivate_timer(State), + {noreply, NState}; + work_to_do -> + NState = activate_timer(State), + {noreply, NState} + end; + +handle_info({'EXIT', Pid, Reason}, + #state{timer = Pid, timeout = Timeout} = State) -> + ?vinfo("exit message from the timer process ~p for reason ~p", + [Pid, Reason]), + set_state(State#state{timer = start_timer(Timeout)}); + +handle_info({'EXIT',Pid,Reason}, State) -> + ?vlog("exit message from ~p for reason ~p",[Pid,Reason]), + {noreply, State}; + +handle_info(Info, State) -> + warning_msg("received unexpected info: ~n~p",[Info]), + {noreply, State}. + + +set_state(S) -> + case gc(S) of + nothing_left -> + NState = deactivate_timer(S), + {noreply, NState}; + work_to_do -> + NState = activate_timer(S), + {noreply, NState} + end. + + +terminate(Reason, _State) -> + ?vdebug("terminate: ~p",[Reason]), + ok. + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +% downgrade +code_change({down, _Vsn}, State, _Extra) -> + NState = activate_timer(deactivate_timer(State)), + {ok, NState}; + +% upgrade +code_change(_Vsn, State, _Extra) -> + process_flag(trap_exit, true), + NState = restart_timer(State), + {ok, NState}. + + +%%---------------------------------------------------------- +%% Timer +%%---------------------------------------------------------- + +activate_timer(#state{timer = Pid, active = false} = State) -> + Pid ! activate, + receive + activated -> ok + end, + State#state{active = true}; +activate_timer(State) -> + State. + +deactivate_timer(#state{timer = Pid, active = true} = State) -> + Pid ! deactivate, + receive + deactivated -> ok + end, + State#state{timeout = false}; +deactivate_timer(State) -> + State. + +start_timer(Timeout) -> + spawn_link(?MODULE, timer, [self(), passive, Timeout]). + +%% Kill, restart and activate timer. +restart_timer(#state{timer = Pid, timeout = Timeout} = State) -> + ?d("restart_timer -> kill current timer process ~p",[Pid]), + exit(Pid, kill), + ?d("restart_timer -> await acknowledgement",[]), + receive + {'EXIT', Pid, _Reason} -> + ok + end, + ?d("restart_timer -> start a new timer process",[]), + activate_timer(State#state{timer = start_timer(Timeout), active = false}). + +timer(Pid, passive, Timeout) -> + receive + deactivate -> + ?d("timer(passive) -> deactivate request, just send ack",[]), + Pid ! deactivated, + ?MODULE:timer(Pid, passive, Timeout); + + activate -> + ?d("timer(deactive) -> activate request, send ack",[]), + Pid ! activated, + ?d("timer(deactive) -> activate",[]), + ?MODULE:timer(Pid, active, Timeout) % code replacement + after + Timeout -> + ?d("timer(deactive) -> timeout",[]), + ?MODULE:timer(Pid, passive, Timeout) + end; +timer(Pid, active, Timeout) -> + receive + activate -> + ?d("timer(active) -> activate request, just send ack",[]), + Pid ! activated, + ?MODULE:timer(Pid, active, Timeout); + + deactivate -> + ?d("timer(active) -> deactivate request, send ack",[]), + Pid ! deactivated, + ?d("timer(active) -> deactivate",[]), + ?MODULE:timer(Pid, passive, Timeout) + after + Timeout -> + ?d("timer(active) -> timeout",[]), + Pid ! timeout, + ?MODULE:timer(Pid, active, Timeout) + end. + + +handle_get_note(Notes, Mod, Key) -> + case ets:lookup(Notes, Key) of + [{Key, {infinity, Val}}] -> + Val; + [{Key, {BestBefore, Val}}] -> + ?vtrace("get note -> BestBefore: ~w", [BestBefore]), + StartTime = Mod:system_start_time(), + ?vtrace("get note -> StartTime: ~w", [StartTime]), + Now = snmp_misc:now(cs), + ?vtrace("get note -> Now: ~w", [Now]), + case (Now - StartTime) of + Diff when BestBefore >= Diff -> + ?vtrace("get note -> Diff: ~w", [Diff]), + Val; + OldDiff -> + ?vtrace("get note -> note to old [~w] - delete", [OldDiff]), + ets:delete(Notes, Key), + undefined + end; + [] -> + undefined + end. + + +%%----------------------------------------------------------------- +%% Clean up all old notes in the database. +%%----------------------------------------------------------------- +gc(#state{mod = Mod, notes = Notes}) -> + RealUpTime = snmp_misc:now(cs) - Mod:system_start_time(), + gc(nothing_left, ets:tab2list(Notes), Notes, RealUpTime). + +gc(Flag, [{_Key, {infinity, _}} | T], Tab, Now) -> gc(Flag, T, Tab, Now); +gc(Flag, [{Key, {BestBefore, _}} | T], Tab, Now) + when is_integer(BestBefore) andalso (BestBefore < Now) -> + ets:delete(Tab, Key), + gc(Flag, T, Tab, Now); +gc(_Flag, [_ | T], Tab, Now) -> gc(work_to_do, T, Tab, Now); +gc(Flag, [], _Tab, _Now) -> Flag. + + +%%----------------------------------------------------------------- + +get_info(Tmr, Notes) -> + ProcSize = proc_mem(self()), + TMRSz = proc_mem(Tmr), + NotesSz = tab_size(Notes), + [{process_memory, [{notes, ProcSize}, {timer, TMRSz}]}, + {db_memory, [{notes, NotesSz}]}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} -> + Sz; + _ -> + undefined + end; +proc_mem(_) -> + undefined. + +tab_size(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + + +%%----------------------------------------------------------------- + +call(Pid, Req) -> + call(Pid, Req, infinity). + +call(Pid, Req, Timeout) -> + gen_server:call(Pid, Req, Timeout). + +cast(Pid, Msg) -> + gen_server:cast(Pid, Msg). + + +%%----------------------------------------------------------------- + +%% info_msg(F, A) -> +%% ?snmp_info(get(snmp_component), "Note store server " ++ F, A). + +warning_msg(F, A) -> + ?snmp_warning(get(snmp_component), "Note store server " ++ F, A). + +error_msg(F, A) -> + ?snmp_error(get(snmp_component), "Note store server " ++ F, A). + + +%%----------------------------------------------------------------- + +get_verbosity(Opts) -> + snmp_misc:get_option(verbosity, Opts, ?default_verbosity). + +get_sname(Opts) -> + snmp_misc:get_option(sname, Opts, ns). + +get_timeout(Opts) -> + snmp_misc:get_option(timeout, Opts, ?timeout). + +get_component(snmpm) -> + "manager"; +get_component(snmpa) -> + "agent"; +get_component(_) -> + "". + |