-
Notifications
You must be signed in to change notification settings - Fork 392
/
spiraltime.erl
128 lines (110 loc) · 4.37 KB
/
spiraltime.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
%% -------------------------------------------------------------------
%%
%% riak_core: Core Riak Application
%%
%% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you 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.
%%
%% -------------------------------------------------------------------
%% @doc A set of sliding windows for recording N-per-second running stats.
%%
%% This keeps stats per second for the last minute.
%%
%% See git commit history for versions of this module which keep stats
%% for more than 1 minute.
-module(spiraltime).
-author('Justin Sheehy <justin@basho.com>').
-export([fresh/0,fresh/1,n/0,incr/2,incr/3,
rep_second/1,rep_minute/1,
test_spiraltime/0]).
%% @type moment() = integer().
%% This is a number of seconds, as produced by
%% calendar:datetime_to_gregorian_seconds(calendar:universal_time())
%% @type count() = integer().
%% The number of entries recorded in some time period.
-record(spiral, {moment :: integer(),
seconds :: [integer()]
}).
n() ->
calendar:datetime_to_gregorian_seconds(calendar:universal_time()).
%% @doc Create an empty spiral with which to begin recording entries.
%% @spec fresh() -> spiral()
fresh() ->
fresh(n()).
%% @doc Create an empty spiral with which to begin recording entries.
%% @spec fresh(moment()) -> spiral()
fresh(Moment) ->
#spiral{moment=Moment,
seconds=[0 || _ <- lists:seq(1,60)]
}.
fieldlen(#spiral.seconds) -> 60.
nextfield(#spiral.seconds) -> done.
%% @doc Produce the number of entries recorded in the last second.
%% @spec rep_second(spiral()) -> {moment(), count()}
rep_second(Spiral) ->
{Spiral#spiral.moment, hd(Spiral#spiral.seconds)}.
%% @doc Produce the number of entries recorded in the last minute.
%% @spec rep_minute(spiral()) -> {moment(), count()}
rep_minute(Spiral) ->
{Minute,_} = lists:split(60,Spiral#spiral.seconds),
{Spiral#spiral.moment, lists:sum(Minute)}.
%% @doc Add N to the counter of events, as recently as possible.
%% @spec incr(count(), spiral()) -> spiral()
incr(N, Spiral) -> incr(N,n(),Spiral).
%% @doc Add N to the counter of events occurring at Moment.
%% @spec incr(count(), moment(), spiral()) -> spiral()
incr(N, Moment, Spiral) when Spiral#spiral.moment =:= Moment ->
% common case -- updates for "now"
Spiral#spiral{seconds=[hd(Spiral#spiral.seconds)+N|
tl(Spiral#spiral.seconds)]};
incr(_N, Moment, Spiral) when Spiral#spiral.moment - Moment > 60 ->
Spiral; % updates more than a minute old are dropped! whee!
incr(N, Moment, Spiral) ->
S1 = update_moment(Moment, Spiral),
{Front,Back} = lists:split(S1#spiral.moment - Moment,
S1#spiral.seconds),
S1#spiral{seconds=Front ++ [hd(Back)+N|tl(Back)]}.
update_moment(Moment, Spiral) when Moment =< Spiral#spiral.moment ->
Spiral;
update_moment(Moment, Spiral) when Moment - Spiral#spiral.moment > 36288000 ->
fresh(Moment);
update_moment(Moment, Spiral) ->
update_moment(Moment, push(0, Spiral#spiral{
moment=Spiral#spiral.moment+1},
#spiral.seconds)).
getfield(Spiral,Field) -> element(Field, Spiral).
setfield(Spiral,X,Field) -> setelement(Field, Spiral, X).
push(_N, Spiral, done) ->
Spiral;
push(N, Spiral, Field) ->
Full = [N|getfield(Spiral,Field)],
Double = 2 * fieldlen(Field),
case length(Full) of
Double ->
{Keep, _Past} = lists:split(fieldlen(Field), Full),
push(lists:sum(Keep),setfield(Spiral,Keep,Field),nextfield(Field));
_ ->
setfield(Spiral,Full,Field)
end.
test_spiraltime() ->
Start = n(),
S0 = fresh(Start),
S1 = incr(17, Start, S0),
PlusOne = Start+1,
S2 = incr(3, PlusOne, S1),
{PlusOne, 3} = rep_second(S2),
{PlusOne, 20} = rep_minute(S2),
true.