forked from boundary/flake
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
serve both raw 128-bit ids and base-62 encoded
- Loading branch information
Showing
19 changed files
with
477 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
ebin/ | ||
.eunit/ | ||
*.dump | ||
deps/ | ||
rel/flake/ | ||
.DS_Store | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{application, flake, | ||
[ | ||
{description, "flake"}, | ||
{vsn, git}, | ||
{modules, []}, | ||
{registered, []}, | ||
{applications, [ | ||
kernel, | ||
stdlib, | ||
crypto, | ||
syslog | ||
]}, | ||
{mod, { flake_app, []}}, | ||
{env, []} | ||
]}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
-module (flake). | ||
-author ('Dietrich Featherston <[email protected]>'). | ||
%%==================================================================== | ||
%% API | ||
%%==================================================================== | ||
-export([start/0, start_link/0, stop/0]). | ||
-export([get_config_value/2]). | ||
|
||
-include("flake.hrl"). | ||
|
||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
ensure_started(App) -> | ||
case application:start(App) of | ||
ok -> | ||
ok; | ||
{error, {already_started, App}} -> | ||
ok | ||
end. | ||
|
||
%% @spec start_link() -> {ok,Pid::pid()} | ||
%% @doc Starts the app for inclusion in a supervisor tree | ||
start_link() -> | ||
flake_sup:start_link(). | ||
|
||
%% @spec start() -> ok | ||
%% @doc Start the snowflake server. | ||
start() -> | ||
application:start(flake). | ||
|
||
%% @spec stop() -> ok | ||
%% @doc Stop the snowflake server. | ||
stop() -> | ||
Res = application:stop(flake), | ||
Res. | ||
|
||
get_config_value(Key, Default) -> | ||
case application:get_env(flake, Key) of | ||
{ok, Value} -> Value; | ||
_ -> Default | ||
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
-ifdef(TEST). | ||
-define(LOG_INFO(F), _ = [F]). | ||
-define(LOG_ERROR(F), _ = [F]). | ||
-define(LOG_INFO_FORMAT(F, A), _ = [F, A]). | ||
-define(LOG_ERROR_FORMAT(F, A), _ = [F, A]). | ||
-else. | ||
-define(LOG_INFO(F), | ||
error_logger:info_msg(F)). | ||
-define(LOG_ERROR(F), | ||
error_logger:error_msg(F)). | ||
-define(LOG_INFO_FORMAT(F, A), | ||
error_logger:info_msg(F, A)). | ||
-define(LOG_ERROR_FORMAT(F, A), | ||
error_logger:error_msg(F, A)). | ||
-endif. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
%% @doc Callbacks for the snowflake application. | ||
|
||
-module (flake_app). | ||
-author('Dietrich Featherston <[email protected]>'). | ||
|
||
-behaviour(application). | ||
-export([start/2,stop/1]). | ||
|
||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
%% @spec start(_Type, _StartArgs) -> ServerRet | ||
%% @doc application start callback for snowflake. | ||
start(_Type, _StartArgs) -> | ||
flake_sup:start_link(). | ||
|
||
%% @spec stop(_State) -> ServerRet | ||
%% @doc application stop callback for snowflake. | ||
stop(_State) -> | ||
ok. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
-module (flake_harness). | ||
-author ('Dietrich Featherston <[email protected]>'). | ||
-export ([timed_generate_ids/1,generate_ids/1]). | ||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
timed_generate_ids(N) -> | ||
?debugTime("generating ids",generate_ids(N)). | ||
|
||
generate_ids(0) -> | ||
ok; | ||
generate_ids(N) -> | ||
{ok,_Flake} = flake_server:id(62), | ||
generate_ids(N-1). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
-module (flake_server). | ||
-behaviour (gen_server). | ||
-export ([start_link/1]). | ||
-export ([id/0,id/1]). | ||
-export ([gen_id/3]). | ||
-export ([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]). | ||
-record (state, {max_time,worker_id,sequence}). | ||
|
||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
%% ---------------------------------------------------------- | ||
%% API | ||
%% ---------------------------------------------------------- | ||
|
||
% start and link to a new flake id generator | ||
start_link(Config) -> | ||
gen_server:start_link({local,flake},?MODULE,Config,[]). | ||
|
||
% generate a new snowflake id | ||
id() -> | ||
respond(gen_server:call(flake,get)). | ||
|
||
id(Base) -> | ||
respond(gen_server:call(flake,{get,Base})). | ||
|
||
respond({ok,Flake}) -> | ||
{ok,Flake}; | ||
respond(X) -> | ||
X. | ||
|
||
|
||
%% ---------------------------------------------------------- | ||
%% gen_server callbacks | ||
%% ---------------------------------------------------------- | ||
|
||
% Example config | ||
% [ {worker_id, int()} ] | ||
% | ||
|
||
init([{worker_id,WorkerId}]) -> | ||
{ok,#state{max_time=curr_time(),worker_id=WorkerId,sequence=0}}. | ||
|
||
handle_call(get, _From, State = #state{max_time=MaxTime,worker_id=WorkerId,sequence=Sequence}) -> | ||
{Resp,S0} = get(curr_time(),MaxTime,WorkerId,Sequence,State), | ||
{reply,Resp,S0}; | ||
|
||
handle_call({get,Base}, _From, State = #state{max_time=MaxTime,worker_id=WorkerId,sequence=Sequence}) -> | ||
{Resp,S0} = get(curr_time(),MaxTime,WorkerId,Sequence,State), | ||
case Resp of | ||
{ok,Id} -> | ||
{reply,{ok,flake_util:as_list(Id,Base)},S0}; | ||
E -> | ||
{reply,E,S0} | ||
end; | ||
|
||
handle_call(X, _From, State) -> | ||
error_logger:error_msg("unrecognized msg in ~p:handle_call -> ~p~n",[?MODULE,X]), | ||
{reply,ok,State}. | ||
|
||
handle_cast(_, State) -> {noreply, State}. | ||
|
||
handle_info(_, State) -> {noreply, State}. | ||
|
||
terminate(_Reason, _State) -> ok. | ||
|
||
code_change(_, State, _) -> {ok, State}. | ||
|
||
% clock hasn't moved, increment sequence | ||
get(Time,Time,WorkerId,Seq0,State) -> | ||
Sequence = Seq0 + 1, | ||
{{ok,gen_id(Time,WorkerId,Sequence)},State#state{sequence=Sequence}}; | ||
% clock has progressed, reset sequence | ||
get(CurrTime,MaxTime,WorkerId,_,State) when CurrTime > MaxTime -> | ||
{{ok,gen_id(CurrTime,WorkerId,0)},State#state{max_time=CurrTime,sequence=0}}; | ||
% clock is running backwards | ||
get(CurrTime,MaxTime,_WorkerId,_Sequence,State) when MaxTime > CurrTime -> | ||
{{error,clock_running_backwards},State}. | ||
|
||
|
||
%% ---------------------------------------------------------- | ||
%% helpers | ||
%% ---------------------------------------------------------- | ||
|
||
curr_time() -> | ||
{MegaSec,Sec,MicroSec} = erlang:now(), | ||
1000000000*MegaSec + Sec*1000 + erlang:trunc(MicroSec/1000). | ||
|
||
gen_id(Time,WorkerId,Sequence) -> | ||
( Time bsl 64 ) + ( WorkerId bsl 16 ) + Sequence. | ||
|
||
|
||
%% ---------------------------------------------------------- | ||
%% tests | ||
%% ---------------------------------------------------------- | ||
|
||
flake_test() -> | ||
TS = curr_time(), | ||
Worker = flake_util:hw_addr_to_int(lists:seq(1,6)), | ||
Flake = gen_id(TS,Worker,0), | ||
?debugVal(Flake), | ||
<<Time:64/unsigned-integer,WorkerId:48/unsigned-integer,Sequence:16/unsigned-integer>> = <<Flake:128/unsigned-integer>>, | ||
?assert(?debugVal(Time) =:= TS), | ||
?assert(?debugVal(Worker) =:= WorkerId), | ||
?assert(?debugVal(Sequence) =:= 0), | ||
?debugVal(flake_util:as_list(Flake,62)), | ||
test_gen_server(), | ||
ok. | ||
|
||
test_gen_server() -> | ||
Worker = flake_util:hw_addr_to_int(lists:seq(1,6)), | ||
{ok,FlakeServer} = flake_server:start_link([{worker_id,Worker}]), | ||
% register(flake,FlakeServer), | ||
?debugVal(flake_server:id()), | ||
?debugVal(flake_server:id(62)), | ||
ok. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
-module (flake_sup). | ||
-author('Dietrich Featherston <[email protected]>'). | ||
-include("flake.hrl"). | ||
|
||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
-define (DEBUG,debug). | ||
|
||
-behaviour(supervisor). | ||
|
||
%% External exports | ||
-export([start_link/0, upgrade/0]). | ||
|
||
%% supervisor callbacks | ||
-export([init/1]). | ||
|
||
%% @spec start_link() -> ServerRet | ||
%% @doc API for starting the supervisor. | ||
start_link() -> | ||
supervisor:start_link({local, ?MODULE}, ?MODULE, []). | ||
|
||
%% @spec upgrade() -> ok | ||
%% @doc Add processes if necessary. | ||
upgrade() -> | ||
{ok, {_, Specs}} = init([]), | ||
|
||
Old = sets:from_list([Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), | ||
New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), | ||
Kill = sets:subtract(Old, New), | ||
|
||
sets:fold( | ||
fun(Id, ok) -> | ||
supervisor:terminate_child(?MODULE, Id), | ||
supervisor:delete_child(?MODULE, Id), | ||
ok | ||
end, ok, Kill), | ||
|
||
[supervisor:start_child(?MODULE, Spec) || Spec <- Specs], | ||
ok. | ||
|
||
%% @spec init([]) -> SupervisorTree | ||
%% @doc supervisor callback. | ||
init([]) -> | ||
If = flake:get_config_value("interface","en0"), | ||
{ok,WorkerId} = flake_util:get_if_hw_int(If), | ||
Config = [ | ||
{worker_id,WorkerId} | ||
], | ||
Flake = {flake, | ||
{flake_server,start_link,[Config]}, | ||
permanent, 5000, worker, [flake_server]}, | ||
|
||
% SyslogOpts = flake:get_config_value(syslog, [{hostname, "syslog.cid1.boundary.com"}, {facility, local5}]), | ||
% Syslog = {syslog, {syslog, start_link, [flake, | ||
% flake, | ||
% proplists:get_value(hostname, SyslogOpts), | ||
% 514, | ||
% proplists:get_value(facility, SyslogOpts)]}, permanent, 2000, worker, [syslog]}, | ||
Processes = [Flake], | ||
{ok, { {one_for_one, 10, 10}, Processes} }. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
-module (flake_util). | ||
-export ([as_list/2]). | ||
-export ([get_if_hw_int/1,hw_addr_to_int/1]). | ||
|
||
% get the mac/hardware address of the given interface as a 48-bit integer | ||
get_if_hw_int(undefined) -> | ||
{error,if_not_found}; | ||
get_if_hw_int(IfName) -> | ||
{ok,IfAddrs} = inet:getifaddrs(), | ||
IfProps = proplists:get_value(IfName,IfAddrs), | ||
case IfProps of | ||
undefined -> | ||
{error,if_not_found}; | ||
_ -> | ||
HwAddr = proplists:get_value(hwaddr,IfProps), | ||
{ok,hw_addr_to_int(HwAddr)} | ||
end. | ||
|
||
% convert an array of 6 bytes into a 48-bit integer | ||
hw_addr_to_int(HwAddr) -> | ||
<<WorkerId:48/unsigned-integer>> = erlang:list_to_binary(HwAddr), | ||
WorkerId. | ||
|
||
%% | ||
% n.b. - unique_id_62/0 and friends pulled from riak | ||
%% | ||
|
||
%% @spec integer_to_list(Integer :: integer(), Base :: integer()) -> | ||
%% string() | ||
%% @doc Convert an integer to its string representation in the given | ||
%% base. Bases 2-62 are supported. | ||
as_list(I, 10) -> | ||
erlang:integer_to_list(I); | ||
as_list(I, Base) | ||
when is_integer(I), | ||
is_integer(Base), | ||
Base >= 2, | ||
Base =< 1+$Z-$A+10+1+$z-$a -> | ||
if | ||
I < 0 -> | ||
[$-|as_list(-I, Base, [])]; | ||
true -> | ||
as_list(I, Base, []) | ||
end; | ||
as_list(I, Base) -> | ||
erlang:error(badarg, [I, Base]). | ||
|
||
%% @spec integer_to_list(integer(), integer(), stringing()) -> string() | ||
as_list(I0, Base, R0) -> | ||
D = I0 rem Base, | ||
I1 = I0 div Base, | ||
R1 = | ||
if | ||
D >= 36 -> | ||
[D-36+$a|R0]; | ||
D >= 10 -> | ||
[D-10+$A|R0]; | ||
true -> | ||
[D+$0|R0] | ||
end, | ||
if | ||
I1 =:= 0 -> | ||
R1; | ||
true -> | ||
as_list(I1, Base, R1) | ||
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,11 @@ | ||
{require_otp_vsn, "R14"}. | ||
{deps, []}. | ||
%%-*- mode: erlang -*- | ||
{sub_dirs, ["apps/flake", "rel", "deps"]}. | ||
|
||
{deps, | ||
[ | ||
{syslog, ".*", {git, "git://github.com/JacobVorreuter/erlang_syslog.git", "master"}} | ||
] | ||
}. | ||
|
||
{erl_opts, [debug_info]}. | ||
{cover_enabled, false}. |
Oops, something went wrong.