Current section
Files
Jump to
Current section
Files
src/cclock.erl
%%
%% Copyright 2013 Joaquim Rocha
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
-module(cclock).
-behaviour(gen_server).
-define(SERVER, {local, ?MODULE}).
-define(DEFAULT_FIX, 60000).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% ====================================================================
%% API functions
%% ====================================================================
-export([start_link/0]).
-export([cluster_timestamp/0, local_timestamp/0]).
-export([time_fix/0]).
start_link() ->
gen_server:start_link(?SERVER, ?MODULE, [], []).
-spec cluster_timestamp() -> integer().
cluster_timestamp() ->
gen_server:call(?MODULE, {timestamp}).
-spec local_timestamp() -> integer().
local_timestamp() ->
TS = {_,_, Micro} = os:timestamp(),
Utc = calendar:now_to_universal_time(TS),
Seconds = calendar:datetime_to_gregorian_seconds(Utc),
((Seconds - 62167219200) * 1000000) + Micro.
-spec time_fix() -> integer().
time_fix() ->
gen_server:call(?MODULE, {time_fix}).
%% ====================================================================
%% Behavioural functions
%% ====================================================================
-record(state, {fix, fix_timer}).
%% init
init([]) ->
process_flag(trap_exit, true),
error_logger:info_msg("~p [~p] is starting...\n", [?MODULE, self()]),
FixInterval = application:get_env(cclock, fix_interval, ?DEFAULT_FIX),
FixTimer = schedule(none, {send_fix}, FixInterval),
initialize(),
{ok, #state{fix=0, fix_timer=FixTimer}}.
%% handle_call
handle_call({timestamp}, _From, State=#state{fix=Fix}) ->
Timestamp = timestamp(Fix),
{reply, Timestamp, State};
handle_call({time_fix}, _From, State=#state{fix=Fix}) ->
{reply, Fix, State}.
%% handle_cast
handle_cast(_Msg, State) ->
{noreply, State}.
%% handle_info
handle_info({fix, RemoteTS}, State=#state{fix=Fix}) ->
{_, NFix} = fix(RemoteTS, Fix),
{noreply, State#state{fix=NFix}};
handle_info({fix, RemoteTS, From}, State=#state{fix=Fix}) ->
{LocalTS, NFix} = fix(RemoteTS, Fix),
Timestamp = timestamp(LocalTS, NFix),
From ! fix_msg(Timestamp),
{noreply, State#state{fix=NFix}};
handle_info({send_fix}, State=#state{fix=Fix}) ->
Timestamp = timestamp(Fix),
columbo:send_to_all(?MODULE, fix_msg(Timestamp)),
{noreply, State}.
%% terminate
terminate(_Reason, #state{fix_timer=FixTimer}) ->
timer:cancel(FixTimer),
ok.
%% code_change
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ====================================================================
%% Internal functions
%% ====================================================================
initialize() ->
Timestamp = local_timestamp(),
columbo:send_to_all(?MODULE, {fix, Timestamp, self()}).
fix(RemoteTS, Fix) ->
LocalTS = local_timestamp(),
Dif = RemoteTS - LocalTS,
if Dif > Fix -> {LocalTS, Dif};
true -> {LocalTS, Fix}
end.
timestamp(Fix) ->
timestamp(local_timestamp(), Fix).
timestamp(Timestamp, Fix) ->
Timestamp + Fix.
fix_msg(Timestamp) ->
{fix, Timestamp}.
schedule(none, Msg, Interval) ->
{ok, NewTimer} = timer:send_interval(Interval, Msg),
NewTimer;
schedule(Timer, Msg, Interval) ->
timer:cancel(Timer),
schedule(none, Msg, Interval).