harness 0.1.2 → 0.2.14

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.
Files changed (36) hide show
  1. data/README.md +72 -3
  2. data/harness.gemspec +2 -0
  3. data/lib/harness/adapters/librato_adapter.rb +15 -2
  4. data/lib/harness/consumer.rb +0 -12
  5. data/lib/harness/counter.rb +5 -1
  6. data/lib/harness/gauge.rb +1 -1
  7. data/lib/harness/instrumentation.rb +4 -2
  8. data/lib/harness/integration/action_controller.rb +6 -6
  9. data/lib/harness/integration/action_mailer.rb +6 -7
  10. data/lib/harness/integration/action_view.rb +6 -7
  11. data/lib/harness/integration/active_support.rb +6 -6
  12. data/lib/harness/integration/sidekiq.rb +47 -0
  13. data/lib/harness/job.rb +23 -0
  14. data/lib/harness/measurement.rb +21 -0
  15. data/lib/harness/meter.rb +55 -0
  16. data/lib/harness/queues/resque_queue.rb +29 -0
  17. data/lib/harness/queues/sidekiq_queue.rb +29 -0
  18. data/lib/harness/queues/syncronous_queue.rb +13 -0
  19. data/lib/harness/railtie.rb +22 -4
  20. data/lib/harness/version.rb +1 -1
  21. data/lib/harness.rb +23 -28
  22. data/test/integration/counters_with_redis_test.rb +22 -3
  23. data/test/integration/instrumentation_test.rb +22 -0
  24. data/test/integration/integrations/action_view_test.rb +13 -3
  25. data/test/integration/integrations/sidekiq_test.rb +54 -0
  26. data/test/integration/meter_test.rb +32 -0
  27. data/test/integration/queues/resque_test.rb +24 -0
  28. data/test/integration/queues/sidekiq_test.rb +34 -0
  29. data/test/test_helper.rb +13 -4
  30. data/test/unit/adapters/librato_adapter_test.rb +7 -14
  31. data/test/unit/counter_test.rb +10 -0
  32. data/test/unit/gauge_test.rb +10 -0
  33. data/test/unit/harness_test.rb +12 -0
  34. data/test/unit/measurement_test.rb +33 -0
  35. data/test/unit/meter_test.rb +0 -0
  36. metadata +50 -12
data/README.md CHANGED
@@ -8,6 +8,39 @@ Currently Supported Services:
8
8
 
9
9
  * Librato
10
10
 
11
+ Current Features:
12
+
13
+ * Track counters over time (# of registered users)
14
+ * Read time specific values (# time to cache something)
15
+ * Build meters on top of counters (# requests per second)
16
+ * Sidekiq integration
17
+ * Resque integration
18
+ * Rails integration
19
+ * Capture and log all measurements coming out of Rails
20
+
21
+ **Crash Course**
22
+
23
+ ```ruby
24
+ class ComplicatedClass
25
+ def hard_work
26
+ # Automatically track how long each of these calls takes so they can
27
+ # tracked and compared over time.
28
+ ActiveSupport::Notifications.instrument "hard_work", :gauge => true do
29
+ # do hard_work
30
+ end
31
+ end
32
+
33
+ def register_user
34
+ # Automatically track the total # of registered users you have.
35
+ # As well, as be able to take measurements of users created in a
36
+ # specific interval
37
+ ActiveSupport::Notifications.instrument "register_user", :counter => true do
38
+ # register_user
39
+ end
40
+ end
41
+ end
42
+ ```
43
+
11
44
  ## Installation
12
45
 
13
46
  Add this line to your application's Gemfile:
@@ -28,7 +61,8 @@ In the metrics world there are two types of things: Gauges and Counters.
28
61
  Gauges are time senstive and represent something at a specific point in
29
62
  time. Counters keep track of things and should be increasing. Counters
30
63
  can be reset back to zero. You can combine counters and/or gauges to
31
- correlate data about your application.
64
+ correlate data about your application. Meters monitor counters. They
65
+ allow you look at rates of counters (read: counters per second).
32
66
 
33
67
  Harness makes this process easily. Harness' primary goal it make it dead
34
68
  simple to start measuring different parts of your application.
@@ -78,7 +112,30 @@ for that gauge or counter.
78
112
  Harness will do all the extra work in sending these metrics to whatever
79
113
  service you're using.
80
114
 
81
- ## Customzing
115
+ Once you the counters are you are instrumented, then you can meter them.
116
+ Meters allow you take arbitary readings of counter rates. The results
117
+ return a gauge so they can be logged as well.
118
+
119
+ ```ruby
120
+ # Define a counter
121
+ class MyClass
122
+ def important_method(stuff)
123
+ ActiveSupport::Notifications.instrument "important_method.my_class", :counter => true do
124
+ do_important_stuff
125
+ end
126
+ end
127
+ end
128
+
129
+ # Now you can meter it
130
+ meter = Harnes::Meter.new('important_method.my_class')
131
+ meter.per_second # returns a gauge
132
+ meter.per_second.value # if you just want the number
133
+ meter.per(1.hour).value # You can use your own interval
134
+ meter.per_minute
135
+ meter.per_hour
136
+ ```
137
+
138
+ ## Customizing
82
139
 
83
140
  You can pash a hash to `:counter` or `:gauge` to initialize the
84
141
  measurement your own way.
@@ -157,7 +214,7 @@ default values are used. You can customize this in an initializer:
157
214
  require 'erb'
158
215
 
159
216
  file = Rails.root.join 'config', 'resque.yml'
160
- config = YAML.load(ERB.new(File.read(Rails.root.join('config', 'resque.yml'))).result)
217
+ config = YAML.load(ERB.new(File.read(Rails.root.join('config', 'redis.yml'))).result)
161
218
 
162
219
  Harness.redis = Redis.new(:url => config[Rails.env])
163
220
  ```
@@ -170,6 +227,18 @@ Measurements are completely ignored in the test env. They are processed
170
227
  in development mode, but not sent to the external service. Everything is
171
228
  logged in production.
172
229
 
230
+ ### Background Processing
231
+
232
+ Harness integrates automatically with Resque or Sidekiq. This is because
233
+ reporting measurements can take time and add unncessary overhead to the
234
+ response time. If neither of these libraries are present, measurements
235
+ **will be posted in realtime.** You can set your own queue by
236
+ specifiying a class like so:
237
+
238
+ ```ruby
239
+ Harness.config.queue = MyCustomQueue
240
+ ```
241
+
173
242
  ## Contributing
174
243
 
175
244
  1. Fork it
data/harness.gemspec CHANGED
@@ -21,4 +21,6 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_development_dependency "simplecov"
23
23
  gem.add_development_dependency "webmock"
24
+ gem.add_development_dependency "resque"
25
+ gem.add_development_dependency "sidekiq"
24
26
  end
@@ -55,14 +55,27 @@ module Harness
55
55
 
56
56
  response = http.request request
57
57
 
58
- raise Harness::LoggingError, response.body unless response.code.to_i == 200
58
+ unless response.code.to_i == 200
59
+ text = %Q{
60
+ Server Said: #{response.body}
61
+ Sent: #{params.inspect}
62
+ }
63
+
64
+ raise Harness::LoggingError, text
65
+ end
59
66
  end
60
67
 
61
68
  true
62
69
  end
63
70
 
64
71
  def self.sanitize(name)
65
- name.gsub(/\./, '-')
72
+ if Harness.config.namespace
73
+ key = "#{name}.#{Harness.config.namespace}"
74
+ else
75
+ key = name
76
+ end
77
+
78
+ key.gsub('/', '.')
66
79
  end
67
80
  end
68
81
  end
@@ -4,18 +4,6 @@ module Harness
4
4
  Thread.new do
5
5
  while measurement = queue.pop
6
6
  begin
7
- logger.debug "[Harness] Processing Measurement: #{measurement.inspect}"
8
-
9
- case measurement.class.to_s.demodulize.underscore.to_sym
10
- when :gauge
11
- adapter.log_gauge measurement
12
- when :counter
13
- adapter.log_counter measurement
14
- end
15
- rescue LoggingError => ex
16
- logger.debug "[Harness] Logging measurement failed! Server Said: #{ex}"
17
- logger.debug ex.backtrace.join("\n")
18
- logger.warn "[Harness] Could not post measurement! Enable debug logging to see full errors"
19
7
  ensure
20
8
  mutex.synchronize { @finished = queue.empty? }
21
9
  end
@@ -1,9 +1,11 @@
1
+ require 'securerandom'
2
+
1
3
  module Harness
2
4
  class Counter < Measurement
3
5
  def self.from_event(event)
4
6
  if event.payload[:counter].is_a? Hash
5
7
  counter = new event.payload[:counter]
6
- elsif event.payload[:counter].is_a? Symbol
8
+ elsif event.payload[:counter].is_a?(Symbol) || event.payload[:counter].is_a?(String)
7
9
  counter = new :id => event.payload[:counter].to_s
8
10
  else
9
11
  counter = new
@@ -23,6 +25,8 @@ module Harness
23
25
  counter.value = Harness.redis.incr(counter.id).to_i
24
26
  end
25
27
 
28
+ Harness.redis.zadd "meters/#{counter.id}", counter.time.to_f, counter.value
29
+
26
30
  counter
27
31
  end
28
32
  end
data/lib/harness/gauge.rb CHANGED
@@ -8,7 +8,7 @@ module Harness
8
8
  def self.from_event(event)
9
9
  if event.payload[:gauge].is_a? Hash
10
10
  gauge = new event.payload[:gauge]
11
- elsif event.payload[:gauge].is_a? Symbol
11
+ elsif event.payload[:gauge].is_a?(Symbol) || event.payload[:gauge].is_a?(String)
12
12
  gauge = new :id => event.payload[:gauge].to_s
13
13
  else
14
14
  gauge = new
@@ -1,6 +1,8 @@
1
1
  ActiveSupport::Notifications.subscribe %r{.+} do |*args|
2
2
  event = ActiveSupport::Notifications::Event.new(*args)
3
3
 
4
- Harness::Gauge.from_event(event).log if event.payload[:gauge]
5
- Harness::Counter.from_event(event).log if event.payload[:counter]
4
+ unless event.payload[:exception]
5
+ Harness::Gauge.from_event(event).log if event.payload[:gauge]
6
+ Harness::Counter.from_event(event).log if event.payload[:counter]
7
+ end
6
8
  end
@@ -1,9 +1,9 @@
1
1
  events = %w(write_fragment read_fragment expire_fragment write_page expire_page process_action send_file)
2
2
 
3
- regex = %r{#{events.join("|")}.action_controller}
4
-
5
- ActiveSupport::Notifications.subscribe regex do |*args|
6
- event = ActiveSupport::Notifications::Event.new(*args)
7
- gauge = Harness::Gauge.from_event event
8
- gauge.log
3
+ events.each do |name|
4
+ ActiveSupport::Notifications.subscribe "#{name}.action_controller" do |*args|
5
+ event = ActiveSupport::Notifications::Event.new(*args)
6
+ gauge = Harness::Gauge.from_event event
7
+ gauge.log
8
+ end
9
9
  end
@@ -1,10 +1,9 @@
1
1
  events = %w(receive deliver)
2
2
 
3
- regex = %r{#{events.join("|")}.action_mailer}
4
-
5
- ActiveSupport::Notifications.subscribe regex do |*args|
6
- event = ActiveSupport::Notifications::Event.new(*args)
7
- gauge = Harness::Gauge.from_event event
8
- gauge.log
3
+ events.each do |name|
4
+ ActiveSupport::Notifications.subscribe "#{name}.action_mailer" do |*args|
5
+ event = ActiveSupport::Notifications::Event.new(*args)
6
+ gauge = Harness::Gauge.from_event event
7
+ gauge.log
8
+ end
9
9
  end
10
-
@@ -1,10 +1,9 @@
1
1
  events = %w(render_template render_partial)
2
2
 
3
- regex = %r{#{events.join("|")}.action_view}
4
-
5
- ActiveSupport::Notifications.subscribe regex do |*args|
6
- event = ActiveSupport::Notifications::Event.new(*args)
7
- gauge = Harness::Gauge.from_event event
8
- gauge.log
3
+ events.each do |name|
4
+ ActiveSupport::Notifications.subscribe "#{name}.action_view" do |*args|
5
+ event = ActiveSupport::Notifications::Event.new(*args)
6
+ gauge = Harness::Gauge.from_event event
7
+ gauge.log
8
+ end
9
9
  end
10
-
@@ -1,9 +1,9 @@
1
1
  events = %w(cache_read cache_generate cache_fetch_hit cache_write cache_delete)
2
2
 
3
- regex = %r{#{events.join("|")}.active_support}
4
-
5
- ActiveSupport::Notifications.subscribe regex do |*args|
6
- event = ActiveSupport::Notifications::Event.new(*args)
7
- gauge = Harness::Gauge.from_event event
8
- gauge.log
3
+ events.each do |name|
4
+ ActiveSupport::Notifications.subscribe "#{name}.active_support" do |*args|
5
+ event = ActiveSupport::Notifications::Event.new(*args)
6
+ gauge = Harness::Gauge.from_event event
7
+ gauge.log
8
+ end
9
9
  end
@@ -0,0 +1,47 @@
1
+ module Sidekiq
2
+ module Middleware
3
+ module Server
4
+ class HarnessInstrumentation
5
+ def call(worker_class, item, queue)
6
+ if instrument? worker_class
7
+ logger.debug "Instrumenting: #{worker_class.inspect}"
8
+
9
+ options = {}
10
+ options[:gauge] = "#{worker_class.class.to_s.underscore}.sidekiq"
11
+ options[:counter] = "#{worker_class.class.to_s.pluralize.underscore}.sidekiq"
12
+
13
+ ActiveSupport::Notifications.instrument "#{worker_class.class.to_s.underscore}.sidekiq", options do
14
+ yield
15
+ end
16
+
17
+ ActiveSupport::Notifications.instrument "job.sidekiq", :counter => true
18
+ else
19
+ logger.debug "Skipped Instrumenting: #{worker_class.inspect}"
20
+ yield
21
+ end
22
+ end
23
+
24
+ private
25
+ def instrument?(worker_class)
26
+ if worker_class.is_a? Harness::SidekiqQueue::SendCounter
27
+ false
28
+ elsif worker_class.is_a? Harness::SidekiqQueue::SendGauge
29
+ false
30
+ else
31
+ worker_class.to_s !~ /^harness/i
32
+ end
33
+ end
34
+
35
+ def logger
36
+ Sidekiq.logger
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ Sidekiq.configure_server do |config|
44
+ config.server_middleware do |chain|
45
+ chain.add Sidekiq::Middleware::Server::HarnessInstrumentation
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ module Harness
2
+ class Job
3
+ def log(measurement)
4
+ logger.debug "[Harness] Processing Measurement: #{measurement.inspect}"
5
+
6
+ case measurement.class.to_s.demodulize.underscore.to_sym
7
+ when :gauge
8
+ adapter.log_gauge measurement
9
+ when :counter
10
+ adapter.log_counter measurement
11
+ end
12
+ end
13
+
14
+ private
15
+ def logger
16
+ Harness.logger
17
+ end
18
+
19
+ def adapter
20
+ Harness.config.adapter
21
+ end
22
+ end
23
+ end
@@ -10,8 +10,29 @@ module Harness
10
10
  self.time ||= Time.now
11
11
  end
12
12
 
13
+ def time=(value)
14
+ if value.is_a? String
15
+ @time = DateTime.parse value
16
+ elsif value.is_a? Fixnum
17
+ @time = Time.at value
18
+ else
19
+ @time = value
20
+ end
21
+ end
22
+
13
23
  def log
14
24
  Harness.log self
15
25
  end
26
+
27
+ def attributes
28
+ {
29
+ :id => id,
30
+ :name => name,
31
+ :source => source,
32
+ :time => time,
33
+ :units => units,
34
+ :value => value
35
+ }
36
+ end
16
37
  end
17
38
  end
@@ -0,0 +1,55 @@
1
+ module Harness
2
+ class Meter
3
+ def initialize(name)
4
+ @name = name
5
+
6
+ raise Harness::NoCounter, "#{@name} is not being metered" unless redis.exists key
7
+ end
8
+
9
+ def per_second
10
+ per 1.second
11
+ end
12
+
13
+ def per_minute
14
+ per 1.minute
15
+ end
16
+
17
+ def per_hour
18
+ per 1.hour
19
+ end
20
+
21
+ def per(rate, base = Time.now)
22
+ gauge = Gauge.new :value => redis.zcount(key, base.to_f - rate, base.to_f)
23
+
24
+ if words = rate_in_words(rate)
25
+ gauge.name = "#{@name} per #{words}"
26
+ gauge.id = "#{@name}-per-#{words}"
27
+ else
28
+ gauge.id = "#{@name} gauge"
29
+ end
30
+
31
+ gauge.time = Time.now
32
+
33
+ gauge
34
+ end
35
+
36
+ private
37
+ def key
38
+ "meters/#{@name}"
39
+ end
40
+
41
+ def redis
42
+ Harness.redis
43
+ end
44
+
45
+ def rate_in_words(rate)
46
+ if rate < 1.minute
47
+ "second"
48
+ elsif rate >= 1.minute && rate < 1.hour
49
+ "minute"
50
+ elsif rate >= 1.hour && rate < 1.day
51
+ "hour"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,29 @@
1
+ module Harness
2
+ class ResqueQueue
3
+ class SendGauge < Job
4
+ @queue = :high
5
+
6
+ def self.perform(attributes)
7
+ gauge = Gauge.new attributes
8
+ new.log gauge
9
+ end
10
+ end
11
+
12
+ class SendCounter < Job
13
+ @queue = :high
14
+
15
+ def self.perform(attributes)
16
+ counter = Counter.new attributes
17
+ new.log counter
18
+ end
19
+ end
20
+
21
+ def self.push(measurement)
22
+ if measurement.is_a? Gauge
23
+ Resque.enqueue SendGauge, measurement.attributes
24
+ elsif measurement.is_a? Counter
25
+ Resque.enqueue SendCounter, measurement.attributes
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module Harness
2
+ class SidekiqQueue
3
+ class SendGauge < Job
4
+ include Sidekiq::Worker
5
+
6
+ def perform(attributes)
7
+ gauge = Gauge.new attributes
8
+ log gauge
9
+ end
10
+ end
11
+
12
+ class SendCounter < Job
13
+ include Sidekiq::Worker
14
+
15
+ def perform(attributes)
16
+ counter = Counter.new attributes
17
+ log counter
18
+ end
19
+ end
20
+
21
+ def self.push(measurement)
22
+ if measurement.is_a? Gauge
23
+ SendGauge.perform_async measurement.attributes
24
+ elsif measurement.is_a? Counter
25
+ SendCounter.perform_async measurement.attributes
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Harness
2
+ class SyncronousQueue
3
+ def self.push(measurement)
4
+ begin
5
+ Harness::Job.new.log(measurement)
6
+ rescue LoggingError => ex
7
+ logger.debug "[Harness] Logging measurement failed! Server Said: #{ex}"
8
+ logger.debug ex.backtrace.join("\n")
9
+ logger.warn "[Harness] Could not post measurement! Enable debug logging to see full errors"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -6,10 +6,6 @@ module Harness
6
6
  load "harness/tasks.rake"
7
7
  end
8
8
 
9
- initializer "harness.thread" do
10
- Thread.abort_on_exception = Rails.env.development? || Rails.env.test?
11
- end
12
-
13
9
  initializer "harness.adapter" do |app|
14
10
  case Rails.env
15
11
  when 'development'
@@ -32,5 +28,27 @@ module Harness
32
28
  Harness.redis ||= Redis::Namespace.new('harness', :redis => Redis.connect(:host => 'localhost', :port => '6379'))
33
29
  end
34
30
  end
31
+
32
+ initializer "harness.queue" do
33
+ Harness.config.queue = Harness::SyncronousQueue
34
+ end
35
+
36
+ initializer "harness.queue.production" do |app|
37
+ use_real_queue = Rails.env != 'development' && Rails.env != 'test'
38
+
39
+ if defined?(Resque::Job) && use_real_queue
40
+ require 'harness/queues/resque_queue'
41
+ Harness.config.queue = :resque
42
+ elsif defined?(Sidekiq::Worker) && use_real_queue
43
+ require 'harness/queues/sidekiq_queue'
44
+ Harness.config.queue = :sidekiq
45
+ end
46
+ end
47
+
48
+ initializer "harness.sidekiq" do |app|
49
+ if defined? Sidekiq::Worker
50
+ require 'harness/integration/sidekiq'
51
+ end
52
+ end
35
53
  end
36
54
  end
@@ -1,3 +1,3 @@
1
1
  module Harness
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.14"
3
3
  end
data/lib/harness.rb CHANGED
@@ -2,32 +2,42 @@ require "harness/version"
2
2
 
3
3
  require 'thread'
4
4
 
5
- require 'securerandom'
6
-
7
5
  require 'redis'
8
6
  require 'redis/namespace'
9
7
 
10
8
  require 'active_support/notifications'
11
9
  require 'active_support/core_ext/string'
10
+ require 'active_support/core_ext/hash/keys'
11
+ require 'active_support/core_ext/numeric'
12
+ require 'active_support/core_ext/integer'
12
13
 
13
14
  module Harness
14
15
  class LoggingError < RuntimeError ; end
16
+ class NoCounter < RuntimeError ; end
15
17
 
16
18
  class Config
17
- attr_reader :adapter
18
- attr_accessor :syncronous
19
+ attr_reader :adapter, :queue
20
+ attr_accessor :namespace
19
21
 
20
22
  def adapter=(val)
21
23
  if val.is_a? Symbol
22
- @adapter = "Harness::#{val.to_s.classify}Adapter".constantize
24
+ @adapter = "Harness::#{val.to_s.camelize}Adapter".constantize
23
25
  else
24
26
  @adapter = val
25
27
  end
26
28
  end
27
29
 
30
+ def queue=(val)
31
+ if val.is_a? Symbol
32
+ @queue= "Harness::#{val.to_s.camelize}Queue".constantize
33
+ else
34
+ @queue= val
35
+ end
36
+ end
37
+
28
38
  def method_missing(name, *args, &block)
29
39
  begin
30
- "Harness::#{name.to_s.classify}Adapter".constantize.config
40
+ "Harness::#{name.to_s.camelize}Adapter".constantize.config
31
41
  rescue NameError
32
42
  super
33
43
  end
@@ -38,25 +48,8 @@ module Harness
38
48
  @config ||= Config.new
39
49
  end
40
50
 
41
- def self.queue
42
- @queue ||= Queue.new
43
- end
44
-
45
- def self.consumer
46
- Thread.current["#{object_id}_harness_consumer_"] ||= Consumer.new
47
- end
48
-
49
51
  def self.log(measurement)
50
- queue << measurement
51
- wait if config.syncronous
52
- end
53
-
54
- def self.mutex
55
- @mutex ||= Mutex.new
56
- end
57
-
58
- def self.wait
59
- sleep 0.01 until consumer.finished? && queue.empty?
52
+ config.queue.push measurement
60
53
  end
61
54
 
62
55
  def self.logger
@@ -77,7 +70,8 @@ module Harness
77
70
 
78
71
  def self.reset_counters!
79
72
  redis.smembers('counters').each do |counter|
80
- redis.set counter, -1
73
+ redis.set counter, 0
74
+ redis.del "meters/#{counter}"
81
75
  end
82
76
  end
83
77
  end
@@ -85,10 +79,13 @@ end
85
79
  require 'harness/measurement'
86
80
  require 'harness/counter'
87
81
  require 'harness/gauge'
82
+ require 'harness/meter'
88
83
 
89
84
  require 'harness/instrumentation'
90
85
 
91
- require 'harness/consumer'
86
+ require 'harness/job'
87
+
88
+ require 'harness/queues/syncronous_queue'
92
89
 
93
90
  require 'harness/adapters/librato_adapter'
94
91
  require 'harness/adapters/memory_adapter'
@@ -101,8 +98,6 @@ require 'harness/integration/active_support'
101
98
 
102
99
  require 'harness/railtie' if defined?(Rails)
103
100
 
104
- Harness.consumer.consume
105
-
106
101
  require 'logger'
107
102
 
108
103
  Harness.logger = Logger.new $stdout
@@ -57,13 +57,32 @@ class CountersWithRedis < IntegrationTest
57
57
 
58
58
  Harness.reset_counters!
59
59
 
60
- assert_equal -1, redis.get("event-counter").to_i
61
- assert_equal -1, redis.get("event-counter2").to_i
60
+ assert_equal 0, redis.get("event-counter").to_i
61
+ assert_equal 0, redis.get("event-counter2").to_i
62
62
 
63
63
  counters.clear
64
64
  instrument "event-counter", :counter => true
65
65
  assert_counter_logged 'event-counter'
66
66
 
67
- assert_equal 0, counters.first.value
67
+ assert_equal 1, counters.first.value
68
+ assert_equal 1, redis.get("event-counter").to_i
69
+ end
70
+
71
+ def test_resets_counters_resets_meters
72
+ instrument "event-counter", :counter => true
73
+
74
+ assert_equal 1, redis.get("event-counter").to_i
75
+ assert_equal 1, redis.zcard("meters/event-counter").to_i
76
+
77
+ Harness.reset_counters!
78
+
79
+ assert_equal 0, redis.get("event-counter").to_i
80
+ assert_equal 0, redis.zcard("meters/event-counter").to_i
81
+ end
82
+
83
+ def test_counting_updates_sorted_set_in_redis
84
+ instrument "event-counter", :counter => true
85
+
86
+ assert_equal 1, redis.zcard("meters/event-counter")
68
87
  end
69
88
  end
@@ -25,4 +25,26 @@ class ActiveSupportTestCase < IntegrationTest
25
25
  assert_counter_logged "test-counter"
26
26
  assert_gauge_logged "test-gauge"
27
27
  end
28
+
29
+ def test_does_not_log_counter_on_exception
30
+ begin
31
+ ActiveSupport::Notifications.instrument "counter_test.harness", :counter => true do |args|
32
+ raise
33
+ end
34
+ rescue
35
+ end
36
+
37
+ refute_counter_logged "counter_test.harness"
38
+ end
39
+
40
+ def test_does_not_log_gauge_on_exception
41
+ begin
42
+ ActiveSupport::Notifications.instrument "gauge_test.harness", :gauge => true do |args|
43
+ raise
44
+ end
45
+ rescue
46
+ end
47
+
48
+ refute_gauge_logged "gauge_test.harness"
49
+ end
28
50
  end
@@ -13,10 +13,20 @@ class ActionViewIntegration < IntegrationTest
13
13
  assert_gauge_logged "render_partial.action_view"
14
14
  end
15
15
 
16
+ def test_skips_internal_partial_events
17
+ instrument "!render_partial"
18
+
19
+ refute_gauge_logged "!render_partial.action_view"
20
+ end
21
+
22
+ def test_skips_internal_template_events
23
+ instrument "!render_template"
24
+
25
+ refute_gauge_logged "!render_template.action_view"
26
+ end
27
+
16
28
  private
17
29
  def instrument(event)
18
- ActiveSupport::Notifications.instrument "#{event}.action_view" do |*args|
19
- # nada
20
- end
30
+ ActiveSupport::Notifications.instrument "#{event}.action_view"
21
31
  end
22
32
  end
@@ -0,0 +1,54 @@
1
+ require 'test_helper'
2
+ require 'sidekiq'
3
+ require 'harness/integration/sidekiq'
4
+
5
+ class SidekiqIntegrationTest < IntegrationTest
6
+ def middleware
7
+ Sidekiq::Middleware::Server::HarnessInstrumentation.new
8
+ end
9
+
10
+ def tests_logs_job_performance_stats
11
+ middleware.call("report", nil, nil) { }
12
+
13
+ assert_gauge_logged "report.sidekiq"
14
+ assert_counter_logged "reports.sidekiq"
15
+ end
16
+
17
+ def tests_logs_a_counter_of_all_jobs
18
+ middleware.call("reports", nil, nil) { }
19
+
20
+ assert_counter_logged "job.sidekiq"
21
+ end
22
+
23
+ def test_ignores_harness_jobs
24
+ middleware.call("Harness::SidekiqQueue::SendCounter", nil, nil) { }
25
+
26
+ assert_empty counters
27
+ assert_empty gauges
28
+
29
+ middleware.call("Harness::SidekiqQueue::SendGauge", nil, nil) { }
30
+
31
+ assert_empty counters
32
+ assert_empty gauges
33
+
34
+ middleware.call(Harness::SidekiqQueue::SendCounter, nil, nil) { }
35
+
36
+ assert_empty counters
37
+ assert_empty gauges
38
+
39
+ middleware.call(Harness::SidekiqQueue::SendGauge, nil, nil) { }
40
+
41
+ assert_empty counters
42
+ assert_empty gauges
43
+
44
+ middleware.call(Harness::SidekiqQueue::SendCounter.new, nil, nil) { }
45
+
46
+ assert_empty counters
47
+ assert_empty gauges
48
+
49
+ middleware.call(Harness::SidekiqQueue::SendGauge.new, nil, nil) { }
50
+
51
+ assert_empty counters
52
+ assert_empty gauges
53
+ end
54
+ end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ class CountersWithRedis < IntegrationTest
4
+ def test_counters_can_act_like_gauges
5
+ 50.times { instrument "event-counter", :counter => true }
6
+
7
+ meter = Harness::Meter.new 'event-counter'
8
+ assert_equal 50, meter.per_second.value
9
+ assert_equal 50, meter.per_minute.value
10
+ assert_equal 50, meter.per_hour.value
11
+ end
12
+
13
+ def tests_raises_an_error_when_no_such_counter
14
+ assert_raises Harness::NoCounter do
15
+ Harness::Meter.new 'unknown-counter'
16
+ end
17
+ end
18
+
19
+ def test_rates_return_gauges
20
+ 50.times { instrument "event-counter", :counter => true }
21
+
22
+ meter = Harness::Meter.new 'event-counter'
23
+
24
+ gauge = meter.per_second
25
+
26
+ assert_kind_of Harness::Gauge, gauge
27
+
28
+ assert_equal "event-counter-per-second", gauge.id
29
+ assert_equal "event-counter per second", gauge.name
30
+ assert_kind_of Time, gauge.time
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+ require 'harness/queues/resque_queue'
3
+
4
+ class ResqueTest < IntegrationTest
5
+ def setup
6
+ super
7
+ Resque.inline = true
8
+ Harness.config.queue = :resque
9
+ end
10
+
11
+ def test_a_gauge_is_logged
12
+ instrument "test-gauge", :gauge => true
13
+
14
+ assert_gauge_logged "test-gauge"
15
+ end
16
+
17
+ def test_a_counter_is_logged
18
+ instrument "test-counter", :counter => true
19
+
20
+ assert_counter_logged "test-counter"
21
+
22
+ assert_equal 1, counters.first.value
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ require 'test_helper'
2
+ require 'harness/queues/sidekiq_queue'
3
+ require 'sidekiq/testing'
4
+
5
+ class SidekiqTest < IntegrationTest
6
+ def setup
7
+ super
8
+ Harness.config.queue = :sidekiq
9
+ end
10
+
11
+ def test_a_gauge_is_logged
12
+ instrument "test-gauge", :gauge => true
13
+
14
+ refute_empty Harness::SidekiqQueue::SendGauge.jobs
15
+
16
+ args = Harness::SidekiqQueue::SendGauge.jobs.first['args'].first
17
+ Harness::SidekiqQueue::SendGauge.new.perform args
18
+
19
+ assert_gauge_logged "test-gauge"
20
+ end
21
+
22
+ def test_a_counter_is_logged
23
+ instrument "test-counter", :counter => true
24
+
25
+ refute_empty Harness::SidekiqQueue::SendCounter.jobs
26
+
27
+ args = Harness::SidekiqQueue::SendCounter.jobs.first['args'].first
28
+ Harness::SidekiqQueue::SendCounter.new.perform args
29
+
30
+ assert_counter_logged "test-counter"
31
+
32
+ assert_equal 1, counters.first.value
33
+ end
34
+ end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'simplecov'
2
2
  SimpleCov.start
3
3
 
4
+ require 'resque'
5
+ require 'sidekiq'
6
+
4
7
  require 'harness'
5
8
 
6
9
  require 'minitest/unit'
@@ -11,10 +14,6 @@ require 'webmock/minitest'
11
14
 
12
15
  WebMock.disable_net_connect!
13
16
 
14
- Thread.abort_on_exception = true
15
-
16
- Harness.config.syncronous = true
17
-
18
17
  Harness.logger = Logger.new '/dev/null'
19
18
 
20
19
  Harness.redis = Redis::Namespace.new 'harness-test', :redis => Redis.connect(:host => 'localhost', :port => '6379')
@@ -22,6 +21,8 @@ Harness.redis = Redis::Namespace.new 'harness-test', :redis => Redis.connect(:ho
22
21
  class IntegrationTest < MiniTest::Unit::TestCase
23
22
  def setup
24
23
  Harness.config.adapter = :memory
24
+ Harness.config.queue = :syncronous
25
+
25
26
  gauges.clear ; counters.clear
26
27
  redis.flushall
27
28
  end
@@ -34,6 +35,14 @@ class IntegrationTest < MiniTest::Unit::TestCase
34
35
  refute_empty counters.select {|c| c.name = name }, "Expected #{counters.inspect} to contain a #{name} result"
35
36
  end
36
37
 
38
+ def refute_gauge_logged(name)
39
+ assert_empty gauges.select {|g| g.name = name }, "No gauge expected to be logged"
40
+ end
41
+
42
+ def refute_counter_logged(name)
43
+ assert_empty counters.select {|c| c.name = name }, "No counter expected to be logged"
44
+ end
45
+
37
46
  def gauges
38
47
  Harness::MemoryAdapter.gauges
39
48
  end
@@ -21,13 +21,7 @@ class LibratoAdapterTest < MiniTest::Unit::TestCase
21
21
 
22
22
  Harness::LibratoAdapter.config.email = email
23
23
  Harness::LibratoAdapter.config.token = token
24
- end
25
-
26
- def teardown
27
- WebMock.reset!
28
-
29
- Harness::LibratoAdapter.config.email = nil
30
- Harness::LibratoAdapter.config.token = nil
24
+ Harness.config.namespace = nil
31
25
  end
32
26
 
33
27
  def test_gauge_is_logged
@@ -50,12 +44,12 @@ class LibratoAdapterTest < MiniTest::Unit::TestCase
50
44
  assert_requested expected_request
51
45
  end
52
46
 
53
- def test_gauge_id_is_sanitized
54
- @gauge.id = "process_action.action_controller"
47
+ def test_gauge_is_logged_with_namespace
48
+ Harness.config.namespace = :foo
55
49
 
56
50
  json = {
57
51
  :gauges => [{
58
- :name => "process_action-action_controller",
52
+ :name => "#{@gauge.id}.foo",
59
53
  :display_name => @gauge.name,
60
54
  :value => @gauge.value,
61
55
  :measure_time => @gauge.time.to_i,
@@ -72,7 +66,6 @@ class LibratoAdapterTest < MiniTest::Unit::TestCase
72
66
  assert_requested expected_request
73
67
  end
74
68
 
75
-
76
69
  def test_logging_gague_raises_an_exception
77
70
  stub_request(:post, %r{metrics}).to_return(:status => 500, :body => "message")
78
71
 
@@ -118,12 +111,12 @@ class LibratoAdapterTest < MiniTest::Unit::TestCase
118
111
  assert_requested expected_request
119
112
  end
120
113
 
121
- def test_counter_id_is_sanitized
122
- @counter.id = "total_requests.action_controller"
114
+ def test_counter_is_logged_with_namespace
115
+ Harness.config.namespace = :foo
123
116
 
124
117
  json = {
125
118
  :counters => [{
126
- :name => "total_requests-action_controller",
119
+ :name => "#{@counter.id}.foo",
127
120
  :display_name => @counter.name,
128
121
  :value => @counter.value,
129
122
  :measure_time => @counter.time.to_i,
@@ -59,6 +59,16 @@ class CounterTest < MiniTest::Unit::TestCase
59
59
 
60
60
  counter = Harness::Counter.from_event event
61
61
 
62
+ assert_equal 'foo', counter.id
63
+ end
64
+
65
+ def test_sets_id_from_payload_if_string
66
+ base = Time.now
67
+
68
+ event = ActiveSupport::Notifications::Event.new "name", base - 1, Time.now, nil, :counter => 'foo'
69
+
70
+ counter = Harness::Counter.from_event event
71
+
62
72
  assert_equal 'foo', counter.id
63
73
  end
64
74
  end
@@ -55,6 +55,16 @@ class GaugeTest < MiniTest::Unit::TestCase
55
55
  assert_equal 'foo', gauge.id
56
56
  end
57
57
 
58
+ def test_sets_id_from_payload_if_string
59
+ base = Time.now
60
+
61
+ event = ActiveSupport::Notifications::Event.new "name", base - 1, Time.now, nil, :gauge => 'foo'
62
+
63
+ gauge = Harness::Gauge.from_event event
64
+
65
+ assert_equal 'foo', gauge.id
66
+ end
67
+
58
68
  def test_initializes_time_if_not_set
59
69
  gauge = Harness::Gauge.new
60
70
 
@@ -13,6 +13,18 @@ class HarnessModuleTest < MiniTest::Unit::TestCase
13
13
  assert_equal Harness::MemoryAdapter, Harness.config.adapter
14
14
  end
15
15
 
16
+ def test_can_set_the_queue_with_a_symbol
17
+ Harness.config.queue = :syncronous
18
+
19
+ assert_equal Harness::SyncronousQueue, Harness.config.queue
20
+ end
21
+
22
+ def test_can_set_the_queue_with_a_class
23
+ Harness.config.adapter = Harness::SyncronousQueue
24
+
25
+ assert_equal Harness::SyncronousQueue, Harness.config.queue
26
+ end
27
+
16
28
  def test_uses_method_missing_to_configure_adapters
17
29
  Harness.config.librato.email = 'foo'
18
30
 
@@ -33,4 +33,37 @@ class MeasurementTest < MiniTest::Unit::TestCase
33
33
  def test_initializes_time
34
34
  assert @measurement.time
35
35
  end
36
+
37
+ def test_can_take_a_string_time
38
+ @measurement.time = "2010-10-1T15:15:15Z"
39
+
40
+ assert_equal DateTime.parse("2010-10-1T15:15:15Z"), @measurement.time
41
+ end
42
+
43
+ def test_can_take_an_integer_time
44
+ time = Time.now
45
+
46
+ @measurement.time = time.to_i
47
+
48
+ assert_equal time.to_i, @measurement.time.to_i
49
+ end
50
+
51
+ def test_can_take_a_time
52
+ time = Time.now
53
+
54
+ @measurement.time = time
55
+
56
+ assert_equal time, @measurement.time
57
+ end
58
+
59
+ def test_attributes
60
+ hash = @measurement.attributes
61
+
62
+ assert hash.has_key?(:id)
63
+ assert hash.has_key?(:name)
64
+ assert hash.has_key?(:source)
65
+ assert hash.has_key?(:time)
66
+ assert hash.has_key?(:units)
67
+ assert hash.has_key?(:value)
68
+ end
36
69
  end
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.14
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-10 00:00:00.000000000 Z
12
+ date: 2012-05-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70328784097540 !ruby/object:Gem::Requirement
16
+ requirement: &70133381445980 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70328784097540
24
+ version_requirements: *70133381445980
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: redis
27
- requirement: &70328784097120 !ruby/object:Gem::Requirement
27
+ requirement: &70133381445320 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70328784097120
35
+ version_requirements: *70133381445320
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: redis-namespace
38
- requirement: &70328784096660 !ruby/object:Gem::Requirement
38
+ requirement: &70133381444680 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70328784096660
46
+ version_requirements: *70133381444680
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: simplecov
49
- requirement: &70328784096240 !ruby/object:Gem::Requirement
49
+ requirement: &70133381443920 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70328784096240
57
+ version_requirements: *70133381443920
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: webmock
60
- requirement: &70328784095820 !ruby/object:Gem::Requirement
60
+ requirement: &70133381443020 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,29 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70328784095820
68
+ version_requirements: *70133381443020
69
+ - !ruby/object:Gem::Dependency
70
+ name: resque
71
+ requirement: &70133381441920 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70133381441920
80
+ - !ruby/object:Gem::Dependency
81
+ name: sidekiq
82
+ requirement: &70133381441440 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70133381441440
69
91
  description: ''
70
92
  email:
71
93
  - me@broadcastingadam.com
@@ -91,7 +113,13 @@ files:
91
113
  - lib/harness/integration/action_mailer.rb
92
114
  - lib/harness/integration/action_view.rb
93
115
  - lib/harness/integration/active_support.rb
116
+ - lib/harness/integration/sidekiq.rb
117
+ - lib/harness/job.rb
94
118
  - lib/harness/measurement.rb
119
+ - lib/harness/meter.rb
120
+ - lib/harness/queues/resque_queue.rb
121
+ - lib/harness/queues/sidekiq_queue.rb
122
+ - lib/harness/queues/syncronous_queue.rb
95
123
  - lib/harness/railtie.rb
96
124
  - lib/harness/tasks.rake
97
125
  - lib/harness/version.rb
@@ -101,7 +129,11 @@ files:
101
129
  - test/integration/integrations/action_mailer_test.rb
102
130
  - test/integration/integrations/action_view_test.rb
103
131
  - test/integration/integrations/active_support_test.rb
132
+ - test/integration/integrations/sidekiq_test.rb
104
133
  - test/integration/logging_test.rb
134
+ - test/integration/meter_test.rb
135
+ - test/integration/queues/resque_test.rb
136
+ - test/integration/queues/sidekiq_test.rb
105
137
  - test/test_helper.rb
106
138
  - test/unit/adapters/librato_adapter_test.rb
107
139
  - test/unit/adapters/memory_adapter_test.rb
@@ -109,6 +141,7 @@ files:
109
141
  - test/unit/gauge_test.rb
110
142
  - test/unit/harness_test.rb
111
143
  - test/unit/measurement_test.rb
144
+ - test/unit/meter_test.rb
112
145
  homepage: ''
113
146
  licenses: []
114
147
  post_install_message:
@@ -140,7 +173,11 @@ test_files:
140
173
  - test/integration/integrations/action_mailer_test.rb
141
174
  - test/integration/integrations/action_view_test.rb
142
175
  - test/integration/integrations/active_support_test.rb
176
+ - test/integration/integrations/sidekiq_test.rb
143
177
  - test/integration/logging_test.rb
178
+ - test/integration/meter_test.rb
179
+ - test/integration/queues/resque_test.rb
180
+ - test/integration/queues/sidekiq_test.rb
144
181
  - test/test_helper.rb
145
182
  - test/unit/adapters/librato_adapter_test.rb
146
183
  - test/unit/adapters/memory_adapter_test.rb
@@ -148,3 +185,4 @@ test_files:
148
185
  - test/unit/gauge_test.rb
149
186
  - test/unit/harness_test.rb
150
187
  - test/unit/measurement_test.rb
188
+ - test/unit/meter_test.rb