snappy_stats 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +0 -1
- data/Gemfile +3 -0
- data/lib/snappy_stats/config.rb +5 -6
- data/lib/snappy_stats/version.rb +2 -2
- data/lib/snappy_stats.rb +81 -77
- data/spec/snappy_stats_spec.rb +35 -16
- data/spec/spec_helper.rb +1 -1
- metadata +2 -2
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/lib/snappy_stats/config.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
extend self
|
1
|
+
class SnappyStats
|
2
|
+
class Config
|
4
3
|
|
5
4
|
attr_accessor :namespace
|
6
5
|
attr_accessor :raise_connection_errors
|
7
6
|
|
8
|
-
def
|
7
|
+
def initialize(options = {})
|
9
8
|
# all keys are prefixed with this namespace
|
10
|
-
|
9
|
+
@namespace = 'stats'
|
11
10
|
# rescue Redis connection errors
|
12
|
-
|
11
|
+
@raise_connection_errors = false
|
13
12
|
end
|
14
13
|
|
15
14
|
# Set the Redis connection to use
|
data/lib/snappy_stats/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.0.
|
1
|
+
class SnappyStats
|
2
|
+
VERSION = "0.0.4"
|
3
3
|
end
|
data/lib/snappy_stats.rb
CHANGED
@@ -4,7 +4,7 @@ require 'redis'
|
|
4
4
|
require 'active_support/time'
|
5
5
|
require 'snappy_stats/config'
|
6
6
|
|
7
|
-
|
7
|
+
class SnappyStats
|
8
8
|
|
9
9
|
GRANULARITIES = {
|
10
10
|
# Available for 24 hours
|
@@ -12,90 +12,94 @@ module SnappyStats
|
|
12
12
|
size: 1440,
|
13
13
|
ttl: 172800,
|
14
14
|
factor: 60
|
15
|
-
|
16
|
-
|
15
|
+
},
|
16
|
+
hour: {
|
17
17
|
# Available for 7 days
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
size: 168,
|
19
|
+
ttl: 1209600,
|
20
|
+
factor: 3600
|
21
|
+
},
|
22
22
|
day: {
|
23
23
|
# Available for 24 months
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def self.hash_key
|
31
|
-
"#{SnappyStats.config.namespace}"
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.configure
|
35
|
-
yield(config)
|
36
|
-
end
|
24
|
+
size: 365,
|
25
|
+
ttl: 63113880,
|
26
|
+
factor: 86400
|
27
|
+
}
|
28
|
+
}
|
37
29
|
|
38
|
-
|
39
|
-
Config
|
40
|
-
end
|
30
|
+
attr_accessor :config
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
def self.getSecondsTimestamp
|
47
|
-
Time.now.getutc.to_i
|
48
|
-
end
|
32
|
+
def initialize(options = {})
|
33
|
+
@config = SnappyStats::Config.new
|
34
|
+
@config.redis = options[:redis]
|
35
|
+
end
|
49
36
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
37
|
+
def hash_key
|
38
|
+
"#{config.namespace}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def configure
|
42
|
+
yield(config)
|
43
|
+
end
|
44
|
+
|
45
|
+
def connection
|
46
|
+
@connection ||= config.redis
|
47
|
+
end
|
48
|
+
|
49
|
+
def getSecondsTimestamp
|
50
|
+
Time.now.getutc.to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
def getRoundedTimestamp( ts , precision )
|
54
|
+
ts = ts || self.getSecondsTimestamp
|
55
|
+
precision = precision || 1
|
56
|
+
( ts / precision ).floor * precision
|
57
|
+
end
|
58
|
+
|
59
|
+
def getFactoredTimestamp( ts_seconds, factor )
|
60
|
+
ts_seconds = ts_seconds || self.getSecondsTimestamp
|
61
|
+
( ts_seconds / factor ).floor * factor
|
62
|
+
end
|
63
|
+
|
64
|
+
def recordHitNow(key)
|
65
|
+
recordHit(Time.now.utc.to_i, key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def recordHit( time, key )
|
69
|
+
GRANULARITIES.keys.each do | gran |
|
70
|
+
granularity = GRANULARITIES[gran]
|
71
|
+
size = granularity[:size]
|
72
|
+
factor = granularity[:factor]
|
73
|
+
ttl = granularity[:ttl]
|
74
|
+
tsround = getRoundedTimestamp(time, size * factor)
|
75
|
+
redis_key = "#{hash_key}:#{key}:#{gran}:#{tsround}"
|
76
|
+
ts = getFactoredTimestamp time, factor
|
77
|
+
connection.pipelined{
|
78
|
+
connection.hincrby redis_key, ts, 1
|
79
|
+
connection.expireat redis_key, tsround + ttl
|
80
|
+
}
|
63
81
|
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get(gran, from, to, key)
|
85
|
+
granularity = GRANULARITIES[gran]
|
86
|
+
size = granularity[:size]
|
87
|
+
factor = granularity[:factor]
|
88
|
+
|
89
|
+
from = getFactoredTimestamp( from, factor )
|
90
|
+
to = getFactoredTimestamp( to, factor )
|
64
91
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
SnappyStats.connection.hincrby redis_key, ts, 1
|
75
|
-
SnappyStats.connection.expireat redis_key, tsround + ttl
|
76
|
-
end
|
92
|
+
ts = from
|
93
|
+
i = 0
|
94
|
+
results = {}
|
95
|
+
while ts <= to
|
96
|
+
tsround = getRoundedTimestamp( ts, size * factor )
|
97
|
+
redis_key = "#{hash_key}:#{key}:#{gran}:#{tsround}"
|
98
|
+
results[ts] = connection.hget( redis_key, ts )
|
99
|
+
i = i+1
|
100
|
+
ts = ts + GRANULARITIES[gran][:factor]
|
77
101
|
end
|
102
|
+
results
|
103
|
+
end
|
78
104
|
|
79
|
-
def self.get(gran, from, to, key)
|
80
|
-
granularity = GRANULARITIES[gran]
|
81
|
-
size = granularity[:size]
|
82
|
-
factor = granularity[:factor]
|
83
|
-
|
84
|
-
from = self.getFactoredTimestamp( from, factor )
|
85
|
-
to = self.getFactoredTimestamp( to, factor )
|
86
|
-
|
87
|
-
ts = from
|
88
|
-
i = 0
|
89
|
-
results = {}
|
90
|
-
while ts <= to
|
91
|
-
tsround = getRoundedTimestamp( ts, size * factor )
|
92
|
-
redis_key = "#{hash_key}:#{key}:#{gran}:#{tsround}"
|
93
|
-
results[ts] = SnappyStats.connection.hget( redis_key, ts )
|
94
|
-
i = i+1
|
95
|
-
ts = ts + GRANULARITIES[gran][:factor]
|
96
|
-
end
|
97
|
-
results
|
98
|
-
end
|
99
|
-
|
100
|
-
config.init!
|
101
105
|
end
|
data/spec/snappy_stats_spec.rb
CHANGED
@@ -1,50 +1,69 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
require_relative '../lib/snappy_stats'
|
3
4
|
describe SnappyStats do
|
4
5
|
|
5
|
-
before(:
|
6
|
+
before(:each) do
|
7
|
+
redis = Redis.new
|
8
|
+
$snappy_stats = SnappyStats.new(:redis => redis)
|
6
9
|
#Redis::Connection.drivers.delete_if {|x| Redis::Connection::Memory == x }
|
7
|
-
keys =
|
8
|
-
keys.each { | key |
|
10
|
+
keys = $snappy_stats.connection.keys("stats:*")
|
11
|
+
keys.each { | key | $snappy_stats.connection.del(key) }
|
9
12
|
end
|
10
13
|
|
11
14
|
it "Calculates ts in seconds" do
|
12
15
|
Timecop.freeze(Time.local(2013, 01, 01, 01, 01))
|
13
|
-
time =
|
16
|
+
time = $snappy_stats.getSecondsTimestamp
|
14
17
|
expect(time).to be 1357002060
|
15
|
-
default_rounded =
|
18
|
+
default_rounded = $snappy_stats.getRoundedTimestamp(nil, 60)
|
16
19
|
expect(default_rounded).to be 1357002060
|
17
20
|
time = 1364833411
|
18
|
-
rounded =
|
21
|
+
rounded = $snappy_stats.getRoundedTimestamp(time, 60)
|
19
22
|
expect(rounded).to be 1364833380
|
20
23
|
end
|
21
24
|
|
22
|
-
it "Records a hit at all granularities" do
|
23
|
-
pending("Need updated version of fakeredis with correct hincrby implementation")
|
25
|
+
it "Records a hit at all granularities" do
|
24
26
|
Timecop.freeze
|
25
27
|
now = Time.now
|
28
|
+
starting_hour = Time.now.beginning_of_hour.to_i
|
26
29
|
5.times do
|
27
|
-
|
30
|
+
$snappy_stats.recordHitNow("test:counter1")
|
28
31
|
end
|
29
32
|
3.times do
|
30
|
-
|
33
|
+
$snappy_stats.recordHitNow("test:counter2")
|
31
34
|
end
|
32
35
|
Timecop.freeze(now + 2.days)
|
33
36
|
2.times do
|
34
|
-
|
37
|
+
$snappy_stats.recordHitNow("test:counter1")
|
35
38
|
end
|
36
39
|
from = now.midnight.to_i
|
37
40
|
to = (now.midnight + 2.day).to_i
|
38
|
-
daily_user1_stat =
|
41
|
+
daily_user1_stat = $snappy_stats.get(:day,from,to,"test:counter1")
|
39
42
|
Timecop.freeze(now + 1.day)
|
40
|
-
puts daily_user1_stat
|
41
43
|
expect(daily_user1_stat[from]).to eq("5")
|
42
|
-
hourly_user1_stat =
|
43
|
-
expect(hourly_user1_stat[
|
44
|
-
daily_user2_stat =
|
44
|
+
hourly_user1_stat = $snappy_stats.get(:hour,from,to,"test:counter1")
|
45
|
+
expect(hourly_user1_stat[ starting_hour.to_i]).to eq("5")
|
46
|
+
daily_user2_stat = $snappy_stats.get(:day,from,to,"test:counter2")
|
45
47
|
expect(daily_user2_stat[from]).to eq("3")
|
46
48
|
end
|
47
49
|
|
50
|
+
it "can have two versions at same time" do
|
51
|
+
redis1 = Redis.new(db: 1)
|
52
|
+
$snappy_stats_eu = SnappyStats.new(:redis => redis1)
|
53
|
+
from = Time.now.midnight.to_i
|
54
|
+
to = (Time.now.midnight + 2.day).to_i
|
55
|
+
500.times do
|
56
|
+
$snappy_stats.recordHitNow("test:counter1")
|
57
|
+
end
|
58
|
+
250.times do
|
59
|
+
$snappy_stats_eu.recordHitNow("test:counter2")
|
60
|
+
end
|
61
|
+
user1_stats = $snappy_stats.get(:day,from,to,"test:counter1")
|
62
|
+
expect(user1_stats[from]).to eq("500")
|
63
|
+
user2_stats = $snappy_stats_eu.get(:day,from,to,"test:counter2")
|
64
|
+
expect(user2_stats[from]).to eq("250")
|
65
|
+
end
|
66
|
+
|
48
67
|
after(:each) do
|
49
68
|
Timecop.return
|
50
69
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snappy_stats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|