counters 1.0.2 → 1.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.textile +43 -27
- data/lib/counters.rb +1 -0
- data/lib/counters/base.rb +18 -4
- data/lib/counters/file.rb +3 -2
- data/lib/counters/memory.rb +4 -2
- data/lib/counters/redis.rb +7 -6
- data/lib/counters/stats_d.rb +35 -0
- data/lib/counters/version.rb +1 -1
- data/samples/crawler.rb +37 -0
- data/spec/file_counter_spec.rb +37 -0
- data/spec/memory_counter_spec.rb +37 -2
- data/spec/redis_counter_spec.rb +36 -1
- data/spec/spec_helper.rb +7 -7
- data/spec/statsd_counter_spec.rb +80 -0
- metadata +8 -20
data/README.textile
CHANGED
@@ -1,53 +1,65 @@
|
|
1
1
|
h1. Counters
|
2
2
|
|
3
|
-
Easily record any
|
3
|
+
Easily record any metrics from anywhere within your code, using a very simple interface:
|
4
4
|
|
5
|
-
|
5
|
+
* <code>ping</code>: When's the last time we saw this thing?
|
6
|
+
* <code>hit</code>: Increments a counter
|
7
|
+
* <code>magnitude</code>: Measures numerical values
|
8
|
+
* <code>latency</code>: Measures time intervals
|
6
9
|
|
7
|
-
|
10
|
+
h2. Example
|
8
11
|
|
9
|
-
<
|
10
|
-
require "redis"
|
11
|
-
require "rest_client"
|
12
|
-
Counter = Counters::Redis.new(Redis.new, "counters")
|
12
|
+
Let's say you have a web crawler. There are a ton of things you can measure about your crawler: how many pages it processed (<code>#hit</code>), how many bytes you read (<code>#magnitude</code>), how long did it take to download the page (<code>#latency</code>), how long did it take to parse the raw HTML to a useable format (<code>#latency</code>).
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
<pre><code>require "uri"
|
15
|
+
require "open-uri"
|
16
|
+
require "counters"
|
17
|
+
require "nokogiri"
|
16
18
|
|
17
|
-
|
18
|
-
Counter.magnitude "crawler.bytes.read", response.length
|
19
|
-
next Counter.hit "crawler.urls.skipped" if response.code == 304
|
19
|
+
Counter = Counters::Redis.new(Redis.new, :namespace => "crawler", :base_key => "counters")
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
urls_to_crawl = ["http://blog.teksol.info/", "http://techcrunch.com/", "http://www.google.com/"]
|
22
|
+
|
23
|
+
while url = urls_to_crawl.pop
|
24
|
+
Counter.ping "crawler"
|
25
|
+
|
26
|
+
begin
|
27
|
+
Counter.hit "urls_popped"
|
28
|
+
|
29
|
+
puts "Fetching #{url}"
|
30
|
+
raw_html = Counter.latency "download" do
|
31
|
+
URI.parse(url).read
|
32
|
+
end
|
33
|
+
|
34
|
+
Counter.magnitude "bytes_in", raw_html.length
|
35
|
+
|
36
|
+
parsed_html = Counter.latency "html_parsing" do
|
37
|
+
Nokogiri::HTML(raw_html)
|
38
|
+
end
|
39
|
+
rescue
|
40
|
+
Counter.hit "error"
|
23
41
|
end
|
24
42
|
end
|
25
43
|
</code></pre>
|
26
44
|
|
27
|
-
|
28
|
-
|
29
|
-
* hits.crawler.urls = 1
|
30
|
-
* magnitudes.crawler.bytes.read = 2041
|
31
|
-
* latencies.crawler.processing.count = 1
|
32
|
-
* latencies.crawler.processing.nanoseconds = 381000000
|
33
|
-
|
34
|
-
h2. Other Implementations
|
45
|
+
h2. Other Backends
|
35
46
|
|
36
47
|
For testing purposes, there also exists a <code>Counters::Memory</code>. This would be good in test mode, for example. The counters are exposed through accessor methods returning a Hash.
|
37
48
|
|
38
|
-
|
49
|
+
You may log to a file, but be advised the file's size grows very quickly. Counters are stored in the file, one per line, in an easily readable format.
|
39
50
|
|
40
51
|
<pre><code>$ irb -r counters
|
41
52
|
> Counter = Counters::File.new("counters.log")
|
42
53
|
=> #<Counters::File:0x00000101a18f18 @logger=#<Logger:0x00000101a18ef0 @progname=nil, @level=0, @default_formatter=#<Logger::Formatter:0x00000101a18ea0 @datetime_format=nil>, @formatter=#<Proc:0x00000101a18bd0@/Users/francois/Projects/counters/lib/counters/file.rb:15 (lambda)>, @logdev=#<Logger::LogDevice:0x00000101a18e28 @shift_size=1048576, @shift_age=0, @filename="counters.log", @dev=#<File:counters.log>, @mutex=#<Logger::LogDevice::LogDeviceMutex:0x00000101a18e00 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0x00000101a18d88>>>>>
|
43
54
|
> Counter.hit "crawler.page_read"
|
44
|
-
=> true
|
55
|
+
=> true
|
45
56
|
> Counter.magnitude "crawler.bytes_in", 9_921
|
46
|
-
=> true
|
57
|
+
=> true
|
47
58
|
> Counter.latency "crawler.processing" do sleep 0.3 ; end
|
48
|
-
=> true
|
59
|
+
=> true
|
49
60
|
> Counter.ping "crawler.alive"
|
50
|
-
=> true
|
61
|
+
=> true
|
62
|
+
> exit
|
51
63
|
|
52
64
|
$ cat counters.log
|
53
65
|
2011-02-21T09:46:21.296326000 - hit: crawler.page_read
|
@@ -59,6 +71,10 @@ $ cat counters.log
|
|
59
71
|
2011-02-21T09:46:27.989183000 - latency: crawler.processing 0.3123122982101s
|
60
72
|
</code></pre>
|
61
73
|
|
74
|
+
You may also output your counters to "StatsD":https://github.com/etsy/statsd. The only change that might be surprising is magnitudes are output as timer events. Magnitudes are used to record
|
75
|
+
|
76
|
+
See the file "samples/crawler.rb":blob/master/samples/crawler.rb for a more detailed example.
|
77
|
+
|
62
78
|
h2. LICENSE
|
63
79
|
|
64
80
|
(The MIT License)
|
data/lib/counters.rb
CHANGED
data/lib/counters/base.rb
CHANGED
@@ -1,13 +1,21 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
1
3
|
module Counters
|
2
4
|
class Base
|
5
|
+
attr_accessor :namespace
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
@namespace = options[:namespace]
|
9
|
+
end
|
10
|
+
|
3
11
|
def hit(key)
|
4
12
|
validate(key)
|
5
|
-
record_hit(key)
|
13
|
+
record_hit(namespaced_key(key))
|
6
14
|
end
|
7
15
|
|
8
16
|
def magnitude(key, value)
|
9
17
|
validate(key)
|
10
|
-
record_magnitude(key, value)
|
18
|
+
record_magnitude(namespaced_key(key), value)
|
11
19
|
end
|
12
20
|
|
13
21
|
def latency(key, time_in_seconds=nil)
|
@@ -18,13 +26,13 @@ module Counters
|
|
18
26
|
time_in_seconds = Benchmark.measure { result = yield }.real
|
19
27
|
end
|
20
28
|
|
21
|
-
record_latency(key, time_in_seconds)
|
29
|
+
record_latency(namespaced_key(key), time_in_seconds)
|
22
30
|
result
|
23
31
|
end
|
24
32
|
|
25
33
|
def ping(key)
|
26
34
|
validate(key)
|
27
|
-
record_ping(key)
|
35
|
+
record_ping(namespaced_key(key))
|
28
36
|
end
|
29
37
|
|
30
38
|
def record_hit(key)
|
@@ -51,5 +59,11 @@ module Counters
|
|
51
59
|
key.to_s =~ /\A[.\w]+\Z/i or raise ArgumentError, "Keys can contain only letters, numbers, the underscore (_) and fullstop (.), received #{key.inspect}"
|
52
60
|
end
|
53
61
|
private :validate
|
62
|
+
|
63
|
+
def namespaced_key(key)
|
64
|
+
return key if namespace.nil?
|
65
|
+
"#{namespace}.#{key}"
|
66
|
+
end
|
67
|
+
private :namespaced_key
|
54
68
|
end
|
55
69
|
end
|
data/lib/counters/file.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
require "benchmark"
|
2
1
|
require "logger"
|
3
2
|
|
4
3
|
module Counters
|
5
4
|
class File < Counters::Base
|
6
|
-
def initialize(path_or_io_or_logger)
|
5
|
+
def initialize(path_or_io_or_logger, options={})
|
6
|
+
super(options)
|
7
|
+
|
7
8
|
@logger = if path_or_io_or_logger.kind_of?(Logger) then
|
8
9
|
path_or_io_or_logger
|
9
10
|
elsif path_or_io_or_logger.respond_to?(:<<) then
|
data/lib/counters/memory.rb
CHANGED
@@ -4,7 +4,9 @@ module Counters
|
|
4
4
|
class Memory < Counters::Base
|
5
5
|
attr_reader :hits, :latencies, :magnitudes, :pings
|
6
6
|
|
7
|
-
def initialize
|
7
|
+
def initialize(options={})
|
8
|
+
super(options)
|
9
|
+
|
8
10
|
@hits = Hash.new {|h,k| h[k] = 0}
|
9
11
|
@magnitudes = Hash.new {|h,k| h[k] = 0}
|
10
12
|
@latencies = Hash.new {|h,k| h[k] = Array.new}
|
@@ -16,7 +18,7 @@ module Counters
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def record_ping(key)
|
19
|
-
@pings[key] = Time.now
|
21
|
+
@pings[key] = Time.now.utc
|
20
22
|
end
|
21
23
|
|
22
24
|
def record_latency(key, time_in_seconds)
|
data/lib/counters/redis.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
-
require "benchmark"
|
2
|
-
|
3
1
|
# The redis gem must already be required - we don't require it.
|
4
2
|
# This allows callers / users to use any implementation that has the right API.
|
5
3
|
|
6
4
|
module Counters
|
7
5
|
class Redis < Counters::Base
|
8
|
-
def initialize(redis=Redis.new,
|
9
|
-
|
6
|
+
def initialize(redis=Redis.new, options={})
|
7
|
+
super(options)
|
8
|
+
|
9
|
+
@redis = redis
|
10
|
+
@base_key = options.fetch(:base_key) { raise "Missing :base_key from #{options.inspect}" }
|
10
11
|
end
|
11
12
|
|
12
13
|
def record_hit(key)
|
@@ -20,8 +21,8 @@ module Counters
|
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
23
|
-
def
|
24
|
-
@redis.hset(@base_key, "pings.#{key}", Time.now.to_i)
|
24
|
+
def record_ping(key)
|
25
|
+
@redis.hset(@base_key, "pings.#{key}", Time.now.utc.to_i)
|
25
26
|
end
|
26
27
|
|
27
28
|
# Redis requires integer keys, thus we scale all latencies to the nanosecond precision
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module Counters
|
4
|
+
class StatsD < Counters::Base
|
5
|
+
attr_reader :host, :port, :socket
|
6
|
+
|
7
|
+
def initialize(host, port, options={})
|
8
|
+
super(options)
|
9
|
+
|
10
|
+
@host, @port = host, port
|
11
|
+
@socket = options.fetch(:socket) { UDPSocket.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
def record_ping(key)
|
15
|
+
socket.send("pings.#{key}:1|c", 0, host, port)
|
16
|
+
end
|
17
|
+
|
18
|
+
def record_hit(key)
|
19
|
+
socket.send("hits.#{key}:1|c", 0, host, port)
|
20
|
+
end
|
21
|
+
|
22
|
+
# StatsD expects millisecond resolution
|
23
|
+
SCALING_FACTOR = 1_000
|
24
|
+
|
25
|
+
def record_latency(key, time_in_seconds)
|
26
|
+
value = "latencies.%s:%d|ms" % [key, time_in_seconds * SCALING_FACTOR]
|
27
|
+
socket.send(value, 0, host, port)
|
28
|
+
end
|
29
|
+
|
30
|
+
def record_magnitude(key, amount)
|
31
|
+
value = "magnitudes.%s:%d|ms" % [key, amount]
|
32
|
+
socket.send(value, 0, host, port)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/counters/version.rb
CHANGED
data/samples/crawler.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "open-uri"
|
3
|
+
require "counters"
|
4
|
+
require "nokogiri"
|
5
|
+
require "redis"
|
6
|
+
require "pp"
|
7
|
+
|
8
|
+
# Counter = Counters::Redis.new(Redis.new, :namespace => "crawler", :base_key => "counters")
|
9
|
+
Counter = Counters::StatsD.new("127.0.0.1", 8125, :namespace => "crawler")
|
10
|
+
# Counter = Counters::Memory.new(:namespace => "crawler")
|
11
|
+
# Counter = Counters::File.new(STDOUT, :namespace => "crawler")
|
12
|
+
|
13
|
+
urls_to_crawl = ["http://blog.teksol.info/", "http://techcrunch.com/", "http://www.google.com/"]
|
14
|
+
|
15
|
+
while url = urls_to_crawl.pop
|
16
|
+
Counter.ping "crawler"
|
17
|
+
|
18
|
+
begin
|
19
|
+
Counter.hit "urls_popped"
|
20
|
+
|
21
|
+
puts "Fetching #{url}"
|
22
|
+
raw_html = Counter.latency "download" do
|
23
|
+
URI.parse(url).read
|
24
|
+
end
|
25
|
+
|
26
|
+
Counter.magnitude "bytes_in", raw_html.length
|
27
|
+
|
28
|
+
parsed_html = Counter.latency "html_parsing" do
|
29
|
+
Nokogiri::HTML(raw_html)
|
30
|
+
end
|
31
|
+
rescue
|
32
|
+
Counter.hit "error"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
pp Counter if Counter.instance_of?(Counters::Memory)
|
37
|
+
pp Redis.new.hgetall("counters") if Counter.instance_of?(Counters::Redis)
|
data/spec/file_counter_spec.rb
CHANGED
@@ -15,6 +15,43 @@ describe Counters::File do
|
|
15
15
|
|
16
16
|
it_should_behave_like "all counters"
|
17
17
|
|
18
|
+
context "#initialize" do
|
19
|
+
it "should accept a namepsace in options" do
|
20
|
+
counter = Counters::File.new(tempfile, :namespace => "wine")
|
21
|
+
counter.namespace.should == "wine"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "given the counter is namespaced" do
|
26
|
+
it "should namespace the key from #hit" do
|
27
|
+
counter.namespace = "juice"
|
28
|
+
counter.hit "foxglove"
|
29
|
+
tempfile.rewind
|
30
|
+
tempfile.read.should =~ /^#{TIMESTAMP_RE}\s-\shit:\sjuice\.foxglove$/
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should namespace the key from #latency" do
|
34
|
+
counter.namespace = "cocktail"
|
35
|
+
counter.latency "angelica", 200
|
36
|
+
tempfile.rewind
|
37
|
+
tempfile.read.should =~ /^#{TIMESTAMP_RE}\s-\slatency:\scocktail\.angelica\s200s$/
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should namespace the key from #magnitude" do
|
41
|
+
counter.namespace = "brew"
|
42
|
+
counter.magnitude "crocus", 100
|
43
|
+
tempfile.rewind
|
44
|
+
tempfile.read.should =~ /^#{TIMESTAMP_RE}\s-\smagnitude:\sbrew\.crocus 100$/
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should namespace the key from #ping" do
|
48
|
+
counter.namespace = "beer"
|
49
|
+
counter.ping "tulip"
|
50
|
+
tempfile.rewind
|
51
|
+
tempfile.read.should =~ /^#{TIMESTAMP_RE}\s-\sping:\sbeer\.tulip$/
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
18
55
|
it "should log a message to the logfile when a hit is recorded" do
|
19
56
|
counter.hit "urls.visited"
|
20
57
|
tempfile.rewind
|
data/spec/memory_counter_spec.rb
CHANGED
@@ -7,6 +7,41 @@ describe Counters::Memory do
|
|
7
7
|
|
8
8
|
it_should_behave_like "all counters"
|
9
9
|
|
10
|
+
context "#initialize" do
|
11
|
+
it "should accept a namepsace in options" do
|
12
|
+
counter = Counters::Memory.new(:namespace => "wine")
|
13
|
+
counter.namespace.should == "wine"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "given the counter is namespaced" do
|
18
|
+
it "should namespace the key from #hit" do
|
19
|
+
counter.namespace = "juice"
|
20
|
+
counter.hit "foxglove"
|
21
|
+
counter.hits["juice.foxglove"].should == 1
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should namespace the key from #latency" do
|
25
|
+
counter.namespace = "cocktail"
|
26
|
+
counter.latency "angelica", 200
|
27
|
+
counter.latencies["cocktail.angelica"].should == [200]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should namespace the key from #magnitude" do
|
31
|
+
counter.namespace = "brew"
|
32
|
+
counter.magnitude "crocus", 100
|
33
|
+
counter.magnitudes["brew.crocus"].should == 100
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should namespace the key from #ping" do
|
37
|
+
counter.namespace = "beer"
|
38
|
+
Timecop.freeze(Time.now.utc) do
|
39
|
+
counter.ping "tulip"
|
40
|
+
counter.pings["beer.tulip"].should == Time.now.utc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
10
45
|
it "should record a hit with key 'pages.read'" do
|
11
46
|
counter.hit "pages.read"
|
12
47
|
counter.hits.should have_key("pages.read")
|
@@ -20,14 +55,14 @@ describe Counters::Memory do
|
|
20
55
|
Timecop.freeze do
|
21
56
|
counter.ping "processor.alive"
|
22
57
|
counter.pings.should have_key("processor.alive")
|
23
|
-
counter.pings["processor.alive"].strftime("%Y-%m-%d %H:%M:%S").should == Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
58
|
+
counter.pings["processor.alive"].strftime("%Y-%m-%d %H:%M:%S").should == Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")
|
24
59
|
end
|
25
60
|
|
26
61
|
target_time = Time.now + 9
|
27
62
|
Timecop.travel(target_time) do
|
28
63
|
counter.ping "processor.alive"
|
29
64
|
counter.pings.should have_key("processor.alive")
|
30
|
-
counter.pings["processor.alive"].strftime("%Y-%m-%d %H:%M:%S").should == target_time.strftime("%Y-%m-%d %H:%M:%S")
|
65
|
+
counter.pings["processor.alive"].strftime("%Y-%m-%d %H:%M:%S").should == target_time.utc.strftime("%Y-%m-%d %H:%M:%S")
|
31
66
|
end
|
32
67
|
end
|
33
68
|
|
data/spec/redis_counter_spec.rb
CHANGED
@@ -7,7 +7,7 @@ describe Counters::Redis, "integration tests" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
let :counter do
|
10
|
-
Counters::Redis.new(redis, "counters")
|
10
|
+
Counters::Redis.new(redis, :base_key => "counters")
|
11
11
|
end
|
12
12
|
|
13
13
|
before(:each) do
|
@@ -16,6 +16,41 @@ describe Counters::Redis, "integration tests" do
|
|
16
16
|
|
17
17
|
it_should_behave_like "all counters"
|
18
18
|
|
19
|
+
context "#initialize" do
|
20
|
+
it "should accept a namepsace in options" do
|
21
|
+
counter = Counters::Redis.new(redis, :namespace => "wine", :base_key => "counters")
|
22
|
+
counter.namespace.should == "wine"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "given the counter is namespaced" do
|
27
|
+
it "should namespace the key from #hit" do
|
28
|
+
counter.namespace = "juice"
|
29
|
+
counter.hit "foxglove"
|
30
|
+
redis.hget("counters", "hits.juice.foxglove").should == "1"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should namespace the key from #latency" do
|
34
|
+
counter.namespace = "cocktail"
|
35
|
+
counter.latency "angelica", 200
|
36
|
+
redis.hget("counters", "latencies.cocktail.angelica.nanoseconds").should == (200 * Counters::Redis::SCALING_FACTOR).to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should namespace the key from #magnitude" do
|
40
|
+
counter.namespace = "brew"
|
41
|
+
counter.magnitude "crocus", 100
|
42
|
+
redis.hget("counters", "magnitudes.brew.crocus.value").should == "100"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should namespace the key from #ping" do
|
46
|
+
counter.namespace = "beer"
|
47
|
+
Timecop.freeze(Time.now.utc) do
|
48
|
+
counter.ping "tulip"
|
49
|
+
redis.hget("counters", "pings.beer.tulip").should == Time.now.utc.to_i.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
19
54
|
it "should record 2 hits on 'pages.read'" do
|
20
55
|
2.times { counter.hit "pages.read" }
|
21
56
|
redis.hkeys("counters").should == ["hits.pages.read"]
|
data/spec/spec_helper.rb
CHANGED
@@ -9,13 +9,13 @@ end
|
|
9
9
|
|
10
10
|
shared_examples_for "all counters" do
|
11
11
|
it "should raise a ArgumentError when the key includes invalid chars" do
|
12
|
-
lambda { counter.hit "hit!" }
|
13
|
-
lambda { counter.hit "hit counter" }
|
14
|
-
lambda { counter.hit "boy.hit?" }
|
15
|
-
lambda { counter.hit "hit/a" }
|
16
|
-
lambda { counter.hit "hit-a" }
|
17
|
-
lambda { counter.hit "" }
|
18
|
-
lambda { counter.hit nil }
|
12
|
+
lambda { counter.hit "hit!" }.should raise_error(ArgumentError)
|
13
|
+
lambda { counter.hit "hit counter" }.should raise_error(ArgumentError)
|
14
|
+
lambda { counter.hit "boy.hit?" }.should raise_error(ArgumentError)
|
15
|
+
lambda { counter.hit "hit/a" }.should raise_error(ArgumentError)
|
16
|
+
lambda { counter.hit "hit-a" }.should raise_error(ArgumentError)
|
17
|
+
lambda { counter.hit "" }.should raise_error(ArgumentError)
|
18
|
+
lambda { counter.hit nil }.should raise_error(ArgumentError)
|
19
19
|
end
|
20
20
|
|
21
21
|
it "should not raise ArgumentError when the key includes a number" do
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Counters::StatsD do
|
4
|
+
let :socket do
|
5
|
+
double("socket")
|
6
|
+
end
|
7
|
+
|
8
|
+
let :host do
|
9
|
+
"127.0.0.1"
|
10
|
+
end
|
11
|
+
|
12
|
+
let :port do
|
13
|
+
8125
|
14
|
+
end
|
15
|
+
|
16
|
+
let :counter do
|
17
|
+
Counters::StatsD.new(host, port, :socket => socket)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "" do
|
21
|
+
before(:each) do
|
22
|
+
socket.as_null_object
|
23
|
+
end
|
24
|
+
|
25
|
+
it_should_behave_like "all counters"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should record a hit as an increment of the key" do
|
29
|
+
socket.should_receive(:send).with("hits.tweets_received:1|c", 0, host, port)
|
30
|
+
counter.hit "tweets_received"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should record a magnitude as a timer" do
|
34
|
+
socket.should_receive(:send).with("magnitudes.bytes_in:381|ms", 0, host, port)
|
35
|
+
counter.magnitude "bytes_in", 381
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should record a latency as a timer" do
|
39
|
+
socket.should_receive(:send).with("latencies.json_parsing:9|ms", 0, host, port)
|
40
|
+
counter.latency "json_parsing", 0.009
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should record a ping as a counter" do
|
44
|
+
socket.should_receive(:send).with("pings.tweet_processor:1|c", 0, host, port)
|
45
|
+
counter.ping "tweet_processor"
|
46
|
+
end
|
47
|
+
|
48
|
+
context "#initialize" do
|
49
|
+
it "should accept a namepsace in options" do
|
50
|
+
counter = Counters::StatsD.new(host, port, :namespace => "wine")
|
51
|
+
counter.namespace.should == "wine"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "given the counter is namespaced" do
|
56
|
+
it "should namespace the key from #hit" do
|
57
|
+
counter.namespace = "juice"
|
58
|
+
socket.should_receive(:send).with(/^hits\.juice\.foxglove\b/, anything, anything, anything)
|
59
|
+
counter.hit "foxglove"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should namespace the key from #latency" do
|
63
|
+
counter.namespace = "cocktail"
|
64
|
+
socket.should_receive(:send).with(/^latencies\.cocktail\.angelica\b/, anything, anything, anything)
|
65
|
+
counter.latency "angelica", 200
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should namespace the key from #magnitude" do
|
69
|
+
counter.namespace = "brew"
|
70
|
+
socket.should_receive(:send).with(/^magnitudes\.brew\.crocus\b/, anything, anything, anything)
|
71
|
+
counter.magnitude "crocus", 100
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should namespace the key from #ping" do
|
75
|
+
counter.namespace = "beer"
|
76
|
+
socket.should_receive(:send).with(/^pings\.beer\.tulip\b/, anything, anything, anything)
|
77
|
+
counter.ping "tulip"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: counters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 1
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
version: 1.0.2
|
4
|
+
prerelease:
|
5
|
+
version: 1.1.0
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- "Fran\xC3\xA7ois Beausoleil"
|
@@ -14,7 +10,7 @@ autorequire:
|
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
12
|
|
17
|
-
date: 2011-03-
|
13
|
+
date: 2011-03-29 00:00:00 -04:00
|
18
14
|
default_executable:
|
19
15
|
dependencies:
|
20
16
|
- !ruby/object:Gem::Dependency
|
@@ -25,8 +21,6 @@ dependencies:
|
|
25
21
|
requirements:
|
26
22
|
- - ">="
|
27
23
|
- !ruby/object:Gem::Version
|
28
|
-
segments:
|
29
|
-
- 0
|
30
24
|
version: "0"
|
31
25
|
type: :development
|
32
26
|
version_requirements: *id001
|
@@ -38,8 +32,6 @@ dependencies:
|
|
38
32
|
requirements:
|
39
33
|
- - ">="
|
40
34
|
- !ruby/object:Gem::Version
|
41
|
-
segments:
|
42
|
-
- 0
|
43
35
|
version: "0"
|
44
36
|
type: :development
|
45
37
|
version_requirements: *id002
|
@@ -51,8 +43,6 @@ dependencies:
|
|
51
43
|
requirements:
|
52
44
|
- - ">="
|
53
45
|
- !ruby/object:Gem::Version
|
54
|
-
segments:
|
55
|
-
- 0
|
56
46
|
version: "0"
|
57
47
|
type: :development
|
58
48
|
version_requirements: *id003
|
@@ -64,8 +54,6 @@ dependencies:
|
|
64
54
|
requirements:
|
65
55
|
- - ">="
|
66
56
|
- !ruby/object:Gem::Version
|
67
|
-
segments:
|
68
|
-
- 0
|
69
57
|
version: "0"
|
70
58
|
type: :development
|
71
59
|
version_requirements: *id004
|
@@ -92,11 +80,14 @@ files:
|
|
92
80
|
- lib/counters/file.rb
|
93
81
|
- lib/counters/memory.rb
|
94
82
|
- lib/counters/redis.rb
|
83
|
+
- lib/counters/stats_d.rb
|
95
84
|
- lib/counters/version.rb
|
85
|
+
- samples/crawler.rb
|
96
86
|
- spec/file_counter_spec.rb
|
97
87
|
- spec/memory_counter_spec.rb
|
98
88
|
- spec/redis_counter_spec.rb
|
99
89
|
- spec/spec_helper.rb
|
90
|
+
- spec/statsd_counter_spec.rb
|
100
91
|
has_rdoc: true
|
101
92
|
homepage: https://github.com/francois/counters
|
102
93
|
licenses: []
|
@@ -111,21 +102,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
102
|
requirements:
|
112
103
|
- - ">="
|
113
104
|
- !ruby/object:Gem::Version
|
114
|
-
segments:
|
115
|
-
- 0
|
116
105
|
version: "0"
|
117
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
107
|
none: false
|
119
108
|
requirements:
|
120
109
|
- - ">="
|
121
110
|
- !ruby/object:Gem::Version
|
122
|
-
segments:
|
123
|
-
- 0
|
124
111
|
version: "0"
|
125
112
|
requirements: []
|
126
113
|
|
127
114
|
rubyforge_project:
|
128
|
-
rubygems_version: 1.
|
115
|
+
rubygems_version: 1.6.2
|
129
116
|
signing_key:
|
130
117
|
specification_version: 3
|
131
118
|
summary: Provides an API to record any kind of metrics within your system
|
@@ -134,3 +121,4 @@ test_files:
|
|
134
121
|
- spec/memory_counter_spec.rb
|
135
122
|
- spec/redis_counter_spec.rb
|
136
123
|
- spec/spec_helper.rb
|
124
|
+
- spec/statsd_counter_spec.rb
|