extra function calls to lookup data and meta-data

This commit is contained in:
Dmitry Kolesnikov 2013-04-26 23:24:05 +03:00
parent cfdba43817
commit dfcf699149
10 changed files with 508 additions and 282 deletions

View File

@ -17,7 +17,7 @@
%,{remove, 1}
]}.
{cache, {mycache, 'cache@127.0.0.1'}}.
%{cache, {mycache, 'cache@127.0.0.1'}}.
{local, [
{ttl, 20}
,{n, 10}

View File

@ -1,2 +1,3 @@
{cover_enabled, true}.
{deps, []}.

View File

@ -1,57 +1,130 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%% segmented in-memory cache, cache memory is split into N segments,
%% each segment is associated with quota and ttl, eviction policy applied
%% at segment level.
-module(cache).
-include("cache.hrl").
-export([
start_link/2, drop/1, i/1,
put/3, put/4, put_/3, put_/4,
get/2, has/2,
start_link/2,
drop/1,
i/1,
heap/2,
put/3, put/4, put_/3, put_/4,
get/2, lookup/2,
has/2, ttl/2,
remove/2, remove_/2
]).
-type(key() :: any()).
-type(entity() :: any()).
%%
%%
%% start new cache
-spec(start_link/2 :: (atom(), list()) -> {ok, pid()} | {error, any()}).
start_link(Cache, Opts) ->
cache_bucket:start_link(Cache, Opts).
%%
%%
%% drop cache
-spec(drop/1 :: (atom()) -> ok).
drop(Cache) ->
erlang:exit(whereis(Cache), shutdown).
%%
%%
%% return cache meta data
-spec(i/1 :: (atom()) -> list()).
i(Cache) ->
gen_server:call(Cache, i).
%%
%% return nth cache heap
-spec(heap/2 :: (atom(), integer()) -> integer() | badarg).
heap(Cache, N) ->
gen_server:call(Cache, {heap, N}).
%%
%% put entity to cache
-spec(put/3 :: (atom(), key(), entity()) -> ok).
-spec(put_/3 :: (atom(), key(), entity()) -> ok).
-spec(put/4 :: (atom(), key(), entity(), integer()) -> ok).
-spec(put_/4 :: (atom(), key(), entity(), integer()) -> ok).
put(Cache, Key, Val) ->
gen_server:call(Cache, {put, Key, Val}).
gen_server:call(Cache, {put, Key, Val}, ?DEF_IO_TIMEOUT).
put(Cache, Key, Val, TTL) ->
gen_server:call(Cache, {put, Key, Val, TTL}).
gen_server:call(Cache, {put, Key, Val, TTL}, ?DEF_IO_TIMEOUT).
put_(Cache, Key, Val) ->
gen_server:cast(Cache, {put, Key, Val}).
put_(Cache, Key, Val, TTL) ->
gen_server:cast(Cache, {put, Key, Val, TTL}).
%%
%%
%% get entity from cache, prolong ttl
-spec(get/2 :: (atom(), key()) -> entity() | undefined).
get(Cache, Key) ->
gen_server:call(Cache, {get, Key}).
gen_server:call(Cache, {get, Key}, ?DEF_IO_TIMEOUT).
%%
%% lookup entity at cache do not prolong ttl
-spec(lookup/2 :: (atom(), key()) -> entity() | undefined).
lookup(Cache, Key) ->
gen_server:call(Cache, {lookup, Key}, ?DEF_IO_TIMEOUT).
%%
%% check entity at cache
-spec(has/2 :: (atom(), key()) -> true | false).
has(Cache, Key) ->
gen_server:call(Cache, {has, Key}).
gen_server:call(Cache, {has, Key}, ?DEF_IO_TIMEOUT).
%%
%% check entity at cache and return estimated ttl
-spec(ttl/2 :: (atom(), key()) -> true | false).
ttl(Cache, Key) ->
gen_server:call(Cache, {ttl, Key}, ?DEF_IO_TIMEOUT).
%%
%% remove entity from cache
-spec(remove/2 :: (atom(), key()) -> ok).
-spec(remove_/2 :: (atom(), key()) -> ok).
remove(Cache, Key) ->
gen_server:call(Cache, {remove, Key}).
gen_server:call(Cache, {remove, Key}, ?DEF_IO_TIMEOUT).
%%
%%
remove_(Cache, Key) ->
gen_server:cast(Cache, {remove, Key}).

View File

@ -17,3 +17,5 @@
%% default cache house keeping frequency
-define(DEF_CACHE_QUOTA, 5).
-define(DEF_IO_TIMEOUT,60000).

View File

@ -1,4 +1,22 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
-module(cache_app).
-behaviour(application).
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').

View File

@ -1,3 +1,21 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%% basho_bench driver
-module(cache_benchmark).

View File

@ -1,4 +1,23 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%%
-module(cache_bucket).
-behaviour(gen_server).
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
@ -12,18 +31,27 @@
%%
%%
-record(cache, {
name :: atom(), %% name of cache bucket
heap :: list(), %% heap
name :: atom(), %% name of cache bucket
heap = [] :: list(), %% cache heap segments
n = ?DEF_CACHE_N :: integer(), %% number of cells
n = ?DEF_CACHE_N :: integer(), %% number of segments
ttl = ?DEF_CACHE_TTL :: integer(), %% chunk time to live
policy = ?DEF_CACHE_POLICY :: integer(), %% eviction policy
policy = ?DEF_CACHE_POLICY :: integer(), %% eviction policy
quota = ?DEF_CACHE_QUOTA :: integer(), %% frequency of limit enforcement (clean-up)
quota_size :: integer(), %% max number of elements
quota_memory :: integer(), %% max number of memory in bytes
cardinality :: integer(), %% cache cardinality
memory :: integer(), %% cache memory limit
counter :: any() %% counter functor
quota = ?DEF_CACHE_QUOTA :: integer(), %% quota enforcement timer
evict = undefined, %% evict timer
stats :: any() %% stats functor
}).
-record(heap, {
id :: integer(), %% segment heap
expire :: integer(), %% segment expire time
cardinality :: integer(), %% segment cardinality quota
memory :: integer() %% segment memory quota
}).
%%
@ -41,10 +69,10 @@ init([{policy, X} | T], #cache{}=S) ->
init(T, S#cache{policy=X});
init([{memory, X} | Opts], S) ->
init(Opts, S#cache{quota_memory=X});
init(Opts, S#cache{memory=X div erlang:sysinfo(wordsize)});
init([{size, X} | Opts], S) ->
init(Opts, S#cache{quota_size=X});
init([{size, X} | Opts], S) ->
init(Opts, S#cache{cardinality=X});
init([{n, X} | Opts], S) ->
init(Opts, S#cache{n = X});
@ -55,24 +83,26 @@ init([{ttl, X} | Opts], S) ->
init([{quota, X} | Opts], S) ->
init(Opts, S#cache{quota=X});
init([{counter, X} | Opts], S) ->
init(Opts, S#cache{counter=X});
init([{stats, X} | Opts], S) ->
init(Opts, S#cache{stats=X});
init([_ | Opts], S) ->
init(Opts, S);
init([], S) ->
random:seed(erlang:now()),
set_quota_timeout(S),
set_evict_timeout(S),
S#cache{
heap = cache_heap:new(S#cache.n)
}.
Evict = cache_util:mdiv(S#cache.ttl, S#cache.n),
init_heap(
S#cache{
evict = cache_util:timeout(Evict * 1000, evict),
quota = cache_util:timeout(S#cache.quota * 1000, quota)
}
).
%%
%%
terminate(_Reason, S) ->
cache_heap:free(S#cache.heap),
terminate(_Reason, _S) ->
%cache_heap:free(S#cache.heap),
ok.
%%%----------------------------------------------------------------------------
@ -82,35 +112,40 @@ terminate(_Reason, S) ->
%%%----------------------------------------------------------------------------
handle_call({put, Key, Val}, _, S) ->
{reply, ok, insert(Key, Val, S)};
{reply, ok, cache_put(Key, Val, S)};
handle_call({put, Key, Val, _}, _, S) ->
{reply, ok, insert(Key, Val, S)};
handle_call({put, Key, Val, TTL}, _, S) ->
{reply, ok, cache_put(Key, Val, cache_util:now() + TTL, S)};
handle_call({get, Key}, _, S) ->
case lookup(Key, S) of
undefined ->
inc_counter(miss, S),
{reply, undefined, S};
Val ->
inc_counter(hit, S),
{reply, Val, S}
end;
{reply, cache_get(Key, S), S};
handle_call({lookup, Key}, _, S) ->
{reply, cache_lookup(Key, S), S};
handle_call({has, Key}, _, S) ->
case member(Key, S) of
true -> {reply, true, S};
_ -> {reply, false, S}
end;
{reply, cache_has(Key, S), S};
handle_call({ttl, Key}, _, S) ->
{reply, cache_ttl(Key, S), S};
handle_call({remove, Key}, _, S) ->
{reply, ok, remove(Key, S)};
{reply, ok, cache_remove(Key, S)};
handle_call(i, _, S) ->
Cells = cache_heap:cells(S#cache.heap),
Size = cache_heap:size(S#cache.heap),
Memory = cache_heap:memory(S#cache.heap),
{reply, [{heap, Cells}, {size, Size}, {memory, Memory}], S};
Heap = [X#heap.id || X <- S#cache.heap],
Expire = [X#heap.expire || X <- S#cache.heap],
Size = [ets:info(X#heap.id, size) || X <- S#cache.heap],
Memory = [ets:info(X#heap.id, memory) || X <- S#cache.heap],
{reply, [{heap, Heap}, {expire, Expire}, {size, Size}, {memory, Memory}], S};
handle_call({heap, N}, _, S) ->
try
H = lists:nth(N, S#cache.heap),
{reply, H#heap.id, S}
catch _:_ ->
{reply, badarg, S}
end;
handle_call(_, _, S) ->
{noreply, S}.
@ -118,24 +153,58 @@ handle_call(_, _, S) ->
%%
%%
handle_cast({put, Key, Val}, S) ->
{noreply, insert(Key, Val, S)};
{noreply, cache_put(Key, Val, S)};
handle_cast({put, Key, Val, _}, S) ->
{noreply, insert(Key, Val, S)};
handle_cast({put, Key, Val, TTL}, S) ->
{noreply, cache_put(Key, Val, cache_util:now() + TTL, S)};
handle_cast({remove, Key}, S) ->
{noreply, remove(Key, S)};
{noreply, cache_remove(Key, S)};
handle_cast(_, S) ->
{noreply, S}.
handle_info(evict, S) ->
set_evict_timeout(S),
{noreply, evict(S)};
Now = cache_util:now(),
case lists:last(S#cache.heap) of
H when H#heap.expire =< Now ->
{noreply,
free_heap(
S#cache{evict = cache_util:timeout(S#cache.evict, evict)}
)
};
_ ->
{noreply,
init_heap(
S#cache{evict = cache_util:timeout(S#cache.evict, evict)}
)
}
end;
handle_info(quota, S) ->
set_quota_timeout(S),
{noreply, quota(S)};
case is_heap_out_of_quota(hd(S#cache.heap)) of
true ->
case length(S#cache.heap) of
N when N =:= S#cache.n ->
{noreply,
free_heap(
S#cache{quota = cache_util:timeout(S#cache.evict, quota)}
)
};
_ ->
{noreply,
init_heap(
S#cache{quota = cache_util:timeout(S#cache.evict, quota)}
)
}
end;
false ->
{noreply,
S#cache{
quota = cache_util:timeout(S#cache.quota, quota)
}
}
end;
handle_info(_, S) ->
{noreply, S}.
@ -152,160 +221,184 @@ code_change(_Vsn, S, _Extra) ->
%%%
%%%----------------------------------------------------------------------------
%% set-up cache evict timeout
set_evict_timeout(#cache{ttl=undefined}) ->
ok;
set_evict_timeout(S) ->
T = (S#cache.ttl div S#cache.n) * 1000,
erlang:send_after(T, self(), evict).
set_quota_timeout(#cache{quota=undefined}) ->
ok;
set_quota_timeout(S) ->
T = S#cache.quota * 1000,
erlang:send_after(T, self(), quota).
%% update statistic
inc_counter(_Id, #cache{counter=undefined}) ->
ok;
inc_counter(Id, #cache{counter={M, F}, name=Name}) ->
M:F({cache, Name, Id});
inc_counter(Id, #cache{counter=Fun, name=Name}) ->
Fun({cache, Name, Id}).
%%
%% evict key from cells
evict_key(Key, List) ->
dowhile(
fun(Cell) ->
case ets:member(Cell, Key) of
true ->
ets:delete(Cell, Key),
Cell;
Result ->
Result
end
end,
List
).
%%
%%
insert(Key, Val, #cache{}=S) ->
[Head | Tail] = cache_heap:cells(S#cache.heap),
true = ets:insert(Head, {Key, Val}),
_ = evict_key(Key, Tail),
inc_counter(put, S),
?DEBUG("cache ~p: put ~p to cell ~p~n", [S#cache.name, Key, Head]),
%% insert value to cache
cache_put(Key, Val, #cache{}=S) ->
[Head | Tail] = S#cache.heap,
true = ets:insert(Head#heap.id, {Key, Val}),
lists:foreach(
fun(X) -> ets:delete(X#heap.id, Key) end,
Tail
),
cache_util:stats(S#cache.stats, {cache, S#cache.name, put}),
?DEBUG("cache ~p: put ~p to heap ~p~n", [S#cache.name, Key, Head#heap.id]),
S.
%%
%%
remove(Key, S) ->
Cell = evict_key(Key, cache_heap:cells(S#cache.heap)),
?DEBUG("cache ~p: remove ~p in cell ~p~n", [S#cache.name, Key, Cell]),
S.
cache_put(Key, Val, Time, #cache{}=S) ->
case lists:splitwith(fun(X) -> X#heap.expire > Time end, S#cache.heap) of
{[], _Tail} ->
cache_put(Key, Val, S);
{Head, Tail} ->
[Heap | Rest] = lists:reverse(Head),
true = ets:insert(Heap#heap.id, {Key, Val}),
lists:foreach(
fun(X) -> ets:delete(X#heap.id, Key) end,
Rest ++ Tail
),
cache_util:stats(S#cache.stats, {cache, S#cache.name, put}),
?DEBUG("cache ~p: put ~p to heap ~p~n", [S#cache.name, Key, Heap#heap.id]),
S
end.
%%
%%
member(Key, S) ->
dowhile(
fun(X) -> ets:member(X, Key) end,
cache_heap:cells(S#cache.heap)
).
%%
%%
lookup(Key, #cache{policy=mru}=S) ->
cache_get(Key, #cache{policy=mru}=S) ->
% cache always evicts last generation
% MRU caches do not prolong recently used entity
dowhile(
fun(Cell) ->
case ets:lookup(Cell, Key) of
[] ->
false;
[{_, Val}] ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Cell]),
Val
end
end,
lists:reverse(cache_heap:cells(S#cache.heap))
);
case heap_lookup(Key, S#cache.heap) of
undefined ->
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}),
undefined;
{Heap, Val} ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
Val
end;
lookup(Key, S) ->
[Head | Tail] = cache_heap:cells(S#cache.heap),
case ets:lookup(Head, Key) of
% no value at head chunk, lookup and evict tail
[] ->
dowhile(
fun(Cell) ->
case ets:lookup(Cell, Key) of
[] ->
false;
[{_, Val}] ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Cell]),
_ = ets:delete(Cell, Key),
true = ets:insert(Head, {Key, Val}),
Val
end
end,
Tail
);
[{_, Val}] ->
?DEBUG("cache ~p: get ~p in cell ~p~n", [S#cache.name, Key, Head]),
cache_get(Key, #cache{}=S) ->
Head = hd(S#cache.heap),
case heap_lookup(Key, S#cache.heap) of
undefined ->
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}),
undefined;
{Heap, Val} when Heap#heap.id =:= Head#heap.id ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
Val;
{Heap, Val} ->
true = ets:insert(Head#heap.id, {Key, Val}),
_ = ets:delete(Heap#heap.id, Key),
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
Val
end.
cache_lookup(Key, #cache{}=S) ->
case heap_lookup(Key, S#cache.heap) of
undefined ->
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}),
undefined;
{Heap, Val} ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
Val
end.
cache_has(Key, #cache{}=S) ->
case heap_has(Key, S#cache.heap) of
false ->
false;
Heap ->
?DEBUG("cache ~p: has ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
true
end.
cache_ttl(Key, #cache{}=S) ->
case heap_has(Key, S#cache.heap) of
false ->
undefined;
Heap ->
Heap#heap.expire - cache_util:now()
end.
cache_remove(Key, #cache{}=S) ->
lists:foreach(
fun(X) -> ets:delete(X#heap.id, Key) end,
S#cache.heap
),
cache_util:stats(S#cache.stats, {cache, S#cache.name, remove}),
?DEBUG("cache ~p: remove ~p~n", [S#cache.name, Key]),
S.
%%
%% execute predicate while it succeeded
dowhile(Pred, [Head | Tail]) ->
case Pred(Head) of
% predicate false, apply predicate to next chunk
false -> dowhile(Pred, Tail);
Result -> Result
%%
heap_lookup(Key, [H | Tail]) ->
case ets:lookup(H#heap.id, Key) of
[] -> heap_lookup(Key, Tail);
[{_, Val}] -> {H, Val}
end;
dowhile(_Pred, []) ->
heap_lookup(_Key, []) ->
undefined.
%%
%%
evict(#cache{}=S) ->
Cell = cache_heap:last(S#cache.heap),
?DEBUG("cache ~p: free cell ~p~n", [S#cache.name, Cell]),
heap_has(Key, [H | Tail]) ->
case ets:member(H#heap.id, Key) of
false -> heap_has(Key, Tail);
true -> H
end;
heap_has(_Key, []) ->
false.
% %%
% %%
% remove(Key, S) ->
% Cell = evict_key(Key, cache_heap:cells(S#cache.heap)),
% ?DEBUG("cache ~p: remove ~p in cell ~p~n", [S#cache.name, Key, Cell]),
% S.
% %%
% %%
% member(Key, S) ->
% dowhile(
% fun(X) -> ets:member(X, Key) end,
% cache_heap:cells(S#cache.heap)
% ).
%%
%% init cache heap
init_heap(#cache{}=S) ->
Id = ets:new(undefined, [set, protected]),
?DEBUG("cache ~p: init heap ~p~n", [S#cache.name, Id]),
Heap = #heap{
id = Id,
expire = cache_util:madd(S#cache.ttl, cache_util:now()),
cardinality = cache_util:mdiv(S#cache.cardinality, S#cache.n),
memory = cache_util:mdiv(S#cache.memory, S#cache.n)
},
S#cache{
heap = cache_heap:alloc(cache_heap:free(Cell, S#cache.heap))
heap = [Heap | S#cache.heap]
}.
%%
%%
quota(#cache{}=S) ->
maybe_memory_quota(
maybe_size_quota(S)
free_heap(#cache{}=S) ->
[H | Tail] = lists:reverse(S#cache.heap),
Size = ets:info(H#heap.id, size),
ets:delete(H#heap.id),
cache_util:stats(S#cache.stats, {cache, S#cache.name, evicted}, Size),
?DEBUG("cache ~p: free heap ~p~n", [S#cache.name, H#heap.id]),
init_heap(
S#cache{
heap = lists:reverse(Tail)
}
).
%%
%%
is_heap_out_of_quota(#heap{}=H) ->
is_out_of_memory(H) orelse is_out_of_capacity(H).
is_out_of_capacity(#heap{cardinality=undefined}) ->
false;
is_out_of_capacity(#heap{cardinality=N}=H) ->
ets:info(H#heap.id, size) >= N.
is_out_of_memory(#heap{memory=undefined}) ->
false;
is_out_of_memory(#heap{memory=N}=H) ->
ets:info(H#heap.id, memory) >= N.
maybe_size_quota(#cache{quota_size=undefined}=S) ->
S;
maybe_size_quota(S) ->
Quota = S#cache.quota_size div S#cache.n,
[Head | _] = cache_heap:cells(S#cache.heap),
case ets:info(Head, size) of
Val when Val >= Quota ->
evict(S);
_ ->
S
end.
maybe_memory_quota(#cache{quota_memory=undefined}=S) ->
S;
maybe_memory_quota(S) ->
Quota = (S#cache.quota_memory div erlang:system_info(wordsize)) div S#cache.n,
[Head | _] = cache_heap:cells(S#cache.heap),
case ets:info(Head, memory) of
Val when Val >= Quota ->
evict(S);
_ ->
S
end.

View File

@ -1,86 +0,0 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%% heap of ETS entities (sorted by age)
-module(cache_heap).
-export([
new/1, alloc/1, free/1, free/2,
cells/1, size/1, memory/1, last/1, first/1
]).
-record(heap, {
cells :: list() %% list of cells
}).
%%
%% create new heap
new(N) ->
lists:foldl(
fun(_, Acc) -> alloc(Acc) end,
#heap{cells=[]},
lists:seq(1, N)
).
%%
%% allocate new cell
alloc(#heap{}=H) ->
H#heap{
cells = [create() | H#heap.cells]
}.
create() ->
ets:new(undefined, [set, protected]).
%%
%% free cells
free(#heap{cells=Cells}) ->
[ets:delete(X) || {_, X} <- Cells],
ok.
free(Cell, #heap{}=H) ->
ets:delete(Cell),
H#heap{
cells = lists:delete(Cell, H#heap.cells)
}.
%%
%%
cells(#heap{}=H) ->
H#heap.cells.
%%
%%
size(#heap{}=H) ->
[ets:info(X, size) || X <- H#heap.cells].
%%
%%
memory(#heap{}=H) ->
[ets:info(X, memory) || X <- H#heap.cells].
%%
%%
last(#heap{}=H) ->
lists:last(H#heap.cells).
%%
%%
first(#heap{}=H) ->
hd(H#heap.cells).

View File

@ -1,4 +1,23 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%% root cache supervisor
-module(cache_sup).
-behaviour(supervisor).

88
src/cache_util.erl Normal file
View File

@ -0,0 +1,88 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% 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.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%%
-module(cache_util).
-export([
mdiv/2, madd/2, mmul/2, now/0, stats/2, stats/3, timeout/2
]).
%%
%%
mdiv(undefined, _) ->
undefined;
mdiv(X, Y) ->
X div Y.
%%
%%
madd(undefined, _) ->
undefined;
madd(X, Y) ->
X + Y.
%%
%%
mmul(undefined, _) ->
undefined;
mmul(X, Y) ->
X * Y.
%%
%%
now() ->
{Mega, Sec, _} = erlang:now(),
Mega * 1000000 + Sec.
%%
%%
stats(undefined, _) ->
ok;
stats({M, F}, Counter) ->
M:F(Counter);
stats(Fun, Counter)
when is_function(Fun) ->
Fun(Counter).
stats(undefined, _, _) ->
ok;
stats({M, F}, Counter, Val) ->
M:F(Counter, Val);
stats(Fun, Counter, Val)
when is_function(Fun) ->
Fun(Counter, Val).
%%
%%
%%
%% re-set alarm after time
timeout(T, Msg)
when is_integer(T) ->
{clock, T, erlang:send_after(T, self(), Msg)};
timeout({clock, T, Timer}, Msg) ->
erlang:cancel_timer(Timer),
{clock, T, erlang:send_after(T, self(), Msg)};
timeout(X, _) ->
X.