src/exometer_admin.erl

%% -------------------------------------------------------------------
%%
%% Copyright (c) 2014 Basho Technologies, Inc.  All Rights Reserved.
%%
%%   This Source Code Form is subject to the terms of the Mozilla Public
%%   License, v. 2.0. If a copy of the MPL was not distributed with this
%%   file, You can obtain one at http://mozilla.org/MPL/2.0/.
%%
%% -------------------------------------------------------------------

-module(exometer_admin).

-behaviour(gen_server).

-export(
   [
    init/1,
    start_link/0,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3
   ]).

-export(
   [
    new_entry/3,
    propose/3,
    re_register_entry/3,
    repair_entry/1,
    delete_entry/1,
    ensure/3,
    auto_create_entry/1
   ]).

-export(
   [
    set_default/3,
    preset_defaults/0,
    load_defaults/0,
    load_predefined/0,
    register_application/1,
    normalize_name/1
   ]).
-export([find_auto_template/1, prefixes/1, make_patterns/2]).

-export([monitor/2, monitor/3, demonitor/1]).

-compile({no_auto_import, [monitor/3]}).
-include_lib("hut/include/hut.hrl").
-include("exometer.hrl").

-record(st, {}).

-spec set_default([atom()], atom(), #exometer_entry{} | [{atom(),any()}]) ->
                         true.

%% @doc Sets a default definition for a metric type, possibly using wildcards.
%%
%% Names are lists of atoms, where '_' is a wildcard. For example,
%% <code>[a, b, c, '_']</code> matches all children and grandchildren of
%% `[a, b, c]', whereas `[a, b, c, d]' specifies a single name.
%%
%% The longest match will be selected, unless an exact match is found.
%% The definition can be given either as an `#exometer_entry{}' record, or
%% a list of `{Key, Value}' tuples, where each `Key' matches an attribute
%% of the `#exometer_entry{}' record.
%% @end
set_default(NamePattern0, Type, #exometer_entry{} = E)
  when is_list(NamePattern0) ->
    NamePattern = lists:map(fun('_') -> '';
                               ('' ) -> error({not_allowed, ''});
                               (X  ) -> X
                            end, NamePattern0),
    ets:insert(?EXOMETER_SHARED,
               E#exometer_entry{name = {default,Type,NamePattern},
                                type = Type});
set_default(NamePattern, Type, Opts) when is_list(NamePattern) ->
    set_default(NamePattern, Type, opts_to_rec(Type, Opts)).

preset_defaults() ->
    load_defaults(),
    load_predefined().

load_defaults() ->
    E = exometer_util:get_env(defaults, []),
    do_load_defaults(env, get_predef(E)),
    [do_load_defaults(Src, get_predef(D))
     || {Src,D} <- setup:find_env_vars(exometer_defaults)],
    ok.

load_predefined() ->
    E = exometer_util:get_env(predefined, []),
    do_load_predef(env, get_predef(E)),
    [do_load_predef(Src, get_predef(P))
     || {Src, P} <- setup:find_env_vars(exometer_predefined)],
    ok.

register_application(App) ->
    %% Ignore if exometer is not running
    case whereis(exometer_admin) of
        undefined -> ok;
        _ ->
            case setup:get_env(App, exometer_defaults) of
                {ok, E} ->
                    do_load_defaults(App, get_predef(E));
                undefined ->
                    ok
            end,
            case setup:get_env(App, exometer_predefined) of
                {ok, P} ->
                    do_load_predef(App, get_predef(P));
                undefined ->
                    ok
            end
    end.

get_predef({script, F} ) -> ok(file:script(F, []), F);
get_predef({consult,F} ) -> ok(file:consult(F), F);
get_predef({apply, M, F, A}) -> ok(apply(M, F, A), {M,F,A});
get_predef(L) when is_list(L) -> L.

do_load_defaults(Src, L) when is_list(L) ->
    lists:foreach(
      fun({NamePattern, Type, Spec}) ->
              try set_default(NamePattern, Type, Spec)
              catch
                  error:E ->
                      ?log(error, "Defaults(~p): ERROR: ~p~n", [Src, E])
              end
      end, L).

do_load_predef(Src, L) when is_list(L) ->
    lists:foreach(
      fun({Name, Type, Options}) ->
              new_entry(Name, Type, Options);
         ({delete, Key}) ->
              predef_delete_entry(Key, Src);
         ({re_register, {Name, Type, Options}}) ->
              re_register_entry(Name, Type, Options);
         ({select_delete, Pat}) ->
              Found = exometer:select(Pat),
              lists:foreach(
                fun({K,_,_}) ->
                        predef_delete_entry(K, Src);
                   (Other) ->
                        ?log(error, "Predef(~p): ~p~n",
                                    [Src, {bad_pattern,Other}])
                end, Found);
         ({aliases, Aliases}) ->
              lists:foreach(
                fun({Alias, Entry, DP}) ->
                        exometer_alias:new(Alias, Entry, DP)
                end, Aliases)
      end, L).

predef_delete_entry(Key, Src) ->
    case delete_entry(Key) of
        ok -> ok;
        Error ->
            ?log(error, "Predef(~p): ~p~n", [Src, Error])
    end.

ok({ok, Res}, _) -> Res;
ok({error, E}, I) ->
    erlang:error({E, I}).

new_entry(Name, Type, Opts) ->
    %% { arg, { function, M, F }}
    %% { arg, { function, M, F }}
    {Type1, Opt1} = check_type_arg(Type, Opts),
    case gen_server:call(?MODULE, {new_entry, Name, Type1, Opt1, false}) of
        {error, Reason} ->
            error(Reason);
        ok ->
            ok
    end.

propose(Name, Type, Opts) ->
    {Type1, Opt1} = check_type_arg(Type, Opts),
    gen_server:call(?MODULE, {propose, Name, Type1, Opt1}).

report_new_entry(#exometer_entry{} = E) ->
    exometer_report:new_entry(E).

re_register_entry(Name, Type, Opts) ->
    {Type1, Opts1} = check_type_arg(Type, Opts),
    case gen_server:call(?MODULE, {new_entry, Name, Type1, Opts1, true}) of
        {error, Reason} ->
            error(Reason);
        ok ->
            ok
    end.

repair_entry(Name) ->
    case gen_server:call(?MODULE, {repair_entry, Name}) of
        {error, Reason} ->
            error(Reason);
        ok ->
            ok
    end.

delete_entry(Name) ->
    gen_server:call(?MODULE, {delete_entry, Name}).

ensure(Name, Type, Opts) ->
    {Type1, Opt1} = check_type_arg(Type, Opts),
    case gen_server:call(?MODULE, {ensure, Name, Type1, Opt1}) of
        {error, Reason} ->
            error(Reason);
        ok ->
            ok
    end.

auto_create_entry(Name) ->
    gen_server:call(?MODULE, {auto_create, Name}).

check_type_arg({function, M, F}, Opts) ->
    {function, [{arg, {M, F}} | Opts]};

check_type_arg({function, Mod, Fun, ArgSpec, Type, DataPoints}, Opts) ->
    {function, [{arg, {Mod, Fun, ArgSpec, Type, DataPoints}} | Opts]};

check_type_arg({T, Arg}, Opts) ->
    {T, [{arg, Arg} | Opts]};

check_type_arg(Type, Opts) ->
    {Type, Opts}.

monitor(Name, Pid) when is_pid(Pid) ->
    monitor(Name, Pid, delete).

monitor(Name, Pid, OnError) when is_pid(Pid) ->
    gen_server:cast(?MODULE, {monitor, Name, Pid, OnError}).

demonitor(Pid) when is_pid(Pid) ->
    gen_server:cast(?MODULE, {demonitor, Pid}).

opts_to_rec(Type, Opts0) ->
    Opts = case lists:keymember(module, 1, Opts0) of
               true -> Opts0;
               false -> [{module, module(Type)}|Opts0]
           end,
    Flds = record_info(fields, exometer_entry),
    lists:foldr(fun({K,V}, Acc) ->
                        setelement(pos(K, Flds), Acc, V)
                end, #exometer_entry{options = Opts0}, Opts).

pos(K, L) -> pos(K, L, 2).

pos(K, [K|_], P) -> P;
pos(K, [_|T], P) -> pos(K, T, P+1);
pos(K, []   , _) -> error({unknown_option, K}).

normalize_name(N) when is_tuple(N) ->
    tuple_to_list(N);
normalize_name(N) when is_list(N) ->
    N.

start_link() ->
    create_ets_tabs(),
    gen_server:start_link({local,?MODULE}, ?MODULE, [], []).

init(_) ->
    {ok, #st{}}.

handle_call({new_entry, Name, Type, Opts, AllowExisting} = _Req, _From, S) ->
    try
        #exometer_entry{options = NewOpts} = E0 =
            lookup_definition(Name, Type, Opts),

        case {ets:lookup(exometer_util:table(), Name), AllowExisting} of
            {[_], false} ->
                {reply, {error, exists}, S};
            {LookupRes, _} ->
                E1 = process_opts(E0, NewOpts),
                try
                   remove_old_instance(LookupRes, Name)
                catch
                    ?EXCEPTION(Cat, Exception, Stacktrace1) ->
                        ?log(debug, "CAUGHT(~p) ~p:~p / ~p",
                             [Name, Cat, Exception, ?GET_STACK(Stacktrace1)]),
                        ok
                end,
                Res = try
                          exometer:create_entry(E1),
                          exometer_report:new_entry(E1)
                      catch
                          ?EXCEPTION(error, Error1, Stacktrace2) ->
                              ?log(debug,
                                   "ERROR create_entry(~p) :- ~p~n~p",
                                   [E1, Error1, ?GET_STACK(Stacktrace2)]),
                              erlang:error(Error1)
                      end,
                {reply, Res, S}
        end
    catch
        ?EXCEPTION(error, Error, Stacktrace) ->
            ?log(error, "~p -*-> error:~p~n~p~n",
                 [_Req, Error, ?GET_STACK(Stacktrace)]),
            {reply, {error, Error}, S}
    end;
handle_call({repair_entry, Name}, _From, S) ->
    try
        #exometer_entry{} = E = exometer:info(Name, entry),
        delete_entry_(Name),
        exometer:create_entry(E),
        {reply, ok, S}
    catch
        error:Error ->
            {reply, {error, Error}, S}
    end;
handle_call({propose, Name, Type, Opts}, _From, S) ->
    try
        #exometer_entry{options = NewOpts} = E0 =
            lookup_definition(Name, Type, Opts),
        E1 = process_opts(E0, NewOpts),
        {reply, exometer_info:pp(E1), S}
    catch
        error:Error ->
            {reply, {error, Error}, S}
    end;
handle_call({delete_entry, Name}, _From, S) ->
    {reply, delete_entry_(Name), S};
handle_call({ensure, Name, Type, Opts}, _From, S) ->
    case ets:lookup(exometer_util:table(), Name) of
        [#exometer_entry{type = Type}] ->
            {reply, ok, S};
        [#exometer_entry{type = _OtherType}] ->
            {reply, {error, type_mismatch}, S};
        [] ->
            #exometer_entry{options = OptsTemplate} = E0 =
                lookup_definition(Name, Type, Opts),
            E1 = process_opts(E0, OptsTemplate ++ Opts),
            Res = exometer:create_entry(E1),
            report_new_entry(E1),
            {reply, Res, S}
    end;
handle_call({auto_create, Name}, _From, S) ->
    case find_auto_template(Name) of
        false ->
            {reply, {error, no_template}, S};
        #exometer_entry{options = Opts} = E ->
            E1 = process_opts(E#exometer_entry{name = Name}, Opts),
            Res = exometer:create_entry(E1),
            report_new_entry(E1),
            {reply, Res, S}
    end;
handle_call(_, _, S) ->
    {reply, error, S}.

handle_cast({monitor, Name, Pid, OnError}, S) ->
    Ref = erlang:monitor(process, Pid),
    put(Ref, {Name, OnError}),
    put(Pid, Ref),
    {noreply, S};
handle_cast({demonitor, Pid}, S) ->
    case get(Pid) of
        undefined ->
            {noreply, S};
        Ref ->
            erase(Pid),
            erase(Ref),
            try erlang:demonitor(Ref) catch error:_ -> ok end,
            {noreply, S}
    end;
handle_cast(_, S) ->
    {noreply, S}.

handle_info({'DOWN', Ref, _, Pid, _}, S) ->
    case get(Ref) of
        undefined ->
            {noreply, S};
        {Name, OnError} when is_atom(Name); is_list(Name) ->
            erase(Ref),
            erase(Pid),
	    on_error(Name, OnError),
            {noreply, S};
	Name when is_atom(Name) ->
	    %% BW compat
            erase(Ref),
            erase(Pid),
            {noreply, S};
        Name when is_list(Name) ->
	    %% BW compat
            erase(Ref),
            erase(Pid),
	    on_error(Name, delete),
            {noreply, S}
    end;
handle_info(_, S) ->
    {noreply, S}.

terminate(_, _) ->
    ok.

code_change(_, S, _) ->
    case ets:info(?EXOMETER_REPORTERS, name) of
        undefined -> create_reporter_tabs();
        _ -> ok
    end,
    {ok, S}.

create_reporter_tabs() ->
    Heir = {heir, whereis(exometer_sup), []},
    ets:new(?EXOMETER_REPORTERS, [public, named_table, set,
                                  {keypos, 2}, Heir]),
    ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set,
                             {keypos, 2}, Heir]).


create_ets_tabs() ->
    case ets:info(?EXOMETER_SHARED, name) of
        undefined ->
            [ets:new(T, [public, named_table, set, {keypos,2}, {read_concurrency, true}, {write_concurrency, true}, {decentralized_counters, true}])
             || T <- tables()],
            ets:new(?EXOMETER_SHARED, [public, named_table, ordered_set,
                                       {keypos, 2}]),
            ets:new(?EXOMETER_ENTRIES, [public, named_table, ordered_set,
                                        {keypos, 2}, {read_concurrency, true}, {write_concurrency, true}]),
            ets:new(?EXOMETER_REPORTERS, [public, named_table, set,
                                          {keypos, 2}]),
            ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set,
                                     {keypos, 2}]);
        _ ->
            true
    end.

tables() ->
    exometer_util:tables().


%% ====

on_error(Name, {restart, {M, F, A}}) ->
    try call_restart(M, F, A) of
	{ok, Ref} ->
	    if is_list(Name) ->
		    [ets:update_element(T, Name, [{#exometer_entry.ref, Ref}])
		     || T <- exometer_util:tables()];
	       true -> ok
	    end,
	    ok;
        disable ->
            try_disable_entry_(Name);
        delete ->
            try_delete_entry_(Name);
	Other ->
	    restart_failed(Name, Other)
    catch error:R ->
	    restart_failed(Name, {error, R})
    end,
    ok;
on_error(Name, delete) ->
    try_delete_entry_(Name);
on_error(_Proc, _OnError) ->
    %% Not good, but will do for now.
    ?log(debug, "Unrecognized OnError: ~p (~p)~n", [_OnError, _Proc]),
    ok.

call_restart(M, F, A) ->
    apply(M, F, A).

restart_failed(Name, Error) ->
    ?log(debug, "Restart failed ~p: ~p~n", [Name, Error]),
    if is_list(Name) ->
	    try_delete_entry_(Name);
       true ->
	    ok
    end.

lookup_definition(Name, Type, Opts) ->
    check_aliases(lookup_definition_(Name, Type, Opts)).

lookup_definition_(Name, ad_hoc, Opts) ->
    case [K || K <- [module, type], not lists:keymember(K, 1, Opts)] of
        [] ->
            {E0, Opts1} =
                lists:foldr(
                  fun({module, M}, {E,Os}) ->
                          {E#exometer_entry{module = M}, Os};
                     ({type, T}, {E, Os}) ->
                          case T of
                              {Type, Arg} ->
                                  {E#exometer_entry{type = Type},
                                   [{arg, Arg}|Os]};
                              _ when is_atom(T) ->
                                  {E#exometer_entry{type = T}, Os}
                          end;
                     (O, {E, Os}) ->
                          {E, [O|Os]}
                  end, {#exometer_entry{name = Name}, []}, Opts),
            E0#exometer_entry{options = Opts1};
        [_|_] = Missing ->
            error({required, Missing})
    end;
lookup_definition_(Name, Type, Opts) ->
    E = case ets:prev(?EXOMETER_SHARED, {default, Type, <<>>}) of
            {default, Type, N} = D0 when N==[''], N==Name ->
                case ets:lookup(?EXOMETER_SHARED, D0) of
                    [#exometer_entry{} = Def] ->
                        Def;
                    [] ->
                        default_definition_(Name, Type)
                end;
            {default, OtherType, _} when Type=/=OtherType ->
                exometer_default(Name, Type);
            '$end_of_table' ->
                exometer_default(Name, Type);
            _ ->
                default_definition_(Name, Type)
        end,
    merge_opts(Opts, E).

merge_opts(Opts, #exometer_entry{options = DefOpts} = E) ->
    Opts1 = lists:foldl(fun({'--', Keys}, Acc) ->
                                case is_list(Keys) of
                                    true ->
                                        lists:foldl(
                                          fun(K,Acc1) ->
                                                  lists:keydelete(K, 1, Acc1)
                                          end, Acc, Keys);
                                    false ->
                                        error({invalid_option,'--'})
                                end;
                           ({K,V}, Acc) ->
                                lists:keystore(K, 1, Acc, {K,V})
                        end, DefOpts, Opts),
    E#exometer_entry{options = Opts1}.

check_aliases(#exometer_entry{name = N, options = Opts} = E) ->
    case lists:keyfind(aliases, 1, Opts) of
	{_, Aliases} ->
	    exometer_alias:check_map([{N, Aliases}]);
	_ ->
	    ok
    end,
    E.

default_definition_(Name, Type) ->
    case search_default(Name, Type) of
        #exometer_entry{} = E ->
            E#exometer_entry{name = Name};
        false ->
            exometer_default(Name, Type)
    end.

exometer_default(Name, Type) ->
    #exometer_entry{name = Name, type = Type, module = module(Type)}.

module(counter )      -> exometer;
module(gauge)         -> exometer;
module(fast_counter)  -> exometer;
module(uniform)       -> exometer_uniform;
module(duration)      -> exometer_duration;
module(histogram)     -> exometer_histogram;
module(spiral   )     -> exometer_spiral;
module(netlink  )     -> exometer_netlink;
module(cpu      )     -> exometer_cpu;
module(function )     -> exometer_function.

search_default(Name, Type) ->
    case ets:lookup(?EXOMETER_SHARED, {default,Type,Name}) of
        [] ->
            case ets:select_reverse(
                   ?EXOMETER_SHARED, make_patterns(Type, Name), 1) of
                {[#exometer_entry{} = E],_} ->
                    E#exometer_entry{name = Name};
                '$end_of_table' ->
                    false
            end;
        [#exometer_entry{} = E] ->
            E
    end.

sort_defaults(L) ->
    lists:sort(fun comp_templates/2,
               [E || #exometer_entry{type = T} = E <- L,
                     T =/= function andalso T =/= cpu]).

comp_templates(#exometer_entry{name = {default, _, A}, type = Ta},
               #exometer_entry{name = {default, _, B}, type = Tb}) ->
    comp_names(A, B, Ta, Tb).


comp_names([H |A], [H |B], Ta, Tb) -> comp_names(A, B, Ta, Tb);
comp_names([''|_], [_ |_], _, _) -> false;
comp_names([_ |_], [''|_], _, _) -> true;
comp_names([],     [_ |_], _, _) -> false;
comp_names([_ |_], []    , _, _) -> true;
comp_names([],     []    , A, B) -> comp_types(A, B).

comp_types(histogram, _) -> true;
comp_types(counter, B) when B=/=histogram -> true;
comp_types(gauge  , B) when B=/=histogram, B=/=counter -> true;
comp_types(spiral , B) when B=/=histogram, B=/=counter, B=/=gauge -> true;
comp_types(A, B) -> A =< B.

-spec find_auto_template(exometer:name()) -> #exometer_entry{} | false.
%% @doc Convenience function for testing which template will apply to
%% `Name'. See {@link set_default/2} and {@link exometer:update_or_create/2}.
%% @end
find_auto_template(Name) ->
    case sort_defaults(ets:select(?EXOMETER_SHARED,
                                  make_patterns('_', Name))) of
        [] -> false;
        [#exometer_entry{name = {default,_,['']}}|_] -> false;
        [#exometer_entry{} = E|_] -> E
    end.

make_patterns(Type, Name) when is_list(Name) ->
    Prefixes = prefixes(Name),
    [{ #exometer_entry{name = {default,Type,[V || {_,V} <- Pfx]}, _ = '_'},
       [{'or',{'=:=',V,X},{'=:=',V,''}} || {X,V} <- Pfx], ['$_'] }
     || Pfx <- Prefixes].

prefixes(L) ->
    Vars = vars(),
    prefixes(L,Vars,[],[]).

vars() ->
    ['$1','$2','$3','$4','$5','$6','$7','$8','$9',
     '$10','$11','$12','$13','$14','$15','$16'].

prefixes([H|T],[V|Vs],Acc,Ps) ->
    P1 = [{H,V}|Acc],
    prefixes(T,Vs,P1,[lists:reverse(P1)|Ps]);
prefixes([],_,_,Ps) ->
    Ps.

%% make_patterns([H|T], Type, Acc) ->
%%     Acc1 = Acc ++ [H],
%%     ID = Acc1 ++ [''],
%%     [{ #exometer_entry{name = {default, Type, ID}, _ = '_'}, [], ['$_'] }
%%      | make_patterns(T, Type, Acc1)];
%% make_patterns([], Type, _) ->
%%     [{ #exometer_entry{name = {default, Type, ['']}, _ = '_'}, [], ['$_'] }].


%% This function is called when creating an #exometer_entry{} record.
%% All options are passed unchanged to the callback module, but some
%% are acted upon by the framework: namely 'cache' and 'status'.
process_opts(Entry, Options) ->
    lists:foldr(
      fun
          %% Some future  exometer_entry-level option
          %% ({something, Val}, Entry1) ->
          %%        Entry1#exometer_entry { something = Val };
          %% Unknown option, pass on to exometer entry options list, replacing
          %% any earlier versions of the same option.
          ({cache, Val}, E) ->
                       if is_integer(Val), Val >= 0 ->
                               E#exometer_entry{cache = Val};
                          true ->
                               error({illegal, {cache, Val}})
                       end;
          ({status, Status}, #exometer_entry{} = E) ->
                       if Status==enabled; Status==disabled ->
                               Status1 = exometer_util:set_status(
                                           Status, Entry#exometer_entry.status),
                               E#exometer_entry{status = Status1};
                          true ->
                               error({illegal, {status, Status}})
                       end;
          ({update_event, UE}, #exometer_entry{} = E) when is_boolean(UE) ->
                       if UE ->
                               Status = exometer_util:set_event_flag(
                                          update, E#exometer_entry.status),
                               E#exometer_entry{status = Status};
                          true ->
                               Status = exometer_util:clear_event_flag(
                                          update, E#exometer_entry.status),
                               E#exometer_entry{status = Status}
                       end;
          ({_Opt, _Val}, #exometer_entry{} = Entry1) ->
                       Entry1
               end, Entry#exometer_entry{options = Options}, Options).

try_disable_entry_(Name) when is_list(Name) ->
    try exometer:setopts(Name, [{status, disabled}])
    catch
        error:Err ->
            ?log(debug, "Couldn't disable ~p: ~p~n", [Name, Err]),
            try_delete_entry_(Name)
    end;
try_disable_entry_(_Name) ->
    ok.

try_delete_entry_(Name) ->
    try delete_entry_(Name)
    catch
	error:R ->
	    ?log(debug, "Couldn't delete ~p: ~p~n", [Name, R]),
	    ok
    end.

delete_entry_(Name) ->
    exometer_cache:delete_name(Name),
    case ets:lookup(exometer_util:table(), Name) of
        [#exometer_entry{} = Entry] ->
            try
                remove_old_instance(Entry, Name)
            after
                [ets:delete(T, Name) ||
                    T <- [?EXOMETER_ENTRIES|exometer_util:tables()]]
            end,
            ok;
        [] ->
            {error, not_found}
    end.

remove_old_instance([], _) ->
    ok;
remove_old_instance([Entry], Name) ->
    remove_old_instance(Entry, Name);
remove_old_instance(#exometer_entry{module = exometer, type = fast_counter,
                                    ref = {M, F}}, _) ->
    exometer_util:set_call_count(M, F, false);
remove_old_instance(#exometer_entry{behaviour = probe,
                                    type = Type, ref = Ref}, Name) ->
    exometer_probe:delete(Name, Type, Ref);
remove_old_instance(#exometer_entry{module = Mod, behaviour = entry,
                                    type = Type, ref = Ref}, Name) ->
    Mod:delete(Name, Type, Ref);
remove_old_instance(_, _) ->
    ok.