fozzie 0.0.27 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +2 -1
  2. data/Guardfile +2 -2
  3. data/README.md +22 -11
  4. data/Rakefile +1 -1
  5. data/fozzie.gemspec +8 -14
  6. data/lib/core_ext/module/monitor.rb +4 -3
  7. data/lib/fozzie.rb +6 -7
  8. data/lib/fozzie/adapter.rb +1 -0
  9. data/lib/fozzie/adapter/statsd.rb +86 -0
  10. data/lib/fozzie/bulk_dsl.rb +28 -0
  11. data/lib/fozzie/configuration.rb +32 -13
  12. data/lib/fozzie/dsl.rb +19 -0
  13. data/lib/fozzie/exception.rb +5 -0
  14. data/lib/fozzie/interface.rb +30 -15
  15. data/lib/fozzie/rack/middleware.rb +3 -1
  16. data/lib/fozzie/sniff.rb +28 -33
  17. data/lib/fozzie/version.rb +1 -1
  18. data/spec/config/fozzie.yml +2 -1
  19. data/spec/lib/core_ext/module/monitor_spec.rb +9 -0
  20. data/spec/lib/fozzie/adapter/statsd_spec.rb +82 -0
  21. data/spec/lib/fozzie/bulk_dsl_spec.rb +47 -0
  22. data/spec/lib/fozzie/configuration_spec.rb +34 -20
  23. data/spec/lib/fozzie/dsl_spec.rb +16 -0
  24. data/spec/lib/fozzie/rack/middleware_spec.rb +6 -36
  25. data/spec/lib/fozzie/rack/sinatra_spec.rb +31 -0
  26. data/spec/lib/fozzie/sniff_spec.rb +37 -37
  27. data/spec/lib/fozzie/version_spec.rb +1 -1
  28. data/spec/lib/fozzie_spec.rb +19 -3
  29. data/spec/shared_examples/fozzie_adapter.rb +7 -0
  30. data/spec/shared_examples/interface.rb +160 -0
  31. data/spec/spec_helper.rb +20 -10
  32. metadata +31 -74
  33. data/lib/core_ext/hash.rb +0 -16
  34. data/lib/fozzie/mill.rb +0 -50
  35. data/lib/fozzie/rails/engine.rb +0 -15
  36. data/lib/fozzie/rails/middleware.rb +0 -39
  37. data/lib/fozzie/railtie.rb +0 -15
  38. data/lib/fozzie/socket.rb +0 -53
  39. data/spec/lib/core_ext/hash_spec.rb +0 -33
  40. data/spec/lib/fozzie/interface_spec.rb +0 -180
  41. data/spec/lib/fozzie/mill_spec.rb +0 -43
  42. data/spec/lib/fozzie/rails/middleware_spec.rb +0 -125
data/.gitignore CHANGED
@@ -2,4 +2,5 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
- coverage/*
5
+ coverage/*
6
+ .DS_Store
data/Guardfile CHANGED
@@ -1,8 +1,8 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard 'rspec', :version => 2 do
4
+ guard 'rspec', :cli => "--color" do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
7
  watch('spec/spec_helper.rb') { "spec" }
8
- end
8
+ end
data/README.md CHANGED
@@ -109,12 +109,31 @@ class FooBar
109
109
  # my code here...
110
110
  end
111
111
 
112
+ _monitor("my.awesome.bucket.name")
113
+ def quux
114
+ # something
115
+ end
116
+
112
117
  end
113
118
  ```
114
119
  This will register the processing time for this method, everytime it is called, under the Graphite bucket `foo_bar.zar`.
115
120
 
116
121
  This will work on both Class and Instance methods.
117
122
 
123
+ ## Bulk
124
+
125
+ You can send a bulk of metrics using the `bulk` method:
126
+ ``` ruby
127
+ Stats.bulk do
128
+ increment 'wat'
129
+ decrement 'wot'
130
+ gauge 'foo', rand
131
+ time_to_do 'wat_timer' { sleep 4 }
132
+ end
133
+ ```
134
+
135
+ This will send all the given metrics in a single packet to the statistics server.
136
+
118
137
  ## Namespaces
119
138
 
120
139
  Fozzie supports the following namespaces as default
@@ -185,13 +204,13 @@ Prefixes are cached on first use, therefore any changes to the Fozzie configure
185
204
 
186
205
  ## Middleware
187
206
 
188
- To time and register the controller actions within your Rails application, Fozzie provides some middleware.
207
+ To time and register the controller actions within your Rack and Rails application, Fozzie provides some middleware.
189
208
 
190
209
  ### Rack
191
210
 
192
211
  ``` ruby
193
212
  require 'rack'
194
- require 'fozzie'
213
+ require 'fozzie/rack/middleware'
195
214
 
196
215
  app = Rack::Builder.new {
197
216
  use Fozzie::Rack::Middleware
@@ -201,9 +220,7 @@ app = Rack::Builder.new {
201
220
 
202
221
  ### Rails
203
222
 
204
- Based on the Rack middleware above, but is more involved in its construction of the bucket value.
205
-
206
- Fozzie::Rails::Middleware will automatically be invoked on Rails initialization.
223
+ See [Fozzie Rails](http://github.com/lonelyplanet/fozzie_rails).
207
224
 
208
225
  ## Bucket name prefixes
209
226
 
@@ -242,18 +259,12 @@ Fozzie.logger = Logger.new 'log/fozzie.log'
242
259
 
243
260
  This may change, depending on feedback and more production experience.
244
261
 
245
- ## Rails User Interface Performance Measuring
246
-
247
- If you also require UI metrics, you can also include the Mill script in the bottom of any page you would like to measure (see `resources/mill.js` and `resources/mill.min.js`) and you start receiving measurements on page performance.
248
-
249
262
  ## Credits
250
263
 
251
264
  Currently supported and maintained by [Marc Watts](marc.watts@lonelyplanet.co.uk) @ Lonely Planet Online.
252
265
 
253
266
  Big thanks and Credits:
254
267
 
255
- * [Mark Barger](mark.barger@lonelyplanet.co.uk) for support in trying to make this Gem useful.
256
-
257
268
  * [Dave Nolan](https://github.com/textgoeshere)
258
269
 
259
270
  * [Etsy](http://codeascraft.etsy.com/) whose [Statsd](https://github.com/etsy/statsd) product has enabled us to come such a long way in a very short period of time. We love Etsy.
data/Rakefile CHANGED
@@ -4,4 +4,4 @@ Bundler::GemHelper.install_tasks
4
4
  require 'rspec/core/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
 
7
- task :default => :spec
7
+ task :default => :spec
@@ -5,18 +5,18 @@ require "fozzie/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "fozzie"
7
7
  s.version = Fozzie::VERSION
8
- s.authors = ["Marc Watts", "Dave Nolan"]
8
+ s.authors = ["Marc Watts"]
9
9
  s.email = ["marc.watts@lonelyplanet.co.uk"]
10
- s.summary = %q{Statsd Ruby gem from Lonely Planet Online}
10
+ s.summary = %q{Ruby gem from Lonely Planet Online to register statistics. Currently supports Statsd.}
11
11
  s.description = %q{
12
- Gem to make statistics sending to Statsd from Ruby applications simple and efficient as possible.
13
- Inspired by the original ruby-statsd gem by Etsy, currently used by Lonely Planet Online.
12
+ Gem to make statistics sending from Ruby applications simple and efficient as possible.
13
+ Currently supports Statsd, and is inspired by the original ruby-statsd gem by Etsy.
14
14
  }
15
15
 
16
16
  s.rubyforge_project = "fozzie"
17
17
 
18
18
  s.files = `git ls-files`.split("\n")
19
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
20
20
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
21
  s.require_paths = ["lib"]
22
22
 
@@ -25,15 +25,9 @@ Gem::Specification.new do |s|
25
25
 
26
26
  s.add_development_dependency 'rake'
27
27
  s.add_development_dependency 'rspec'
28
- s.add_development_dependency 'mocha'
29
- s.add_development_dependency 'syntax'
30
- s.add_development_dependency 'simplecov'
31
-
32
- s.add_development_dependency 'sinatra'
33
- s.add_development_dependency 'rack-test'
34
- s.add_development_dependency 'actionpack', '2.3.14'
35
-
36
28
  s.add_development_dependency 'guard'
37
29
  s.add_development_dependency 'guard-rspec'
38
-
30
+ s.add_development_dependency 'rb-fsevent'
31
+ s.add_development_dependency 'sinatra'
32
+ s.add_development_dependency 'rack-test'
39
33
  end
@@ -2,10 +2,11 @@ require 'fozzie/sniff'
2
2
 
3
3
  class Module
4
4
 
5
- def _monitor
5
+ def _monitor(bucket_name = nil)
6
6
  return unless Fozzie.c.sniff?
7
- self.class_eval { include Fozzie::Sniff }
7
+ self.class_eval { extend Fozzie::Sniff }
8
8
  @_monitor_flag = true
9
+ @_bucket_name = bucket_name
9
10
  end
10
11
 
11
- end
12
+ end
@@ -11,15 +11,14 @@ module Fozzie
11
11
 
12
12
  require 'core_ext/module/monitor'
13
13
 
14
+ require 'fozzie/adapter'
15
+
16
+ require "fozzie/exception"
14
17
  require 'fozzie/configuration'
15
- require "fozzie/interface"
18
+ require "fozzie/dsl"
19
+ require "fozzie/bulk_dsl"
16
20
  require "fozzie/version"
17
21
 
18
- require "fozzie/rack/middleware"
19
- require "fozzie/rails/middleware"
20
-
21
- require 'fozzie/railtie' if defined?(::Rails)
22
-
23
22
  class << self
24
23
 
25
24
  # Shortcut for `Fozzie.config`
@@ -58,7 +57,7 @@ module Fozzie
58
57
 
59
58
  # Loads each namespace for registering statistics
60
59
  self.c.namespaces.each do |klas|
61
- Kernel.const_set(klas, Interface.instance) unless const_defined?(klas)
60
+ Kernel.const_set(klas, Dsl.instance) unless const_defined?(klas)
62
61
  end
63
62
 
64
63
  end
@@ -0,0 +1 @@
1
+ %w{statsd}.each {|r| require "fozzie/adapter/#{r}" }
@@ -0,0 +1,86 @@
1
+ require 'socket'
2
+
3
+ module Fozzie
4
+ module Adapter
5
+
6
+ class Statsd
7
+
8
+ RESERVED_CHARS_REGEX = /[\:\|\@\s]/
9
+ RESERVED_CHARS_REPLACEMENT = '_'
10
+ DELIMETER = '.'
11
+ SAFE_SEPARATOR = '-'
12
+ TYPES = { :gauge => 'g', :count => 'c', :timing => 'ms' }
13
+ BULK_DELIMETER = "\n"
14
+
15
+ # Send the statistic to the server
16
+ #
17
+ # Creates the Statsd key from the given values, and sends to socket (depending on sample rate)
18
+ def register(*stats)
19
+ metrics = stats.flatten.collect do |stat|
20
+ next if sampled?(stat[:sample_rate])
21
+
22
+ bucket = format_bucket(stat[:bin])
23
+ value = format_value(stat[:value], stat[:type], stat[:sample_rate])
24
+
25
+ [bucket, value].join(':')
26
+ end.compact.join(BULK_DELIMETER)
27
+
28
+ send_to_socket(metrics)
29
+ end
30
+
31
+ def format_bucket(stat)
32
+ bucket = [stat].flatten.compact.collect(&:to_s).join(DELIMETER).downcase
33
+ bucket = bucket.gsub('::', DELIMETER).gsub(RESERVED_CHARS_REGEX, RESERVED_CHARS_REPLACEMENT)
34
+ bucket = [Fozzie.c.data_prefix, bucket].compact.join(DELIMETER)
35
+
36
+ bucket
37
+ end
38
+
39
+ def format_value(val, type, sample_rate)
40
+ converted_type = TYPES[type.to_sym]
41
+ converted_type ||= TYPES[:gauge]
42
+
43
+ value = [val, converted_type].join('|')
44
+ value << '@%s' % sample_rate.to_s if sample_rate < 1
45
+
46
+ value
47
+ end
48
+
49
+ # If the statistic is sampled, generate a condition to check if it's good to send
50
+ def sampled(sample_rate)
51
+ yield unless sampled?(sample_rate)
52
+ end
53
+
54
+ def sampled?(sample_rate)
55
+ sample_rate < 1 and rand > sample_rate
56
+ end
57
+
58
+ # Send data to the server via the socket
59
+ def send_to_socket(message)
60
+ Fozzie.logger.debug {"Statsd: #{message}"} if Fozzie.logger
61
+ Timeout.timeout(Fozzie.c.timeout) {
62
+ res = socket.send(message, 0, Fozzie.c.host, Fozzie.c.port)
63
+ Fozzie.logger.debug {"Statsd sent: #{res}"} if Fozzie.logger
64
+ (res.to_i == message.length)
65
+ }
66
+ rescue => exc
67
+ Fozzie.logger.debug {"Statsd Failure: #{exc.message}\n#{exc.backtrace}"} if Fozzie.logger
68
+ false
69
+ end
70
+
71
+ # The Socket we want to use to send data
72
+ def socket
73
+ @socket ||= ::UDPSocket.new
74
+ end
75
+
76
+ def delimeter
77
+ DELIMETER
78
+ end
79
+
80
+ def safe_separator
81
+ SAFE_SEPARATOR
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,28 @@
1
+ module Fozzie
2
+ class BulkDsl
3
+ include Fozzie::Interface
4
+
5
+ def initialize(&block)
6
+ @metrics = []
7
+ block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
8
+ send_bulk
9
+ end
10
+
11
+ private
12
+
13
+ # Cache the requested metrics for bulk sending
14
+ #
15
+ def send(stat, value, type, sample_rate = 1)
16
+ val = { :bin => stat, :value => value, :type => type, :sample_rate => sample_rate }
17
+
18
+ @metrics.push(val)
19
+ end
20
+
21
+ def send_bulk
22
+ return if @metrics.empty?
23
+
24
+ adapter.register(@metrics)
25
+ end
26
+
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  require 'yaml'
2
- require 'core_ext/hash'
2
+ require 'facets/hash/symbolize_keys'
3
3
  require 'sys/uname'
4
4
  require 'timeout'
5
5
 
@@ -7,16 +7,32 @@ module Fozzie
7
7
 
8
8
  # Fozzie configuration allows assignment of global properties
9
9
  # that will be used within the Fozzie codebase.
10
+
10
11
  class Configuration
11
12
  include Sys
13
+ extend Forwardable
14
+
15
+ def_delegators :adapter, :delimeter, :safe_separator
12
16
 
13
- attr_accessor :env, :config_path, :host, :port, :appname, :namespaces, :timeout, :monitor_classes, :sniff_envs, :ignore_prefix, :prefix
17
+ attr_accessor :env, :config_path, :host, :port, :appname, :namespaces,
18
+ :timeout, :monitor_classes, :sniff_envs, :ignore_prefix, :prefix
14
19
 
15
20
  def initialize(args = {})
16
21
  merge_and_assign_config(args)
22
+ self.adapter
17
23
  self.origin_name
18
24
  end
19
25
 
26
+ def adapter=(adapter)
27
+ @adapter = eval("Fozzie::Adapter::#{adapter}").new
28
+ rescue NoMethodError
29
+ raise AdapterMissing, "Adapter could not be found for given provider #{@provider}"
30
+ end
31
+
32
+ def adapter
33
+ @adapter || default_configuration[:adapter]
34
+ end
35
+
20
36
  def disable_prefix
21
37
  @ignore_prefix = true
22
38
  end
@@ -26,15 +42,17 @@ module Fozzie
26
42
  return nil if @ignore_prefix
27
43
  return @data_prefix if @data_prefix
28
44
 
29
- data_prefix = @prefix.collect do |me|
30
- (me.kind_of?(Symbol) && self.respond_to?(me.to_sym) ? self.send(me) : me.to_s)
31
- end
32
-
33
- data_prefix = data_prefix.collect do |s|
34
- s.empty? ? nil : s.gsub(Socket::DELIMETER, '-')
35
- end.compact.join(Socket::DELIMETER).strip
36
-
37
- @data_prefix ||= (data_prefix.empty? ? nil : data_prefix)
45
+ escaped_prefix_with_dynamically_resolved_parts = prefix.map do |part|
46
+ resolved_part = (part.kind_of?(Symbol) && self.respond_to?(part) ? self.send(part) : part.to_s)
47
+ escaped_resolved_part = resolved_part.gsub(delimeter, safe_separator)
48
+ escaped_resolved_part == "" ? nil : escaped_resolved_part
49
+ end.compact
50
+
51
+ @data_prefix = if escaped_prefix_with_dynamically_resolved_parts.any?
52
+ escaped_prefix_with_dynamically_resolved_parts.join(delimeter).strip
53
+ else
54
+ nil
55
+ end
38
56
  end
39
57
 
40
58
  # Returns the origin name of the current machine to register the stat against
@@ -70,7 +88,8 @@ module Fozzie
70
88
  :timeout => 0.5,
71
89
  :monitor_classes => [],
72
90
  :sniff_envs => [:development, :staging, :production],
73
- :ignore_prefix => false
91
+ :ignore_prefix => false,
92
+ :adapter => :Statsd
74
93
  }.dup
75
94
  end
76
95
 
@@ -89,4 +108,4 @@ module Fozzie
89
108
 
90
109
  end
91
110
 
92
- end
111
+ end
@@ -0,0 +1,19 @@
1
+ require 'singleton'
2
+ require "fozzie/interface"
3
+
4
+ module Fozzie
5
+ class Dsl
6
+ include Fozzie::Interface, Singleton
7
+
8
+ private
9
+
10
+ # Send the statistic to the chosen provider
11
+ #
12
+ def send(stat, value, type, sample_rate = 1)
13
+ val = { :bin => stat, :value => value, :type => type, :sample_rate => sample_rate }
14
+
15
+ adapter.register(val)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module Fozzie
2
+
3
+ class AdapterMissing < StandardError; end
4
+
5
+ end
@@ -1,9 +1,7 @@
1
- require 'singleton'
2
- require 'fozzie/socket'
1
+ require 'fozzie/adapter/statsd'
3
2
 
4
3
  module Fozzie
5
- class Interface
6
- include Fozzie::Socket, Singleton
4
+ module Interface
7
5
 
8
6
  # Increments the given stat by one, with an optional sample rate
9
7
  #
@@ -23,14 +21,14 @@ module Fozzie
23
21
  #
24
22
  # `Stats.count 'wat', 500`
25
23
  def count(stat, count, sample_rate=1)
26
- send(stat, count, 'c', sample_rate)
24
+ send(stat, count, :count, sample_rate)
27
25
  end
28
26
 
29
27
  # Registers a timing (in ms) for the given stat, with an optional sample rate
30
28
  #
31
29
  # `Stats.timing 'wat', 500`
32
30
  def timing(stat, ms, sample_rate=1)
33
- send(stat, ms, 'ms', sample_rate)
31
+ send(stat, ms, :timing, sample_rate)
34
32
  end
35
33
 
36
34
  # Registers the time taken to complete a given block (in ms), with an optional sample rate
@@ -47,7 +45,7 @@ module Fozzie
47
45
  #
48
46
  # `Stats.time_to_do 'wat' { # Do something, again... }`
49
47
  def time_to_do(stat, sample_rate=1, &block)
50
- time_for(stat, sample_rate, &block)
48
+ time(stat, sample_rate, &block)
51
49
  end
52
50
 
53
51
  # Registers the time taken to complete a given block (in ms), with an optional sample rate
@@ -99,6 +97,13 @@ module Fozzie
99
97
  deployed(app)
100
98
  end
101
99
 
100
+ # Register an event of any type
101
+ #
102
+ # `Stats.event 'wat', 'app'`
103
+ def event(type, app = nil)
104
+ gauge ["event", type.to_s, app], Time.now.usec
105
+ end
106
+
102
107
  # Registers an increment on the result of the given boolean
103
108
  #
104
109
  # `Stats.increment_on 'wat', wat.random?`
@@ -108,18 +113,28 @@ module Fozzie
108
113
  perf
109
114
  end
110
115
 
111
- # Register an event of any type
112
- #
113
- # `Stats.event 'wat', 'app'`
114
- def event(type, app = nil)
115
- gauge ["event", type.to_s, app], Time.now.usec
116
- end
117
-
118
116
  # Register an arbitrary value
119
117
  #
120
118
  # `Stats.gauge 'wat', 'app'`
121
119
  def gauge(stat, value, sample_rate = 1)
122
- send(stat, value, "g", sample_rate)
120
+ send(stat, value, :gauge, sample_rate)
123
121
  end
122
+
123
+ # Register multiple statistics in a single call
124
+ #
125
+ # `Stats.bulk do
126
+ # increment 'wat'
127
+ # decrement 'wot'
128
+ # end`
129
+ def bulk(&block)
130
+ Fozzie::BulkDsl.new(&block)
131
+ end
132
+
133
+ private
134
+
135
+ def adapter
136
+ Fozzie.c.adapter
137
+ end
138
+
124
139
  end
125
140
  end