tally_counter 0.0.1 → 0.1.0

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/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