counters 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,7 @@ h2. Sample Usage
7
7
  Let's say you have a crawler. You'd like to record the number of URLs you visit, the number of URLs you skipped due to 304 Not Modified responses, and the number of bytes you consumed, and the amount of time each page takes to process. Here's how you'd do that:
8
8
 
9
9
  <pre><code>require "counters"
10
+ require "redis"
10
11
  require "rest_client"
11
12
  Counter = Counters::Redis.new(Redis.new, "counters")
12
13
 
@@ -17,6 +17,11 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
+ # Eventually, we might have something like this
21
+ # s.add_optional_dependency "redis"
22
+ s.add_development_dependency "redis"
23
+
20
24
  s.add_development_dependency "rspec"
21
25
  s.add_development_dependency "timecop"
26
+ s.add_development_dependency "ruby-debug19"
22
27
  end
@@ -1,9 +1,11 @@
1
1
  require "benchmark"
2
- require "counters"
2
+
3
+ # The redis gem must already be required - we don't require it.
4
+ # This allows callers / users to use any implementation that has the right API.
3
5
 
4
6
  module Counters
5
7
  class Redis < Counters::Base
6
- def initialize(redis, base_key)
8
+ def initialize(redis=Redis.new, base_key="counters")
7
9
  @redis, @base_key = redis, base_key
8
10
  end
9
11
 
@@ -12,7 +14,10 @@ module Counters
12
14
  end
13
15
 
14
16
  def record_magnitude(key, amount)
15
- @redis.hset(@base_key, "magnitudes.#{key}", amount)
17
+ @redis.multi do
18
+ @redis.hincrby(@base_key, "magnitudes.#{key}.value", amount)
19
+ @redis.hincrby(@base_key, "magnitudes.#{key}.count", 1)
20
+ end
16
21
  end
17
22
 
18
23
  def ping(key)
@@ -1,3 +1,3 @@
1
1
  module Counters
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
@@ -1,57 +1,77 @@
1
1
  require "spec_helper"
2
+ require "redis"
2
3
 
3
- describe Counters::Redis do
4
+ describe Counters::Redis, "integration tests" do
4
5
  let :redis do
5
- double("redis")
6
+ ::Redis.new
6
7
  end
7
8
 
8
9
  let :counter do
9
10
  Counters::Redis.new(redis, "counters")
10
11
  end
11
12
 
13
+ before(:each) do
14
+ redis.flushdb
15
+ end
16
+
12
17
  it_should_behave_like "all counters"
13
18
 
14
- it "should record a hit on 'pages.read' by HINCRBY counters/hits.pages.read" do
15
- redis.should_receive(:hincrby).with("counters", "hits.pages.read", 1).twice
19
+ it "should record 2 hits on 'pages.read'" do
16
20
  2.times { counter.hit "pages.read" }
21
+ redis.hkeys("counters").should == ["hits.pages.read"]
22
+ redis.hget("counters", "hits.pages.read").to_i.should == 2
17
23
  end
18
24
 
19
- it "should record a magnitude on 'bytes.in' by HSET counters/magnitudes.bytes.in" do
20
- redis.should_receive(:hset).with("counters", "magnitudes.bytes.in", 309).once
21
- redis.should_receive(:hset).with("counters", "magnitudes.bytes.in", 392).once
22
- counter.magnitude "bytes.in", 309
23
- counter.magnitude "bytes.in", 392
25
+ it "should record magnitude using a signed 64 bit 'value' and a 'count'" do
26
+ 4.times { counter.magnitude "bytes.in", 2_047 }
27
+ redis.hkeys("counters").sort.should == %w(magnitudes.bytes.in.value magnitudes.bytes.in.count).sort
28
+
29
+ redis.hget("counters", "magnitudes.bytes.in.count").to_i.should == 4
30
+ redis.hget("counters", "magnitudes.bytes.in.value").to_i.should == 4*2_047
31
+ end
32
+
33
+ it "does not detect 63 bit integer overflow" do
34
+ counter.magnitude "bytes.in", 2**63 - 20
35
+ counter.magnitude "bytes.in", 2_047
36
+
37
+ target_value = (2**63 - 20) + 2_047
38
+
39
+ redis.hget("counters", "magnitudes.bytes.in.value").to_i.should == (target_value - 2**64)
40
+ redis.hget("counters", "magnitudes.bytes.in.count").to_i.should == 2
24
41
  end
25
42
 
26
43
  it "should record a ping on 'crawler' by HSET counters/pings.crawler with today's date/time as an int" do
27
- Timecop.freeze do
28
- redis.should_receive(:hset).with("counters", "pings.crawler", Time.now.utc.to_i).once
44
+ now = Time.now
45
+ Timecop.freeze(now) do
29
46
  counter.ping "crawler"
30
47
  end
31
48
 
49
+ redis.hget("counters", "pings.crawler").should == now.to_i.to_s
50
+
32
51
  target_time = Time.now + 9
33
52
  Timecop.travel(target_time) do
34
- redis.should_receive(:hset).with("counters", "pings.crawler", target_time.to_i).once
35
53
  counter.ping "crawler"
36
54
  end
55
+
56
+ redis.hget("counters", "pings.crawler").should == target_time.to_i.to_s
37
57
  end
38
58
 
39
59
  it "should record latency on 'crawler.download' by HINCRBY counters/latencies.crawler.download.count by 1 and counters/latencies.crawler.download.nanoseconds by the latency" do
40
- redis.should_receive(:hincrby).with("counters", "latencies.crawler.download.count", 1).twice
41
- redis.should_receive(:hincrby).with("counters", "latencies.crawler.download.nanoseconds", 1.90 * 1_000_000_000).once
42
- redis.should_receive(:hincrby).with("counters", "latencies.crawler.download.nanoseconds", 2.02 * 1_000_000_000).once
43
-
44
60
  counter.latency "crawler.download", 1.9
45
61
  counter.latency "crawler.download", 2.02
62
+
63
+ redis.hget("counters", "latencies.crawler.download.count").to_i.should == 2
64
+ redis.hget("counters", "latencies.crawler.download.nanoseconds").to_i.should == (2.02 + 1.90) * ONE_NANOSECOND
46
65
  end
47
66
 
48
67
  it "should record a block's latency" do
49
- redis.should_receive(:hincrby).with("counters", "latencies.crawler.process.count", 1).once
50
- redis.should_receive(:hincrby).once.with do |key, subkey, latency|
51
- key == "counters" && subkey == "latencies.crawler.process.nanoseconds" && latency >= 0.2 * 1_000_000_000 && latency < 0.3 * 1_000_000_000
52
- end
53
68
  counter.latency "crawler.process" do
54
69
  sleep 0.2
55
70
  end
71
+
72
+ redis.hget("counters", "latencies.crawler.process.count").to_i.should == 1
73
+ redis.hget("counters", "latencies.crawler.process.nanoseconds").to_i.should be_within(0.01 * ONE_NANOSECOND ).of ( 0.2 * ONE_NANOSECOND )
56
74
  end
75
+
76
+ ONE_NANOSECOND = 1_000_000_000
57
77
  end
@@ -1,6 +1,12 @@
1
1
  require "counters"
2
2
  require "timecop"
3
3
 
4
+ begin
5
+ require "ruby-debug"
6
+ rescue LoadError
7
+ # Optional dependency - ignoring
8
+ end
9
+
4
10
  shared_examples_for "all counters" do
5
11
  it "should raise a ArgumentError when the key includes invalid chars" do
6
12
  lambda { counter.hit "hit!" } .should raise_error(ArgumentError)
@@ -23,4 +29,12 @@ shared_examples_for "all counters" do
23
29
  it "should not raise ArgumentError when the key includes an underscore" do
24
30
  lambda { counter.hit "hit_" }.should_not raise_error(ArgumentError)
25
31
  end
32
+
33
+ it "should return the latency block's value" do
34
+ value = counter.latency "process" do
35
+ "the returned value"
36
+ end
37
+
38
+ value.should == "the returned value"
39
+ end
26
40
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 0
8
- - 1
9
- version: 1.0.1
8
+ - 2
9
+ version: 1.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - "Fran\xC3\xA7ois Beausoleil"
@@ -14,11 +14,11 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-02-26 00:00:00 -05:00
17
+ date: 2011-03-21 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: rspec
21
+ name: redis
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
@@ -31,7 +31,7 @@ dependencies:
31
31
  type: :development
32
32
  version_requirements: *id001
33
33
  - !ruby/object:Gem::Dependency
34
- name: timecop
34
+ name: rspec
35
35
  prerelease: false
36
36
  requirement: &id002 !ruby/object:Gem::Requirement
37
37
  none: false
@@ -43,6 +43,32 @@ dependencies:
43
43
  version: "0"
44
44
  type: :development
45
45
  version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: timecop
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: ruby-debug19
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :development
71
+ version_requirements: *id004
46
72
  description: Using the provided API, record metrics (such as number of hits to a particular controller, bytes in/out, compression ratio) within your system. Visualization is NOT provided within this gem.
47
73
  email:
48
74
  - francois@teksol.info