Initial import.

This commit is contained in:
Gregory Burd 2013-02-21 16:02:32 -05:00
commit 7b5b040f64
8 changed files with 659 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.eunit/*
deps/*
ebin
priv/*.so
*.o
*.beam

58
Makefile Normal file
View file

@ -0,0 +1,58 @@
TARGET= hamt
REBAR= /usr/bin/env rebar
ERL= /usr/bin/env erl
DIALYZER= /usr/bin/env dialyzer
REBAR= /usr/bin/env rebar
ifdef suites
SUITE_OPTION := suites=$(suites)
endif
ifdef tests
TESTS_OPTION := tests=$(tests)
endif
EUNIT_OPTIONS := $(SUITE_OPTION) $(TESTS_OPTION)
.PHONY: deps test
all: deps compile
deps:
$(REBAR) get-deps
compile: deps
$(REBAR) compile
clean:
$(REBAR) clean
distclean: clean
$(REBAR) delete-deps
eunit: test
test: compile
$(REBAR) skip_deps=true $(EUNIT_OPTIONS) eunit
console: compile
erl -pa ebin deps/*/ebin
plt: compile
@$(DIALYZER) --build_plt --output_plt .$(TARGET).plt \
-pa deps/plain_fsm/ebin \
deps/plain_fsm/ebin \
--apps kernel stdlib
analyze: compile
$(DIALYZER) --plt .$(TARGET).plt \
-pa deps/plain_fsm/ebin \
-pa deps/ebloom/ebin \
ebin
repl:
$(ERL) -pz deps/*/ebin -pa ebin
eunit-repl:
erl -pa .eunit -pz deps/*/ebin -pz ebin -exec 'cd(".eunit").'
gdb-eunit-repl:
USE_GDB=1 erl -pa .eunit -pz deps/*/ebin -pz ebin -exec 'cd(".eunit").'

33
rebar.config Normal file
View file

@ -0,0 +1,33 @@
%%% -*- mode: erlang -*-
{require_otp_vsn, "R15"}.
{cover_enabled, true}.
%{clean_files, ["*.eunit", "ebin/*.beam"]}.
{eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}.
{erl_opts, [%{d,'DEBUG',true},
debug_info,
fail_on_warning,
warn_unused_vars,
warn_export_all,
warn_shadow_vars,
warn_unused_import,
warn_unused_function,
warn_bif_clash,
warn_unused_record,
warn_deprecated_function,
warn_obsolete_guard,
warn_export_vars,
warn_exported_vars,
warn_untyped_record
%warn_missing_spec,
%strict_validation
%{parse_transform, lager_transform},
]}.
{xref_checks, [undefined_function_calls]}.
{deps, [
]}.

65
src/bitpop.erl Normal file
View file

@ -0,0 +1,65 @@
%% -*- coding: utf-8 -*-
%%
%% %CopyrightBegin%
%%
%% Copyright (C) 2013 Gregory Burd. All Rights Reserved.
%%
%% The contents of this file are subject to the Mozilla Public License,
%% Version 2, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Mozilla Public License along with this software. If not, it can be
%% retrieved online at http://www.mozilla.org/MPL/
%%
%% 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(bitpop).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-export([count/1]).
count(0) -> 0;
count(X) when is_integer(X), X > 0 -> count(X, 0).
count(0, Acc) -> Acc;
count(X, Acc) -> count((X band (X - 1)), (Acc + 1)).
-ifdef(TEST).
bitpop_test_() ->
[?_assertEqual(0, count(0)),
?_assertEqual(1, count(1)),
?_assertEqual(2, count(3)),
?_assertEqual(3, count(7)),
?_assertEqual(4, count(15)),
?_assertEqual(5, count(31)),
?_assertEqual(6, count(63)),
?_assertEqual(7, count(127)),
?_assertEqual(8, count(255)),
?_assertEqual(1, count(4)),
?_assertEqual(1, count(8)),
?_assertEqual(1, count(16)),
?_assertEqual(1, count(32)),
?_assertEqual(1, count(64)),
?_assertEqual(1, count(128)),
?_assertEqual(1, count(256)),
?_assertEqual(1, count(512)),
?_assertEqual(1, count(1024)),
?_assertEqual(1, count(2048)),
?_assertEqual(1, count(16#FFFF + 1)),
?_assertEqual(19, count(16#FFFFE)),
?_assertEqual(1, count(16#FFFFF + 1)),
?_assertEqual(23, count(16#FFFFFE)),
?_assertEqual(1, count(16#FFFFF + 1)),
?_assertEqual(27, count(16#FFFFFFE)),
?_assertEqual(1, count(16#FFFFF + 1)),
?_assertEqual(31, count(16#FFFFFFFE)),
?_assertException(error, function_clause, count(-1))].
-endif.

12
src/hamt.app.src Normal file
View file

@ -0,0 +1,12 @@
{application, hamt,
[
{description, "Hash Array Mapped Tries"},
{vsn, "1.0.0"},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, {hamt_app, []}},
{env, []}
]}.

442
src/hamt.erl Normal file
View file

@ -0,0 +1,442 @@
%% -*- coding: utf-8 -*-
%%
%% %CopyrightBegin%
%%
%% Copyright (C) 2013 Gregory Burd. All Rights Reserved.
%%
%% The contents of this file are subject to the Mozilla Public License,
%% Version 2, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Mozilla Public License along with this software. If not, it can be
%% retrieved online at http://www.mozilla.org/MPL/
%%
%% 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%
%%
%% =========================================================================
%% Ideal Hash Array Mapped Tries: an Erlang functional datatype
%%
%% The Hash Array Mapped Trie (HAMT) is based on the simple notion of hashing a
%% key and storing the key in a trie based on this hash value. The AMT is used
%% to implement the required structure e#ciently. The Array Mapped Trie (AMT)
%% is a versatile data structure and yields attractive alternative to
%% contemporary algorithms in many applications. Here I describe how it is used
%% to develop Hash Trees with near ideal characteristics that avoid the
%% traditional problem, setting the size of the initial root hash table or
%% incurring the high cost of dynamic resizing to achieve an acceptable
%% performance.
%%
%% Based on the paper "Ideal Hash Tries" by Phil Bagwell [2000].
%% @ARTICLE{Bagwell01idealhash,
%% author = {Phil Bagwell},
%% title = {Ideal Hash Trees},
%% journal = {Es Grands Champs},
%% year = {2001},
%% volume = {1195}
%% }
%% http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.6279
%% http://lampwww.epfl.ch/papers/idealhashtrees.pdf
%% http://en.wikipedia.org/wiki/Hash_array_mapped_trie
%% -------------------------------------------------------------------------
%% Operations:
%%
%% - new(): returns empty hamt.
%%
%% - is_empty(T): returns 'true' if T is an empty hamt, and 'false'
%% otherwise.
%%
%% - get(K, T): retreives the value stored with key K in hamt T or
%% `not_found' if the key is not present in the hamt.
%%
%% - put(K, V, T): inserts key K with value V into hamt T; if the key
%% is not present in the hamt, otherwise updates key X to value V in
%% T. Returns the new hamt.
%%
%% - delete(K, T): removes key K from hamt T; returns new hamt. Assumes
%% that the key is present in the hamt.
%%
%% - map(F, T): maps the function F(K, V) -> V' to all key-value pairs
%% of the hamt T and returns a new hamt T' with the same set of keys
%% as T and the new set of values V'.
%%
%% - fold(F, T, Acc): applies the function F(K, V, Acc) -> V' to all
%% key-value pairs of the hamt T and returns the accumulated result.
%%
-module(hamt).
-compile({no_auto_import,[put/2]}).
-ifdef(PULSE).
-compile({parse_transform, pulse_instrument}).
-endif.
-ifdef(TEST).
-ifdef(EQC).
%include_lib("eqc/include/eqc.hrl").
%include_lib("eqc/include/eqc_fsm.hrl").
-endif.
-compile(export_all).
-include_lib("eunit/include/eunit.hrl").
-endif.
-export([new/0, is_empty/1, get/2, put/2, put/3, delete/2,
map/2, fold/3,
from_list/1, to_list/1]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The Hamt data structure consists of:
%% - {hamt, nil | {SNode, CNode, LNode}
%% - {snode, Key::binary(), Value::binary()}
%% - {cnode, Bitmap, Branch}
%% - {lnode, [snode]}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Some types.
-export_type([hamt/0]).
-type hamt_snode() :: {snode, binary(), binary()}.
-type hamt_lnode() :: {lnode, [hamt_snode()]}.
-type hamt_cnode() :: {cnode, non_neg_integer(), [hamt_snode() | hamt_cnode() | hamt_lnode()]}.
-opaque hamt() :: {hamt, non_neg_integer(), nil | hamt_cnode()}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec new() -> hamt().
new() ->
{hamt, nil}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec is_empty(Hamt) -> boolean() when
Hamt :: hamt().
is_empty({hamt, nil}) ->
true;
is_empty(_) ->
false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec get(Key, Hamt) -> not_found | Value when
Key :: binary(),
Value :: binary(),
Hamt :: hamt().
get(_Key, {hamt, nil}) ->
not_found;
get(Key, {hamt, {snode, Key, Value}}) ->
Value;
get(Key, {hamt, {cnode, _Bitmap, _Nodes}=CN}) ->
case get_1(hash(Key), CN, 0) of
none ->
not_found;
{Key, Value} ->
Value;
{list, List} ->
case get_2(Key, List) of
none -> not_found;
{Key, Value} -> Value;
{_Key, _Value} -> not_found
end;
{_Key, _Value} ->
not_found
end.
get_1(H, {cnode, Bitmap, Nodes}, L) ->
Bit = bitpos(H, L),
case exists(Bit, Bitmap) of
true -> get_1(H, ba_get(index(Bit, Bitmap), Nodes), L + 5);
false -> none
end;
get_1(_H, {snode, Key, Value}, _L) ->
{Key, Value};
get_1(_H, {lnode, List}, _L) when is_list(List) ->
{list, List}.
get_2(_Key, []) ->
none;
get_2(Key, [{Key, Value} | _Rest]) ->
{Key, Value};
get_2(Key, [{_DifferentKey, _Value} | Rest]) ->
get_2(Key, Rest).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
from_list(L) ->
put(L, hamt:new()).
to_list({hamt, _}=T) ->
fold(fun(Key, Value, Acc) -> [{Key, Value} | Acc] end, T, []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec put([{Key, Value}], Hamt1) -> Hamt2 when
Key :: binary(),
Value :: binary(),
Hamt1 :: hamt(),
Hamt2 :: hamt().
put([], {hamt, _Node}=T) ->
T;
put([{Key, Value} | Rest], {hamt, _Node}=T) ->
put(Rest, put(Key, Value, T)).
-spec put(Key, Value, Hamt1) -> Hamt2 when
Key :: binary(),
Value :: binary(),
Hamt1 :: hamt(),
Hamt2 :: hamt().
put(Key, Value, {hamt, nil})
when is_binary(Key), is_binary(Value) ->
{hamt, {snode, Key, Value}};
put(Key, Value, {hamt, Node})
when is_binary(Key), is_binary(Value) ->
{hamt, put_1(hash(Key), Key, Value, Node, 0)}.
put_1(H, Key, Value, {cnode, Bitmap, Nodes}, L) when is_integer(L), L =< 30 ->
Bit = bitpos(H, L),
Idx = index(Bit, Bitmap),
case exists(Bit, Bitmap) of
true ->
CN = put_1(H, Key, Value, ba_get(Idx, Nodes), L + 5),
{cnode, Bitmap, ba_set(Idx, CN, Nodes)};
false ->
{cnode, (Bitmap bor Bit), ba_ins(Idx, {snode, Key, Value}, Nodes)}
end;
put_1(_H, Key, Value, {snode, Key, _}, _L) ->
{snode, Key, Value};
put_1(H, Key, Value, {snode, SNKey, SNValue}, L) when is_integer(L), L =< 30 ->
put_1(H, Key, Value, split(SNKey, SNValue, L), L);
put_1(_H, Key, Value, {snode, _, _}, L) when L > 30 ->
{lnode, [{Key, Value}]};
put_1(_H, Key, Value, {lnode, List}, _L) when is_list(List) ->
{lnode, [{Key, Value} | List]}.
split(SNKey, SNValue, L) ->
{cnode, bitpos(hash(SNKey), L), [{snode, SNKey, SNValue}]}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec delete(Key, Hamt1) -> Hamt2 when
Key :: binary(),
Hamt1 :: hamt(),
Hamt2 :: hamt().
delete(Key, {hamt, nil})
when is_binary(Key) ->
{hamt, nil};
delete(Key, {hamt, Node}=T)
when is_binary(Key) ->
case delete_1(hash(Key), Key, Node, 0) of
not_found -> T;
delete -> {hamt, nil};
N -> {hamt, N}
end.
delete_1(H, Key, {cnode, Bitmap, Nodes}=CNode, L)
when is_integer(L), L =< 30 ->
Bit = bitpos(H, L),
Idx = index(Bit, Bitmap),
case exists(Bit, Bitmap) of
true ->
case delete_1(H, Key, ba_get(Idx, Nodes), L + 5) of
not_found -> not_found;
delete -> delete_2(Key, Bit, CNode);
N -> {cnode, Bitmap, ba_set(Idx, N, Nodes)}
end;
false ->
not_found
end;
delete_1(_H, Key, {snode, Key, _}, _L) ->
delete;
delete_1(_H, _Key, {snode, _, _}, _L) ->
not_found;
delete_1(_H, Key, {lnode, List}, _L) ->
case length(List) > 2 of
true ->
{lnode, lists:filter(fun({snode, K, _}) when K =:= Key -> true;
({snode, _, _}) -> false end,
List)};
false ->
{snode, Key, lists:keyfind(Key, 2, List)}
end.
%% @doc This CNode only has 2 elements in it and one is about to be
%% be deleted, time to collapse this CNode into an SNode.
delete_2(Key, _Bit, {cnode, _Bitmap, Nodes})
when length(Nodes) =:= 2 ->
[{snode, _, _}=SN] = ba_del(Key, Nodes),
SN;
%% @doc Remove the right key and update the bitmap
delete_2(Key, Bit, {cnode, Bitmap, Nodes}) ->
{cnode, (Bitmap bxor Bit), ba_del(Key, Nodes)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec map(Function, Hamt1) -> Hamt2 when
Function :: fun((K :: term(), V1 :: term()) -> V2 :: term()),
Hamt1 :: hamt(),
Hamt2 :: hamt().
map(F, {hamt, _}=T) when is_function(F, 2) ->
{map_1(F, T)}.
map_1(_, nil) -> nil;
map_1(F, {K, V, Smaller, Larger}) ->
{K, F(K, V), map_1(F, Smaller), map_1(F, Larger)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec fold(Function, Hamt, Acc) -> Hamt when
Function :: fun((K :: term(), V :: term()) -> V2 :: term()),
Hamt :: hamt(),
Acc :: any().
fold(Function, {hamt, Node}, Acc) ->
fold_1(Function, Acc, Node).
fold_1(F, Acc, {snode, Key, Value}) ->
F(Key, Value, Acc);
fold_1(_F, Acc, {cnode, _, []}) ->
Acc;
fold_1(F, Acc, {cnode, _, [Node]}) ->
fold_1(F, Acc, Node);
fold_1(F, Acc, {cnode, Bitmap, [Node | Nodes]}) ->
fold_1(F, fold_1(F, Acc, Node), {cnode, Bitmap, Nodes});
fold_1(F, Acc, {lnode, Nodes}) ->
lists:foldl(F, Acc, Nodes).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ba_get(I, Nodes)
when I =< 32, erlang:length(Nodes) =< 32 ->
lists:nth(I, Nodes).
ba_set(1, V, [_|T]=Nodes)
when erlang:length(Nodes) =< 32 ->
[V|T];
ba_set(I, V, [H|T]=Nodes)
when I =< 32, erlang:length(Nodes) =< 32 ->
[H|ba_set(I-1, V, T)].
ba_ins(1, V, [H|T]=Nodes)
when erlang:length(Nodes) =< 32 ->
[V|[H|T]];
ba_ins(I, V, [H|T]=Nodes)
when I =< 32, erlang:length(Nodes) =< 32 ->
[H|ba_ins(I-1, V, T)];
ba_ins(1, V, [H]) -> [H, V];
ba_ins(2, V, [H]) -> [V, H];
ba_ins(_I, V, []) -> [V].
ba_del(Key, Nodes) ->
lists:filter(fun({snode, K, _}) when K =:= Key -> false;
({snode, _, _}) -> true;
({cnode, _, _}) -> true;
({lnode, _}) -> true
end, Nodes).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
mask(Hash, Shift) ->
(Hash bsr Shift) band 2#11111.
bitpos(Hash, Shift) ->
1 bsl mask(Hash, Shift).
index(Bit, Bitmap) ->
bitpop:count(Bitmap band (Bit - 1)) + 1. % Arrays start with index 1, not 0
exists(Bit, Bitmap) ->
(Bitmap band Bit) =:= Bit.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
hash(Key) when is_binary(Key) ->
erlang:phash2(Key).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-ifdef(TEST).
create_a_hamt_test_() ->
[?_assertEqual({hamt, nil}, hamt:new()),
?_assertEqual(true, hamt:is_empty(hamt:new())),
?_assertEqual(false, hamt:is_empty(hamt:put(<<"k">>, <<"v">>, hamt:new()))),
?_assertEqual(<<"v">>, hamt:get(<<"k">>, hamt:put(<<"k">>, <<"v">>, hamt:new())))].
put_causes_split_root_snode_test() ->
?assertEqual(hamt:put(<<"k2">>, <<"v2">>, {hamt,{snode,<<"k1">>,<<"v1">>}}),
{hamt,{cnode,4456448,
[{snode,<<"k1">>,<<"v1">>},{snode,<<"k2">>,<<"v2">>}]}}).
put_causes_2_splits_test() ->
?assertEqual(hamt:put(<<5>>,<<5>>,{hamt,{cnode,17563904,
[{snode,<<3>>,<<3>>},
{snode,<<1>>,<<1>>},
{snode,<<2>>,<<2>>},
{snode,<<4>>,<<4>>}]}}),
{hamt,
{cnode,17563904,
[{snode,<<3>>,<<3>>},
{snode,<<1>>,<<1>>},
{cnode,131072,
[{cnode,142606336,
[{snode,<<2>>,<<2>>},
{snode,<<5>>,<<5>>}]}]},
{snode,<<4>>,<<4>>}]}}).
put_existing_key_replaces_value_test() ->
?assertEqual(hamt:put(<<"k1">>, <<"v'">>,
{hamt,{cnode,4456448,
[{snode,<<"k1">>,<<"v1">>},{snode,<<"k2">>,<<"v2">>}]}}),
{hamt,{cnode,4456448,
[{snode,<<"k1">>,<<"v'">>},{snode,<<"k2">>,<<"v2">>}]}}).
del_from_empty_trie_test() ->
?assertEqual(hamt:delete(<<"k1">>, {hamt, nil}), {hamt, nil}).
del_last_key_in_trie_test() ->
?assertEqual(hamt:delete(<<"k1">>, {hamt,{snode,<<"k1">>,<<"v1">>}}), {hamt, nil}).
del_one_of_many_keys_test() ->
?assertEqual(hamt:delete(<<"k1">>,
{hamt,{cnode,4456448,
[{snode,<<"k1">>,<<"v1">>},
{snode,<<"k2">>,<<"v2">>}]}}),
{hamt,{snode,<<"k2">>,<<"v2">>}}).
del_causes_cascading_cnode_collapse_test() ->
H = hamt:put([{<<X>>, <<X>>} || X <- lists:seq(1,5)], hamt:new()),
?assertEqual(hamt:delete(<<5>>, H),
hamt:put([{<<X>>, <<X>>} || X <- lists:seq(1,4)], hamt:new())).
put_lots_test() ->
KVPs = [{<<X>>, <<X>>} || X <- lists:seq(1,10000)],
hamt:put(KVPs, hamt:new()).
%% test() ->
%% test(500).
%% test(NumTimes) ->
%% eqc:quickcheck(eqc:numtests(NumTimes, prop_to_list())).
%% input_list() ->
%% list({list(int()), int()}).
%% prop_to_list() ->
%% ?FORALL(Xs, input_list(),
%% equiv_to_orddict(Xs)).
%% equiv_to_orddict(Xs) ->
%% orddict:from_list(hamt:to_list(Xs)) =:= orddict:from_list(Xs).
%% from_list(L) ->
%% lists:foldl(fun insert_fun/2, empty(), L).
%% insert_fun({Key, Value}, Acc) ->
%% put(Key, Value, Acc).
-endif.

16
src/hamt_app.erl Normal file
View file

@ -0,0 +1,16 @@
-module(hamt_app).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
%% ===================================================================
%% Application callbacks
%% ===================================================================
start(_StartType, _StartArgs) ->
hamt_sup:start_link().
stop(_State) ->
ok.

27
src/hamt_sup.erl Normal file
View file

@ -0,0 +1,27 @@
-module(hamt_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
%% Helper macro for declaring children of supervisor
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
%% ===================================================================
%% API functions
%% ===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%% ===================================================================
%% Supervisor callbacks
%% ===================================================================
init([]) ->
{ok, { {one_for_one, 5, 10}, []} }.