tally_counter 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -59,14 +59,27 @@ to cause a count skip.
59
59
  ## Keys and Scoring
60
60
 
61
61
  The system uses Redis sorted sets for tracking application hits.
62
- For each request, a key will be counted, and the score for the
62
+ For each request, a key will be generated, and the score for the
63
63
  remote request ip will be incremented by 1.
64
64
 
65
65
  Keys are generated like 'tally_counter:1371283200' where the time
66
66
  is the epoch seconds floor for the current window. The floor for
67
- a 5 minute interval at 12:38 would be 12:35, at 12:33 its 12:30,
67
+ a 5 minute interval at 12:38 would be 12:35, at 12:33 it's 12:30,
68
68
  and so on.
69
69
 
70
+ Keys are generated using the `TallyCounter::KeyGenerate` class.
71
+ This can be used in client applications for generating keys for
72
+ Redis lookups.
73
+
74
+ ```ruby
75
+ # Create a key generator for 5 minute windows with :foo namespace
76
+ key_generate = TallyCounter::KeyGenerate.new(300, :foo)
77
+ # Generate a key for the current time
78
+ key_generate.for(Time.now)
79
+ # Generate a key from 10 minutes ago
80
+ key_generate.for(Time.now, 2)
81
+ ```
82
+
70
83
  It is recommended to use a scheduled process to inspect tally_counter
71
84
  sets past a certain age (say, 3 days) and prune them to keep your
72
85
  data set small and clean.
@@ -75,21 +88,16 @@ Finding totals for a range of time can be accomplished via a Redis
75
88
  zunionstore on a range of keys. For example, if you have a a 5
76
89
  minute interval and want to see the last 15 minutes, simple grab
77
90
  the current window and the 2 previous and union them with equally
78
- weighted scoring. See `TallyCounter::Window#floow` for generating
91
+ weighted scoring. See `TallyCounter::Window#floor` for generating
79
92
  window times and offsets.
80
93
 
81
94
  ## Reporting
82
95
 
83
96
  In the interest of giving this gem a single responsibility, reporting
84
97
  can be offloaded to other systems. It should be easy to deploy
85
- a separate admin application conneted to the same server, and use
98
+ a separate admin application connected to the same server, and use
86
99
  the `TallyCounter::Window` class for generating keys.
87
100
 
88
- ## Todo
89
-
90
- Extract key generation to a utility for use by middleware and client
91
- apps.
92
-
93
101
  ## Contributing
94
102
 
95
103
  1. Fork it
@@ -1,3 +1,3 @@
1
1
  module TallyCounter
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/tally_counter.rb CHANGED
@@ -14,7 +14,7 @@ module TallyCounter
14
14
  end
15
15
 
16
16
  # Returns the floor time for a given window. For example,
17
- # if the interval is 5 minutes and it is 12:38, the floow
17
+ # if the interval is 5 minutes and it is 12:38, the floor
18
18
  # would be 12:35. If offset is 1, the floor would be 12:30
19
19
  #
20
20
  # @param [Time] a time instance, commonly Time.now
@@ -26,6 +26,22 @@ module TallyCounter
26
26
 
27
27
  end
28
28
 
29
+ class KeyGenerate
30
+ # @param [Integer] a seconds interval for Window use
31
+ # @param [String] optional app namespace
32
+ def initialize(interval, namespace = nil)
33
+ @window = TallyCounter::Window.new(interval)
34
+ @namespace = namespace
35
+ end
36
+
37
+ # @param [Time] time for a key, often Time.now
38
+ # @param [Integer] offset for window to step back
39
+ def for(time, offset = 0)
40
+ timestamp = @window.floor(time, offset).to_i
41
+ [@namespace, "tally_counter", timestamp].compact.join(':')
42
+ end
43
+ end
44
+
29
45
  class Middleware
30
46
 
31
47
  # @param app - a rack compliant app
@@ -68,11 +84,11 @@ module TallyCounter
68
84
  end
69
85
 
70
86
  def current_key
71
- [@namespace, "tally_counter", window_floor].compact.join(':')
87
+ key_generate.for(Time.now)
72
88
  end
73
89
 
74
- def window_floor
75
- TallyCounter::Window.new(@interval).floor(Time.now).to_i
90
+ def key_generate
91
+ @key_generate ||= TallyCounter::KeyGenerate.new(@interval, @namespace)
76
92
  end
77
93
 
78
94
  end
@@ -2,17 +2,15 @@ require "test/unit"
2
2
  require "rack/test"
3
3
  require File.expand_path('../../lib/tally_counter', __FILE__)
4
4
 
5
- module TestHelper
6
- def test(name, &block)
5
+ class TallyCounterTestCase < Test::Unit::TestCase
6
+ def self.test(name, &block)
7
7
  name = name.gsub(/\W+/, ' ').strip
8
8
  test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
9
9
  define_method(test_name, &block)
10
10
  end
11
11
  end
12
12
 
13
- class TallyCounterWindowTest < Test::Unit::TestCase
14
- extend TestHelper
15
-
13
+ class TallyCounterWindowTest < TallyCounterTestCase
16
14
  test "initializing with interval seconds" do
17
15
  window = TallyCounter::Window.new(60)
18
16
  assert_equal 60, window.interval
@@ -38,8 +36,30 @@ class TallyCounterWindowTest < Test::Unit::TestCase
38
36
  end
39
37
  end
40
38
 
41
- class TallyCounterMiddlewareTest < Test::Unit::TestCase
42
- extend TestHelper
39
+ class TallyCounterKeyGenerateTest < TallyCounterTestCase
40
+ def time
41
+ Time.parse('2013-06-15 08:52:41 -0700')
42
+ end
43
+
44
+ test "generating a key without a namespace" do
45
+ key_generate = TallyCounter::KeyGenerate.new(300)
46
+ assert_equal 'tally_counter:1371311400', key_generate.for(time)
47
+ end
48
+
49
+ test "generating a key with a namespace" do
50
+ key_generate = TallyCounter::KeyGenerate.new(300, :some_app)
51
+ assert_equal 'some_app:tally_counter:1371311400', key_generate.for(time)
52
+ end
53
+
54
+ test "generating a key for a given window offset" do
55
+ key_generate = TallyCounter::KeyGenerate.new(300, :some_app)
56
+ assert_equal 'some_app:tally_counter:1371311400', key_generate.for(time, 0)
57
+ assert_equal 'some_app:tally_counter:1371311100', key_generate.for(time, 1)
58
+ assert_equal 'some_app:tally_counter:1371310800', key_generate.for(time, 2)
59
+ end
60
+ end
61
+
62
+ class TallyCounterMiddlewareTest < TallyCounterTestCase
43
63
  include Rack::Test::Methods
44
64
 
45
65
  def redis
@@ -104,10 +124,9 @@ class TallyCounterMiddlewareTest < Test::Unit::TestCase
104
124
  end
105
125
 
106
126
  test "tolerating a timeout error" do
107
- fake_redis = Object.new.tap do |r|
108
- def r.zincrby(*)
109
- sleep 5
110
- end
127
+ fake_redis = Object.new
128
+ def fake_redis.zincrby(*)
129
+ sleep 5
111
130
  end
112
131
  self.app_options = {:redis => fake_redis}
113
132
 
@@ -116,15 +135,13 @@ class TallyCounterMiddlewareTest < Test::Unit::TestCase
116
135
  end
117
136
 
118
137
  test "tolerating a redis error" do
119
- fake_redis = Object.new.tap do |r|
120
- def r.zincrby(*)
121
- raise Redis::ConnectionError
122
- end
138
+ fake_redis = Object.new
139
+ def fake_redis.zincrby(*)
140
+ raise Redis::ConnectionError
123
141
  end
124
142
  self.app_options = {:redis => fake_redis}
125
143
 
126
144
  assert_nothing_raised { get "/" }
127
145
  assert_match "ok", last_response.body
128
146
  end
129
-
130
147
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tally_counter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-06-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &2152445000 !ruby/object:Gem::Requirement
16
+ requirement: &2160507200 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2152445000
24
+ version_requirements: *2160507200
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack-test
27
- requirement: &2152444560 !ruby/object:Gem::Requirement
27
+ requirement: &2160506760 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2152444560
35
+ version_requirements: *2160506760
36
36
  description: Tally web application hits with Rack & Redis sorted sets
37
37
  email:
38
38
  - xternal1+github@gmail.com