camayoc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +163 -0
- data/lib/camayoc/handlers/filter.rb +43 -0
- data/lib/camayoc/handlers/io.rb +25 -0
- data/lib/camayoc/handlers/logger.rb +44 -0
- data/lib/camayoc/handlers/memory.rb +35 -0
- data/lib/camayoc/handlers/redis.rb +49 -0
- data/lib/camayoc/handlers/statsd.rb +47 -0
- data/lib/camayoc/handlers/timing.rb +26 -0
- data/lib/camayoc/stat_event.rb +18 -0
- data/lib/camayoc/stats.rb +77 -0
- data/lib/camayoc/thread_safety.rb +31 -0
- data/lib/camayoc/version.rb +3 -0
- data/lib/camayoc.rb +67 -0
- data/test/handlers/filter_test.rb +53 -0
- data/test/handlers/io_test.rb +33 -0
- data/test/handlers/logger_test.rb +37 -0
- data/test/handlers/memory_test.rb +38 -0
- data/test/handlers/redis_test.rb +28 -0
- data/test/handlers/statsd_test.rb +22 -0
- data/test/registration_test.rb +57 -0
- data/test/stat_event_test.rb +8 -0
- data/test/stats_test.rb +94 -0
- data/test/test_helper.rb +6 -0
- data/test/thread_safety_test.rb +33 -0
- metadata +105 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 by Appozite, LLC
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
Overview
|
2
|
+
========
|
3
|
+
Camayoc is a flexible way to keep track of application stats. Inspired by
|
4
|
+
various logging frameworks (especially Log4r) it makes it easy to send stats
|
5
|
+
information to multiple destinations via the use of Handlers. Right out of the
|
6
|
+
box, it supports sending stats to the following:
|
7
|
+
|
8
|
+
* A [statsd daemon](https://github.com/etsy/statsd/) for ridiculously easy [Graphite](http://graphite.wikidot.com/) stats
|
9
|
+
* An in-memory hash for in-process stats
|
10
|
+
* A logger or IO stream
|
11
|
+
* Redis (EXPERIMENTAL - you'll need the [redis gem](https://github.com/ezmobius/redis-rb))
|
12
|
+
|
13
|
+
Philosophy
|
14
|
+
----------
|
15
|
+
Application stats are critical. But even critical things get ignored if they're
|
16
|
+
hard (believe me, we know). Stats should be easy:
|
17
|
+
|
18
|
+
* Collecting stats should take just one line of code
|
19
|
+
* All stat collection should be fire-and-forget with no error handling required
|
20
|
+
* Organizing stats should be easy
|
21
|
+
* There should be essentially no performance cost to collecting stats
|
22
|
+
|
23
|
+
Examples
|
24
|
+
==========
|
25
|
+
Here's all it takes to fire stats to statsd.
|
26
|
+
|
27
|
+
require 'camayoc'
|
28
|
+
# Grab a stats instance
|
29
|
+
stats = Camayoc["my_app"]
|
30
|
+
# Add a handler for statsd
|
31
|
+
stats.add(Camayoc::Handlers::Statsd.new(:host=>"localhost",:port=>1234))
|
32
|
+
|
33
|
+
# later in your app
|
34
|
+
def do_some_stuff
|
35
|
+
# Do some stuff
|
36
|
+
Camayoc["my_app"].increment("did_stuff")
|
37
|
+
end
|
38
|
+
|
39
|
+
Your stat will be sent to statsd with the name "my_app.did_stuff". See the
|
40
|
+
statsd docs for more information about how that gets translated into Graphite.
|
41
|
+
|
42
|
+
Namespaces
|
43
|
+
----------
|
44
|
+
Many logging frameworks support the concept of namespaced logs that "extend"
|
45
|
+
other logs. This makes it easy to log messages from different areas of your
|
46
|
+
app and stay sane. Camayoc does this as well.
|
47
|
+
|
48
|
+
Let's say you have a service within your app where you want to store some timing
|
49
|
+
data.
|
50
|
+
|
51
|
+
stats = Camayoc["my_app"]
|
52
|
+
stats.add(Camayoc::Handlers::Statsd.new(:host=>"localhost",:port=>1234))
|
53
|
+
|
54
|
+
class AwesomeService
|
55
|
+
def be_awesome
|
56
|
+
start_time = Time.now.to_f
|
57
|
+
# Be, you know, awesome
|
58
|
+
ms = ((Time.now_to_f - start_time) * 1000).round
|
59
|
+
Camayoc["my_app:awesome_service"].timing("awesome_duration",ms)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
This will automatically create a timing metric in graphite via statsd called
|
64
|
+
"my_app.awesome_service.awesome_duration". It does this by using the statsd
|
65
|
+
handler already configured for its "my_app" parent. Now, about handlers...
|
66
|
+
|
67
|
+
Handlers
|
68
|
+
--------
|
69
|
+
Just like loggers can have multiple outputters, you might want to send your
|
70
|
+
stats to different places.
|
71
|
+
|
72
|
+
app_stats = Camayoc["my_app"]
|
73
|
+
app_stats.add(Camayoc::Handlers::Statsd.new(:host=>"localhost",:port=>1234))
|
74
|
+
|
75
|
+
foo_stats = Camayoc["my_app:foo"]
|
76
|
+
foo_stats.add(Camayoc::Handlers::Redis.new(:host=>"localhost"))
|
77
|
+
|
78
|
+
app_stats.count("bar",1000) # Stats end up in statsd
|
79
|
+
|
80
|
+
foo_stats.count("baz",150) # Stats go to redis *and* statsd
|
81
|
+
|
82
|
+
Filters
|
83
|
+
-------
|
84
|
+
Sometimes you may want to send only certain stats to certain places.
|
85
|
+
|
86
|
+
app_stats = Camayoc["my_app"]
|
87
|
+
app_stats.add(Camayoc::Handlers::Statsd.new(:host=>"localhost",:port=>1234))
|
88
|
+
|
89
|
+
foo_stats = Camayoc["my_app:foo"]
|
90
|
+
foo_stats.add(Camayoc::Handlers::Redis.new(:host=>"localhost"),:when=>/baz/)
|
91
|
+
|
92
|
+
foo_stats.increment("baz",1000) #Stats go to redis and statsd
|
93
|
+
foo_stats.increment("bar",5) #Stats only go to statsd, not redis
|
94
|
+
|
95
|
+
There are other options as well like :if and :unless that take Procs that can
|
96
|
+
be executed to determine if a metric should be sent to the specified handler.
|
97
|
+
See Camayoc::Handlers::Filter for more.
|
98
|
+
|
99
|
+
Available Handlers
|
100
|
+
==================
|
101
|
+
Statsd
|
102
|
+
------
|
103
|
+
Class: Camayoc::Handlers::Statsd
|
104
|
+
|
105
|
+
This handler sends data to the statd daemon for use in graphite. If you can get
|
106
|
+
graphite and statsd going, then you'll get pretty graphs.
|
107
|
+
|
108
|
+
Memory
|
109
|
+
------
|
110
|
+
Class: Camayoc::Handlers::Memory
|
111
|
+
|
112
|
+
Stores counts and timing data in an in-memory hash. This might be handy for
|
113
|
+
storing some temporary in-process stats.
|
114
|
+
|
115
|
+
Logger
|
116
|
+
------
|
117
|
+
Class: Camayoc::Handlers::Logger
|
118
|
+
|
119
|
+
Writes out stat values and a timestamp to a logger instance for later analysis.
|
120
|
+
The format and method called on the logger can be controlled by constructor
|
121
|
+
arguments. See the class for details.
|
122
|
+
|
123
|
+
IO
|
124
|
+
--
|
125
|
+
Class: Camayoc::Handlers::IO
|
126
|
+
|
127
|
+
Writes out stats values and a timestamp to some stream of your choice. See
|
128
|
+
class for configuration details.
|
129
|
+
|
130
|
+
Redis (Experimental)
|
131
|
+
--------------------
|
132
|
+
Class: Camayoc::Handlers::Redis (require "camayoc/handlers/redis" first)
|
133
|
+
|
134
|
+
The redis gem is required to use this handler. This is a very, very simple
|
135
|
+
implementation that stores some data in redis. It is in no way a full-fledged
|
136
|
+
time-based stats system. It does make it easy to collect some simple counts and
|
137
|
+
some timing data. You can easily retrieve the stored data from redis by using
|
138
|
+
the #get method.
|
139
|
+
|
140
|
+
|
141
|
+
Implementing a Handler
|
142
|
+
======================
|
143
|
+
Let's say you want to implement your own handler, pehaps to MongoDB. Handlers
|
144
|
+
just need to respond to a simple interface. See Camayoc::Handlers::StatEvent
|
145
|
+
for info on the argument to the two methods.
|
146
|
+
|
147
|
+
class SomeHandler
|
148
|
+
def count(stat_event)
|
149
|
+
# Do something
|
150
|
+
end
|
151
|
+
|
152
|
+
def timing(stat_event)
|
153
|
+
# Do something
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
If you write a handler and would like it included in Camayoc, please fork
|
158
|
+
and send a pull request and we'll get it integrated in.
|
159
|
+
|
160
|
+
Acknowledgements
|
161
|
+
================
|
162
|
+
* The basic structure of Camayoc owes a lot of [Log4r](http://log4r.rubyforge.org/)
|
163
|
+
* The socket code for the Statsd handler is a modification of [reinh](https://github.com/reinh)'s [statsd](https://github.com/reinh/statsd)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Camayoc
|
2
|
+
module Handlers
|
3
|
+
class Filter
|
4
|
+
|
5
|
+
def initialize(dest,options={},&block)
|
6
|
+
@dest = dest
|
7
|
+
if block_given?
|
8
|
+
@filter = block
|
9
|
+
elsif options[:with]
|
10
|
+
pattern = options[:with]
|
11
|
+
@filter = Proc.new {|type,event| event.ns_stat =~ pattern }
|
12
|
+
elsif options[:if]
|
13
|
+
@filter = options[:if]
|
14
|
+
elsif options[:unless]
|
15
|
+
proc = options[:unless]
|
16
|
+
@filter = Proc.new do |type,event|
|
17
|
+
!proc.call(type,event)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@filter = Proc.new {|args| true }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def count(event)
|
25
|
+
if allowed?(:count,event)
|
26
|
+
@dest.count(event)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def timing(event)
|
31
|
+
if allowed?(:timing,event)
|
32
|
+
@dest.timing(event)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def allowed?(type,event)
|
38
|
+
@filter.call(type,event)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Camayoc
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Write to a raw IO stream with optional thread-safe locking before writing
|
5
|
+
# to the stream. By default, each stat message is written on a single line
|
6
|
+
# using puts. See the options in Camayoc::Handlers:Logger for options.
|
7
|
+
class IO < Logger
|
8
|
+
|
9
|
+
include ThreadSafety
|
10
|
+
|
11
|
+
def initialize(io=$stdout,options={})
|
12
|
+
super(io,{:method=>:puts}.merge(options))
|
13
|
+
self.thread_safe = Camayoc.thread_safe?
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
def write(type,event)
|
18
|
+
synchronize do
|
19
|
+
super(type,event)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Camayoc
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Write stats to a logger. Specify the method to call on the logger instance
|
5
|
+
# with :method (usually something like :info). If not :method is specified
|
6
|
+
# :debug will be called on the logger. You can control the format of the
|
7
|
+
# message passed to the logger method using the :formatter Proc.
|
8
|
+
class Logger
|
9
|
+
|
10
|
+
attr_accessor :logger, :method, :formatter
|
11
|
+
|
12
|
+
def initialize(logger, options={})
|
13
|
+
self.logger = logger
|
14
|
+
self.method = options[:method]
|
15
|
+
self.formatter = (options[:formatter] || default_formatter)
|
16
|
+
end
|
17
|
+
|
18
|
+
def count(event)
|
19
|
+
write(:count,event)
|
20
|
+
end
|
21
|
+
|
22
|
+
def timing(event)
|
23
|
+
write(:timing,event)
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_formatter
|
27
|
+
Proc.new do |type,event|
|
28
|
+
"#{type} #{event.ns_stat} #{event.value} #{Time.now.utc.to_i}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
def write(type,event)
|
34
|
+
msg = formatter.call(type,event)
|
35
|
+
if @method
|
36
|
+
@logger.send(@method,msg)
|
37
|
+
else
|
38
|
+
@logger.debug(msg)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Camayoc
|
2
|
+
module Handlers
|
3
|
+
class Memory
|
4
|
+
|
5
|
+
include ThreadSafety
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
@data = {}
|
9
|
+
self.thread_safe = Camayoc.thread_safe?
|
10
|
+
end
|
11
|
+
|
12
|
+
def count(event)
|
13
|
+
stat = event.ns_stat
|
14
|
+
synchronize do
|
15
|
+
@data[stat] ||= 0
|
16
|
+
@data[stat] += event.value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def timing(event)
|
21
|
+
synchronize do
|
22
|
+
(@data[event.ns_stat] ||= Timing.new) << event.value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(stat)
|
27
|
+
synchronize do
|
28
|
+
@data[stat]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias_method :[], :get
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module Camayoc
|
4
|
+
module Handlers
|
5
|
+
|
6
|
+
# This class is experimental.
|
7
|
+
class Redis
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
@namespace = options.delete(:namespace)
|
11
|
+
@redis = (options[:redis] || ::Redis.new(options))
|
12
|
+
end
|
13
|
+
|
14
|
+
def count(event)
|
15
|
+
@redis.incrby(key(event.ns_stat),event.value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def timing(event)
|
19
|
+
stat = event.ns_stat
|
20
|
+
ms = event.value
|
21
|
+
@redis.set(key(stat),"timing")
|
22
|
+
@redis.incrby(key("#{stat}:_count"),1)
|
23
|
+
@redis.incrby(key("#{stat}:_total"),ms)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(stat)
|
27
|
+
value = @redis.get(key(stat))
|
28
|
+
if value == "timing"
|
29
|
+
Timing.new(
|
30
|
+
@redis.get(key("#{stat}:_total")).to_i,
|
31
|
+
@redis.get(key("#{stat}:_count")).to_i,
|
32
|
+
nil,nil # Don't support min and max right now in redis
|
33
|
+
)
|
34
|
+
elsif value
|
35
|
+
value.to_i
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
alias_method :[], :get
|
41
|
+
|
42
|
+
private
|
43
|
+
def key(stat)
|
44
|
+
@namespace ? "#{@namespace}:#{stat}" : stat
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# This class is a port of the code in https://github.com/reinh/statsd to fit
|
4
|
+
# with the Camayoc handler interface.
|
5
|
+
module Camayoc
|
6
|
+
module Handlers
|
7
|
+
class Statsd
|
8
|
+
|
9
|
+
include ThreadSafety
|
10
|
+
|
11
|
+
attr_accessor :namespace
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
self.namespace = options[:namespace]
|
15
|
+
@host = options[:host]
|
16
|
+
@port = options[:port]
|
17
|
+
@socket = UDPSocket.new
|
18
|
+
self.thread_safe = Camayoc.thread_safe?
|
19
|
+
end
|
20
|
+
|
21
|
+
def count(event)
|
22
|
+
send(event.ns_stat,event.value,'c',event.options[:sample_rate]||1)
|
23
|
+
end
|
24
|
+
|
25
|
+
def timing(event)
|
26
|
+
send(event.ns_stat, event.value, 'ms', event.options[:sample_rate]||1)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def sampled(sample_rate)
|
31
|
+
yield unless sample_rate < 1 and rand > sample_rate
|
32
|
+
end
|
33
|
+
|
34
|
+
def send(stat, delta, type, sample_rate)
|
35
|
+
prefix = "#{@namespace}." unless @namespace.nil?
|
36
|
+
sampled(sample_rate) do
|
37
|
+
stat = stat.gsub(Camayoc::DELIMITER,'.')
|
38
|
+
msg = "#{prefix}#{stat}:#{delta}|#{type}#{'|@' << sample_rate.to_s if sample_rate < 1}"
|
39
|
+
synchronize do
|
40
|
+
@socket.send(msg, 0, @host, @port)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Camayoc
|
2
|
+
module Handlers
|
3
|
+
class Timing
|
4
|
+
attr_accessor :total, :count, :min, :max
|
5
|
+
|
6
|
+
def initialize(total=0,count=0,min=nil,max=nil)
|
7
|
+
self.total = total
|
8
|
+
self.count = count
|
9
|
+
self.max = max
|
10
|
+
self.min = min
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(ms)
|
14
|
+
@total += ms
|
15
|
+
@count += 1
|
16
|
+
@max = ms if @max.nil? || ms > @max
|
17
|
+
@min = ms if @min.nil? || ms < @min
|
18
|
+
end
|
19
|
+
|
20
|
+
def average
|
21
|
+
return nil if @count == 0
|
22
|
+
@total/@count
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Camayoc
|
2
|
+
class StatEvent
|
3
|
+
|
4
|
+
attr_accessor :source, :stat, :value, :options
|
5
|
+
|
6
|
+
def initialize(source,stat,value,options={})
|
7
|
+
self.source = source
|
8
|
+
self.stat = stat
|
9
|
+
self.value = value
|
10
|
+
self.options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def ns_stat
|
14
|
+
"#{source}#{Camayoc::DELIMITER}#{stat}"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Camayoc
|
2
|
+
class Stats
|
3
|
+
|
4
|
+
attr_accessor :name, :parent, :handlers
|
5
|
+
|
6
|
+
def initialize(name,parent=nil)
|
7
|
+
self.name = name
|
8
|
+
self.parent = parent
|
9
|
+
self.handlers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(handler,filter_opts={})
|
13
|
+
if !filter_opts.empty?
|
14
|
+
handler = Handlers::Filter.new(handler,filter_opts)
|
15
|
+
end
|
16
|
+
self.handlers << handler
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(handler)
|
21
|
+
self.add(handler)
|
22
|
+
end
|
23
|
+
|
24
|
+
def increment(stat,options={})
|
25
|
+
count(stat,1,options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def decrement(stat,options={})
|
29
|
+
count(stat,-1,options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def count(stat,value,options={})
|
33
|
+
count_event(StatEvent.new(name,stat,value,options))
|
34
|
+
end
|
35
|
+
|
36
|
+
def timing(stat,ms,options={})
|
37
|
+
timing_event(StatEvent.new(name,stat,ms,options))
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def count_event(event)
|
42
|
+
each_handler do |handler|
|
43
|
+
handler.count(event)
|
44
|
+
end
|
45
|
+
if parent
|
46
|
+
parent.count_event(event)
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def timing_event(event)
|
52
|
+
each_handler do |handler|
|
53
|
+
handler.timing(event)
|
54
|
+
end
|
55
|
+
if parent
|
56
|
+
parent.timing_event(event)
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def each_handler
|
63
|
+
handlers.each do |handler|
|
64
|
+
begin
|
65
|
+
yield(handler)
|
66
|
+
rescue
|
67
|
+
# Swallow up errors
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ns_stat(stat,ns=nil)
|
73
|
+
"#{ns || name}#{Camayoc::NAME_DELIMITER}#{stat}"
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Camayoc
|
2
|
+
module ThreadSafety
|
3
|
+
|
4
|
+
class PlaceboLock
|
5
|
+
def synchronize
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Ideally this should be called from your constructor
|
11
|
+
def thread_safe=(value)
|
12
|
+
@lock = value ? Mutex.new : PlaceboLock.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def thread_safe?
|
16
|
+
lock.is_a?(Mutex)
|
17
|
+
end
|
18
|
+
|
19
|
+
def synchronize
|
20
|
+
lock.synchronize { yield }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def lock
|
25
|
+
# If we get here and have to init the lock because it's nil, obviously
|
26
|
+
# it's not thread safe
|
27
|
+
@lock ||= PlaceboLock.new
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/camayoc.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'camayoc/version'
|
3
|
+
require 'camayoc/thread_safety'
|
4
|
+
require 'camayoc/stat_event'
|
5
|
+
require 'camayoc/handlers/timing'
|
6
|
+
require 'camayoc/handlers/filter'
|
7
|
+
require 'camayoc/handlers/logger'
|
8
|
+
require 'camayoc/handlers/io'
|
9
|
+
require 'camayoc/handlers/memory'
|
10
|
+
require 'camayoc/handlers/statsd'
|
11
|
+
require 'camayoc/stats'
|
12
|
+
|
13
|
+
module Camayoc
|
14
|
+
|
15
|
+
DELIMITER = ":"
|
16
|
+
|
17
|
+
@registry = {}
|
18
|
+
@lock = Mutex.new
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def [](name)
|
22
|
+
@lock.synchronize { @registry[name] ||= _new(name) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def new(name)
|
26
|
+
@lock.synchronize { _new(name) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def all
|
30
|
+
@lock.synchronize { @registry.values.dup }
|
31
|
+
end
|
32
|
+
|
33
|
+
def thread_safe=(value)
|
34
|
+
@thread_safe = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def thread_safe?
|
38
|
+
@thread_safe == true
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def _new(name)
|
43
|
+
stats = Stats.new(name,ancestor(name))
|
44
|
+
@registry[name] = stats
|
45
|
+
reassign_children(stats)
|
46
|
+
stats
|
47
|
+
end
|
48
|
+
|
49
|
+
def reassign_children(node)
|
50
|
+
@registry.values.each do |other_node|
|
51
|
+
if other_node != node && other_node.name.index(node.name) == 0
|
52
|
+
other_node.parent = node
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def ancestor(name)
|
58
|
+
ancestor = nil
|
59
|
+
path = name.split(DELIMITER)
|
60
|
+
while path.pop && !ancestor
|
61
|
+
ancestor = @registry[path.join(DELIMITER)]
|
62
|
+
end
|
63
|
+
ancestor
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class FilterTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def setup
|
4
|
+
@handler = mock("handler")
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_constructor_block_used_as_filter
|
8
|
+
filter = Camayoc::Handlers::Filter.new(@handler) do |type,event|
|
9
|
+
event.stat.length > 5
|
10
|
+
end
|
11
|
+
@handler.expects(:count).once
|
12
|
+
@handler.expects(:timing).once
|
13
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","short",500))
|
14
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","very_long",500))
|
15
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","short",500))
|
16
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","very_long",500))
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_with_filters_on_namespaced_stat
|
20
|
+
filter = Camayoc::Handlers::Filter.new(@handler,:with=>/a{2}/)
|
21
|
+
@handler.expects(:count).once
|
22
|
+
@handler.expects(:timing).once
|
23
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","aa_blah",500))
|
24
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","bb_blah",500))
|
25
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","aa_blah",500))
|
26
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","bb_blah",500))
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_constructor_block_used_as_filter
|
30
|
+
filter = Camayoc::Handlers::Filter.new(@handler,
|
31
|
+
:if=>Proc.new{|type,event| type == :timing && event.value > 1000 })
|
32
|
+
|
33
|
+
@handler.expects(:count).never
|
34
|
+
@handler.expects(:timing).once
|
35
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","short",500))
|
36
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","short",500))
|
37
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","very_long",1500))
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_constructor_block_used_as_filter
|
41
|
+
filter = Camayoc::Handlers::Filter.new(@handler,
|
42
|
+
:unless=>Proc.new{|type,event| type == :count })
|
43
|
+
|
44
|
+
@handler.expects(:count).never
|
45
|
+
@handler.expects(:timing).twice
|
46
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","short",500))
|
47
|
+
filter.count(Camayoc::StatEvent.new("foo:bar","very_long",500))
|
48
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","short",500))
|
49
|
+
filter.timing(Camayoc::StatEvent.new("foo:bar","very_long",1500))
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class IOTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def setup
|
4
|
+
# Hack to get around weird Mocha issue with expecting :puts
|
5
|
+
@io = []
|
6
|
+
def @io.puts(msg)
|
7
|
+
self << msg
|
8
|
+
end
|
9
|
+
@handler = Camayoc::Handlers::IO.new(@io)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_count_sends_correct_log_message
|
13
|
+
expect_message(/count foo:bar:baz 500/)
|
14
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","baz",500))
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_timing_sends_correct_log_message
|
18
|
+
expect_message(/timing foo:bar:time 100/)
|
19
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",100))
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_formatter_changes_format_of_message
|
23
|
+
@handler.formatter = Proc.new{|type,event| "#{type}: #{event.ns_stat}"}
|
24
|
+
expect_message(/timing: foo:bar:time/)
|
25
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",100))
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def expect_message(*messages)
|
30
|
+
assert(@io.zip(messages).all?{ |actual,patt| actual =~ patt })
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class LoggerTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def setup
|
4
|
+
@dest_logger = mock("logger")
|
5
|
+
@handler = Camayoc::Handlers::Logger.new(@dest_logger)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_count_sends_correct_log_message
|
9
|
+
expect_message(:debug, /count foo:bar:baz 500/)
|
10
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","baz",500))
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_timing_sends_correct_log_message
|
14
|
+
expect_message(:debug,/timing foo:bar:time 100/)
|
15
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",100))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_method_option_changes_method_called_on_logger
|
19
|
+
@handler.method = :info
|
20
|
+
expect_message(:info,/timing foo:bar:time 100/)
|
21
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",100))
|
22
|
+
expect_message(:info, /count foo:bar:baz 5000/)
|
23
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","baz",5000))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_formatter_changes_format_of_message
|
27
|
+
@handler.formatter = Proc.new{|type,event| "#{type}: #{event.ns_stat}"}
|
28
|
+
expect_message(:debug,/timing: foo:bar:time/)
|
29
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",100))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def expect_message(method,message)
|
34
|
+
@dest_logger.expects(method).with(regexp_matches(message))
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class MemoryTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def setup
|
4
|
+
@handler = Camayoc::Handlers::Memory.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_count_initializes_to_0_and_increments_value
|
8
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","count",100))
|
9
|
+
assert_equal(100,@handler["foo:bar:count"])
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_multiple_counts_increments_value
|
13
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","count",100))
|
14
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","count",500))
|
15
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","count",400))
|
16
|
+
assert_equal(1000,@handler["foo:bar:count"])
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_timing_initializes_timing_object_and_adds_data
|
20
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",500))
|
21
|
+
timing = @handler["foo:bar:time"]
|
22
|
+
assert_not_nil(timing)
|
23
|
+
assert_equal(1,timing.count)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_timing_increments_timing_object
|
27
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",100))
|
28
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",600))
|
29
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",1000))
|
30
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",500))
|
31
|
+
timing = @handler["foo:bar:time"]
|
32
|
+
assert_equal(4,timing.count)
|
33
|
+
assert_equal(100,timing.min)
|
34
|
+
assert_equal(1000,timing.max)
|
35
|
+
assert_equal(2200,timing.total)
|
36
|
+
assert_equal(550,timing.average)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
if (begin; require 'camayoc/handlers/redis'; true; rescue LoadError; false end)
|
2
|
+
class RedisTest < Test::Unit::TestCase
|
3
|
+
def setup
|
4
|
+
@redis = mock("redis")
|
5
|
+
@handler = Camayoc::Handlers::Redis.new(:redis=>@redis)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_count_increments_correct_key_in_redis
|
9
|
+
@redis.expects(:incrby).with("foo:bar:baz",500)
|
10
|
+
@handler.count(Camayoc::StatEvent.new("foo:bar","baz",500))
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_timing_updates_redis_keys_with_timing_info
|
14
|
+
seq = sequence("increments")
|
15
|
+
@redis.expects(:set).with("foo:bar:time","timing").times(3)
|
16
|
+
@redis.expects(:incrby).with("foo:bar:time:_count",1).times(3)
|
17
|
+
@redis.expects(:incrby).with("foo:bar:time:_total",500).in_sequence(seq)
|
18
|
+
@redis.expects(:incrby).with("foo:bar:time:_total",400).in_sequence(seq)
|
19
|
+
@redis.expects(:incrby).with("foo:bar:time:_total",200).in_sequence(seq)
|
20
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",500))
|
21
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",400))
|
22
|
+
@handler.timing(Camayoc::StatEvent.new("foo:bar","time",200))
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
else
|
27
|
+
puts "Warn: Skipping Redis test because redis gem is not installed"
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class StatsdTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def setup
|
4
|
+
@statsd = Camayoc::Handlers::Statsd.new(:host=>"localhost",:port=>1234)
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_count_sends_correct_statsd_message
|
8
|
+
expect_message("foo.bar.baz:500|c")
|
9
|
+
@statsd.count(Camayoc::StatEvent.new("foo:bar","baz",500,{}))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_timing_sends_correct_statsd_message
|
13
|
+
expect_message("foo.bar.time:100|ms")
|
14
|
+
@statsd.timing(Camayoc::StatEvent.new("foo:bar","time",100,{}))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def expect_message(message)
|
19
|
+
@statsd.instance_variable_get("@socket").expects(:send).with(message,0,"localhost",1234)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class RegistrationTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def test_accessing_stats_creates_it
|
4
|
+
stats = Camayoc["foo:bar"]
|
5
|
+
assert_equal(Camayoc::Stats,stats.class)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_accessing_stats_returns_same_instance
|
9
|
+
stats1 = Camayoc["foo:bar"]
|
10
|
+
stats2 = Camayoc["foo:bar"]
|
11
|
+
assert_same(stats1,stats2)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_accessing_path_with_no_ancestor_leaves_parent_nil
|
15
|
+
stats1 = Camayoc["foo:bar"]
|
16
|
+
assert_nil(stats1.parent)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_accessing_path_with_already_existing_ancestor_sets_parent
|
20
|
+
parent = Camayoc["foo"]
|
21
|
+
child = Camayoc["foo:bar"]
|
22
|
+
assert_same(parent,child.parent)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_accessing_path_with_intermediate_steps_gets_closest_existing_ancestor_as_parent
|
26
|
+
parent = Camayoc["foo"]
|
27
|
+
child = Camayoc["foo:a:b:c:baz"]
|
28
|
+
assert_same(parent,child.parent)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_accessing_path_in_middle_of_existing_hierarchy_reassociates_children
|
32
|
+
root = Camayoc["foo"]
|
33
|
+
child1 = Camayoc["foo:a:b:c:1"]
|
34
|
+
child2 = Camayoc["foo:a:b:c:2"]
|
35
|
+
middle = Camayoc["foo:a:b"]
|
36
|
+
|
37
|
+
assert_same(root,middle.parent)
|
38
|
+
assert_same(middle,child1.parent)
|
39
|
+
assert_same(middle,child2.parent)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_all_lists_all_existing_instances
|
43
|
+
root = Camayoc["foo"]
|
44
|
+
child1 = Camayoc["foo:a:b:c:1"]
|
45
|
+
child2 = Camayoc["foo:a:b:c:2"]
|
46
|
+
middle = Camayoc["foo:a:b"]
|
47
|
+
|
48
|
+
all = Camayoc.all
|
49
|
+
all = all.sort_by{|stats| stats.name}
|
50
|
+
|
51
|
+
assert_equal([root,middle,child1,child2],all)
|
52
|
+
end
|
53
|
+
|
54
|
+
def teardown
|
55
|
+
Camayoc.instance_variable_get("@registry").clear
|
56
|
+
end
|
57
|
+
end
|
data/test/stats_test.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
class StatsTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
def setup
|
4
|
+
@stats = Camayoc::Stats.new("foo:bar")
|
5
|
+
@handler = mock("handler1")
|
6
|
+
@stats.add(@handler)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_count_fires_to_all_handlers
|
10
|
+
h2 = mock("handler2")
|
11
|
+
@stats.add(h2)
|
12
|
+
|
13
|
+
@handler.expects(:count).with(kind_of(Camayoc::StatEvent))
|
14
|
+
h2.expects(:count).with(kind_of(Camayoc::StatEvent))
|
15
|
+
@stats.count("count",500)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_count_generates_correct_stat_event
|
19
|
+
@handler.expects(:count).with(
|
20
|
+
&stat_event_match("foo:bar","count",500,{:pass_through=>true}))
|
21
|
+
@stats.count("count",500,:pass_through=>true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_count_propagates_event_to_parent_after_firing_to_handlers
|
25
|
+
@stats.parent = Camayoc::Stats.new("foo")
|
26
|
+
|
27
|
+
seq = sequence("firing")
|
28
|
+
evt = stat_event_match("foo:bar","count",100,{:pass_through=>true})
|
29
|
+
@handler.expects(:count).with(&evt).in_sequence(seq)
|
30
|
+
@stats.parent.expects(:count_event).with(&evt).in_sequence(seq)
|
31
|
+
|
32
|
+
@stats.count("count",100,:pass_through=>true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_increment_delegates_to_count
|
36
|
+
@stats.expects(:count).with("count",1,{})
|
37
|
+
@stats.increment("count")
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_decrement_delegates_to_count
|
41
|
+
@stats.expects(:count).with("count",-1,{})
|
42
|
+
@stats.decrement("count")
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_timing_fires_to_all_handlers
|
46
|
+
h2 = mock("handler2")
|
47
|
+
@stats.add(h2)
|
48
|
+
|
49
|
+
@handler.expects(:timing).with(kind_of(Camayoc::StatEvent))
|
50
|
+
h2.expects(:timing).with(kind_of(Camayoc::StatEvent))
|
51
|
+
@stats.timing("time",500)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_timing_generates_correct_stat_event
|
55
|
+
@handler.expects(:timing).with(
|
56
|
+
&stat_event_match("foo:bar","time",1,{:pass_through=>true}))
|
57
|
+
@stats.timing("time",1,:pass_through=>true)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_timing_propagates_event_to_parent_after_firing_to_handlers
|
61
|
+
@stats.parent = Camayoc::Stats.new("foo")
|
62
|
+
|
63
|
+
seq = sequence("firing")
|
64
|
+
evt = stat_event_match("foo:bar","time",100,{:pass_through=>true})
|
65
|
+
@handler.expects(:timing).with(&evt).in_sequence(seq)
|
66
|
+
@stats.parent.expects(:timing_event).with(&evt).in_sequence(seq)
|
67
|
+
|
68
|
+
@stats.timing("time",100,:pass_through=>true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_handler_errors_are_swallowed_and_firing_continues
|
72
|
+
h2 = mock("handler2")
|
73
|
+
@stats.add(h2)
|
74
|
+
|
75
|
+
seq = sequence("firing")
|
76
|
+
@handler.expects(:count).raises("FAIL").in_sequence(seq)
|
77
|
+
h2.expects(:count).in_sequence(seq)
|
78
|
+
|
79
|
+
assert_nothing_raised do
|
80
|
+
@stats.count("baz",100)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def stat_event_match(*args)
|
86
|
+
template = Camayoc::StatEvent.new(*args)
|
87
|
+
Proc.new do |event|
|
88
|
+
event.source == template.source &&
|
89
|
+
event.stat == template.stat &&
|
90
|
+
event.value == template.value &&
|
91
|
+
event.options == template.options
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
class ThreadSafetyTest < Test::Unit::TestCase
|
2
|
+
|
3
|
+
class SafeThing
|
4
|
+
include Camayoc::ThreadSafety
|
5
|
+
end
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@thing = SafeThing.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_thread_safe_turns_on_lock
|
12
|
+
@thing.thread_safe = true
|
13
|
+
assert_equal(Mutex,@thing.__send__(:lock).class)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_lock_is_placebo_by_default
|
17
|
+
assert_equal(Camayoc::ThreadSafety::PlaceboLock,
|
18
|
+
@thing.__send__(:lock).class)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_synchronize_yields_whether_safe_or_not
|
22
|
+
yielded = false
|
23
|
+
@thing.thread_safe = true
|
24
|
+
@thing.synchronize { yielded = true }
|
25
|
+
assert(yielded)
|
26
|
+
|
27
|
+
yielded = false
|
28
|
+
@thing.thread_safe = false
|
29
|
+
@thing.synchronize {yielded = true}
|
30
|
+
assert(yielded)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: camayoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Hayes Davis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-02 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: mocha
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: " Inspired by logging frameworks, Camayoc makes it easy send stats and metrics \n from your application to collectors like Graphite (via statsd) or Redis. \n Extensible handlers mean you can use whatever storage system you like \n with the same interface.\n"
|
36
|
+
email: hayes@appozite.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- README.md
|
46
|
+
- LICENSE
|
47
|
+
- lib/camayoc/handlers/filter.rb
|
48
|
+
- lib/camayoc/handlers/io.rb
|
49
|
+
- lib/camayoc/handlers/logger.rb
|
50
|
+
- lib/camayoc/handlers/memory.rb
|
51
|
+
- lib/camayoc/handlers/redis.rb
|
52
|
+
- lib/camayoc/handlers/statsd.rb
|
53
|
+
- lib/camayoc/handlers/timing.rb
|
54
|
+
- lib/camayoc/stat_event.rb
|
55
|
+
- lib/camayoc/stats.rb
|
56
|
+
- lib/camayoc/thread_safety.rb
|
57
|
+
- lib/camayoc/version.rb
|
58
|
+
- lib/camayoc.rb
|
59
|
+
- test/handlers/filter_test.rb
|
60
|
+
- test/handlers/io_test.rb
|
61
|
+
- test/handlers/logger_test.rb
|
62
|
+
- test/handlers/memory_test.rb
|
63
|
+
- test/handlers/redis_test.rb
|
64
|
+
- test/handlers/statsd_test.rb
|
65
|
+
- test/registration_test.rb
|
66
|
+
- test/stat_event_test.rb
|
67
|
+
- test/stats_test.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
- test/thread_safety_test.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/appozite/camayoc
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.7
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Camayoc makes it easy to send stats anywhere.
|
104
|
+
test_files: []
|
105
|
+
|