harness 0.9.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/README.md +108 -323
  4. data/harness.gemspec +3 -12
  5. data/lib/harness.rb +69 -68
  6. data/lib/harness/async_queue.rb +29 -0
  7. data/lib/harness/fake_collector.rb +40 -0
  8. data/lib/harness/instrumentation.rb +21 -6
  9. data/lib/harness/null_collector.rb +23 -0
  10. data/lib/harness/subscription.rb +7 -0
  11. data/lib/harness/sync_queue.rb +14 -0
  12. data/lib/harness/version.rb +1 -1
  13. data/test/acceptance_test.rb +20 -0
  14. data/test/active_support_notifications_test.rb +93 -0
  15. data/test/async_queue_test.rb +14 -0
  16. data/test/instrumentation_test.rb +54 -0
  17. data/test/null_collector_test.rb +29 -0
  18. data/test/test_helper.rb +38 -34
  19. metadata +21 -196
  20. data/lib/harness/adapters/librato_adapter.rb +0 -90
  21. data/lib/harness/adapters/memory_adapter.rb +0 -27
  22. data/lib/harness/adapters/null_adapter.rb +0 -11
  23. data/lib/harness/adapters/stathat_adapter.rb +0 -75
  24. data/lib/harness/adapters/statsd_adapter.rb +0 -50
  25. data/lib/harness/consumer.rb +0 -35
  26. data/lib/harness/counter.rb +0 -29
  27. data/lib/harness/gauge.rb +0 -23
  28. data/lib/harness/integration/action_controller.rb +0 -9
  29. data/lib/harness/integration/action_mailer.rb +0 -9
  30. data/lib/harness/integration/action_view.rb +0 -9
  31. data/lib/harness/integration/active_model_serializers.rb +0 -9
  32. data/lib/harness/integration/active_support.rb +0 -9
  33. data/lib/harness/integration/sidekiq.rb +0 -47
  34. data/lib/harness/job.rb +0 -23
  35. data/lib/harness/measurement.rb +0 -43
  36. data/lib/harness/queues/delayed_job_queue.rb +0 -7
  37. data/lib/harness/queues/resque_queue.rb +0 -29
  38. data/lib/harness/queues/sidekiq_queue.rb +0 -31
  39. data/lib/harness/queues/synchronous_queue.rb +0 -18
  40. data/lib/harness/railtie.rb +0 -71
  41. data/lib/harness/tasks.rake +0 -6
  42. data/test/integration/counters_with_redis_test.rb +0 -67
  43. data/test/integration/instrumentation_test.rb +0 -50
  44. data/test/integration/integrations/action_controller_test.rb +0 -51
  45. data/test/integration/integrations/action_mailer_test.rb +0 -22
  46. data/test/integration/integrations/action_view_test.rb +0 -32
  47. data/test/integration/integrations/active_model_serializers_test.rb +0 -22
  48. data/test/integration/integrations/active_support_test.rb +0 -41
  49. data/test/integration/integrations/sidekiq_test.rb +0 -54
  50. data/test/integration/logging_test.rb +0 -17
  51. data/test/integration/queues/delayed_job_test.rb +0 -59
  52. data/test/integration/queues/resque_test.rb +0 -24
  53. data/test/integration/queues/sidekiq_test.rb +0 -34
  54. data/test/integration/railtie_test.rb +0 -26
  55. data/test/unit/adapters/librato_adapter_test.rb +0 -169
  56. data/test/unit/adapters/memory_adapter_test.rb +0 -22
  57. data/test/unit/adapters/stathat_adapter_test.rb +0 -144
  58. data/test/unit/adapters/statsd_adapter_test.rb +0 -74
  59. data/test/unit/counter_test.rb +0 -84
  60. data/test/unit/gauge_test.rb +0 -93
  61. data/test/unit/harness_test.rb +0 -39
  62. data/test/unit/measurement_test.rb +0 -76
data/harness.gemspec CHANGED
@@ -4,7 +4,7 @@ require File.expand_path('../lib/harness/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["ahawkins"]
6
6
  gem.email = ["adam@hawkins.io"]
7
- gem.description = %q{Log ActiveSupport::Notifications to various services like Librato or StatsD}
7
+ gem.description = %q{Collect high level application performance metrics and forward them for analysis}
8
8
  gem.summary = %q{}
9
9
  gem.homepage = "https://github.com/ahawkins/harness"
10
10
 
@@ -16,17 +16,8 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Harness::VERSION
17
17
 
18
18
  gem.add_dependency "activesupport", ">= 3"
19
- gem.add_dependency "redis"
20
- gem.add_dependency "redis-namespace"
21
- gem.add_dependency "multi_json"
19
+ gem.add_dependency "statsd-ruby"
22
20
 
23
21
  gem.add_development_dependency "simplecov"
24
- gem.add_development_dependency "webmock"
25
- gem.add_development_dependency "resque"
26
- gem.add_development_dependency "sidekiq"
27
- gem.add_development_dependency "delayed_job_active_record"
28
- gem.add_development_dependency "active_model_serializers"
29
- gem.add_development_dependency "rails", ">= 3"
30
- gem.add_development_dependency "sqlite3"
31
- gem.add_development_dependency "statsd-instrument"
22
+ gem.add_development_dependency "rake"
32
23
  end
data/lib/harness.rb CHANGED
@@ -1,52 +1,57 @@
1
1
  require "harness/version"
2
2
 
3
- require 'multi_json'
4
-
5
- require 'redis'
6
- require 'redis/namespace'
7
-
3
+ require 'statsd'
8
4
  require 'active_support/notifications'
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'
13
-
14
- require 'active_support/ordered_options'
15
5
 
16
6
  module Harness
17
- class LoggingError < RuntimeError ; end
18
- class NoCounter < RuntimeError ; end
19
-
20
7
  class Config
21
- attr_reader :adapter, :queue
22
- attr_accessor :namespace, :source
23
- attr_reader :instrument
8
+ attr_accessor :collector, :queue
9
+ end
24
10
 
25
- def initialize
26
- @instrument = ActiveSupport::OrderedOptions.new
11
+ Measurement = Struct.new(:name, :value, :rate) do
12
+ def self.from_event(event)
13
+ payload = event.payload
14
+ value = payload.fetch name.split('::').last.downcase.to_sym
15
+
16
+ case value
17
+ when true
18
+ new event.name
19
+ when Fixnum
20
+ new event.name, value
21
+ when String
22
+ new value
23
+ when Hash
24
+ new(value[:name] || event.name, value[:value], value[:rate])
25
+ end
27
26
  end
28
27
 
29
- def adapter=(val)
30
- if val.is_a? Symbol
31
- @adapter = "Harness::#{val.to_s.camelize}Adapter".constantize.new
32
- else
33
- @adapter = val
34
- end
28
+ def sample_rate
29
+ rate.nil? ? 1 : rate
35
30
  end
31
+ end
36
32
 
37
- def queue=(val)
38
- if val.is_a? Symbol
39
- @queue = "Harness::#{val.to_s.camelize}Queue".constantize.new
40
- else
41
- @queue = val
42
- end
33
+ class Timer < Measurement
34
+ def self.from_event(event)
35
+ timer = super
36
+ timer.value = event.duration
37
+ timer
38
+ end
39
+
40
+ def ms
41
+ value
43
42
  end
44
43
 
45
- def method_missing(name, *args, &block)
46
- begin
47
- "Harness::#{name.to_s.camelize}Adapter".constantize.config
48
- rescue NameError
49
- super
44
+ def log
45
+ Harness.timing name, ms, sample_rate
46
+ end
47
+ end
48
+
49
+ class Counter < Measurement
50
+ def log
51
+ if value.nil?
52
+ Harness.increment name, sample_rate
53
+ else
54
+ Harness.count name, value, sample_rate
50
55
  end
51
56
  end
52
57
  end
@@ -55,53 +60,49 @@ module Harness
55
60
  @config ||= Config.new
56
61
  end
57
62
 
58
- def self.log(measurement)
59
- config.queue.push measurement
63
+ def self.increment(*args)
64
+ queue.push [:increment, args]
60
65
  end
61
66
 
62
- def self.logger
63
- @logger
67
+ def self.decrement(*args)
68
+ queue.push [:decrement, args]
64
69
  end
65
70
 
66
- def self.logger=(logger)
67
- @logger = logger
71
+ def self.timing(*args)
72
+ queue.push [:timing, args]
68
73
  end
69
74
 
70
- def self.redis=(redis)
71
- @redis = redis
75
+ def self.count(*args)
76
+ queue.push [:count, args]
72
77
  end
73
78
 
74
- def self.redis
75
- @redis
79
+ def self.time(stat, sample_rate = 1)
80
+ start = Time.now
81
+ result = yield
82
+ timing(stat, ((Time.now - start) * 1000).round, sample_rate)
83
+ result
76
84
  end
77
85
 
78
- def self.reset_counters!
79
- counters.each do |counter|
80
- redis.set counter, 0
81
- end
86
+ def self.gauge(*args)
87
+ queue.push [:gauge, args]
82
88
  end
83
89
 
84
- def self.counters
85
- redis.keys "counters/*"
90
+ def self.queue
91
+ config.queue
86
92
  end
87
- end
88
-
89
- require 'harness/measurement'
90
- require 'harness/counter'
91
- require 'harness/gauge'
92
93
 
93
- require 'harness/instrumentation'
94
-
95
- require 'harness/job'
96
-
97
- require 'harness/queues/synchronous_queue'
94
+ def self.collector
95
+ config.collector
96
+ end
97
+ end
98
98
 
99
- require 'harness/adapters/librato_adapter'
100
- require 'harness/adapters/memory_adapter'
101
- require 'harness/adapters/null_adapter'
99
+ require 'harness/sync_queue'
100
+ require 'harness/async_queue'
102
101
 
103
- require 'harness/railtie' if defined?(Rails)
102
+ require 'harness/null_collector'
103
+ require 'harness/fake_collector'
104
104
 
105
- require 'logger'
105
+ require 'harness/instrumentation'
106
+ require 'harness/subscription'
106
107
 
107
- Harness.logger = Logger.new $stdout
108
+ Harness.config.queue = Harness::AsyncQueue.new
@@ -0,0 +1,29 @@
1
+ require 'thread'
2
+
3
+ module Harness
4
+ class AsyncQueue
5
+ attr_reader :consumer, :queue
6
+
7
+ def initialize
8
+ @queue = Queue.new
9
+ @consumer = Thread.new do
10
+ loop do
11
+ msg = queue.pop
12
+
13
+ method_name = msg.first
14
+ args = msg.last
15
+
16
+ collector.__send__ method_name, *args
17
+ end
18
+ end
19
+ end
20
+
21
+ def push(msg)
22
+ queue.push msg
23
+ end
24
+
25
+ def collector
26
+ Harness.collector
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ module Harness
2
+ class FakeCollector
3
+ Increment = Struct.new(:name, :amount, :rate)
4
+ Decrement = Struct.new(:name, :amount, :rate)
5
+ Gauge = Struct.new(:name, :value, :rate)
6
+
7
+ attr_reader :gauges, :counters, :timers, :increments, :decrements
8
+
9
+ def initialize
10
+ @gauges, @counters, @timers, @increments, @decrements = [], [], [], [], []
11
+ end
12
+
13
+ def timing(*args)
14
+ timers << Harness::Timer.new(*args)
15
+ end
16
+
17
+ def time(stat, sample_rate = 1)
18
+ start = Time.now
19
+ result = yield
20
+ timing(stat, ((Time.now - start) * 1000).round, sample_rate)
21
+ result
22
+ end
23
+
24
+ def increment(*args)
25
+ increments << Increment.new(*args)
26
+ end
27
+
28
+ def decrement(*args)
29
+ decrements << Decrement.new(*args)
30
+ end
31
+
32
+ def count(*args)
33
+ counters << Harness::Counter.new(*args)
34
+ end
35
+
36
+ def gauge(*args)
37
+ gauges << Gauge.new(*args)
38
+ end
39
+ end
40
+ end
@@ -1,8 +1,23 @@
1
- ActiveSupport::Notifications.subscribe %r{.+} do |*args|
2
- event = ActiveSupport::Notifications::Event.new(*args)
3
-
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]
1
+ module Harness
2
+ module Instrumentation
3
+ def timing(*args)
4
+ Harness.timing *args
5
+ end
6
+
7
+ def time(*args, &block)
8
+ Harness.time *args, &block
9
+ end
10
+
11
+ def gauge(*args)
12
+ Harness.gauge *args
13
+ end
14
+
15
+ def increment(*args)
16
+ Harness.increment *args
17
+ end
18
+
19
+ def decrement(*args)
20
+ Harness.decrement *args
21
+ end
7
22
  end
8
23
  end
@@ -0,0 +1,23 @@
1
+ module Harness
2
+ class NullCollector
3
+ def increment(*args)
4
+
5
+ end
6
+
7
+ def count(*args)
8
+
9
+ end
10
+
11
+ def time(*args, &block)
12
+ yield block
13
+ end
14
+
15
+ def timing(*args)
16
+
17
+ end
18
+
19
+ def gauge(*args)
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ ActiveSupport::Notifications.subscribe do |*args|
2
+ event = ActiveSupport::Notifications::Event.new(*args)
3
+ next if event.payload[:exception]
4
+
5
+ Harness::Timer.from_event(event).log if event.payload[:timer]
6
+ Harness::Counter.from_event(event).log if event.payload[:counter]
7
+ end
@@ -0,0 +1,14 @@
1
+ module Harness
2
+ class SyncQueue
3
+ def push(msg)
4
+ method_name = msg.first
5
+ args = msg.last
6
+
7
+ collector.__send__ method_name, *args
8
+ end
9
+
10
+ def collector
11
+ Harness.collector
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Harness
2
- VERSION = "0.9.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,20 @@
1
+ require_relative 'test_helper'
2
+
3
+ class AcceptanceTest < MiniTest::Unit::TestCase
4
+ def test_works_with_actual_statsd
5
+ Harness.config.collector = Statsd.new
6
+ Harness.config.queue = Harness::SyncQueue.new
7
+
8
+ Harness.increment 'foo', 0.6
9
+ Harness.decrement 'foo', 0.5
10
+ Harness.count 'foo', 5, 0.5
11
+
12
+ Harness.gauge 'foo', 0.5, 0.1
13
+
14
+ Harness.timing 'foo', 0.5, 0.1
15
+
16
+ Harness.time 'foo', 1 do
17
+ true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,93 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ActiveSupportNotificationsTestCase < MiniTest::Unit::TestCase
4
+ def test_timers_are_logged
5
+ instrument 'test', timer: true
6
+
7
+ refute_empty timers
8
+ timer = timers.first
9
+ assert_equal 'test', timer.name
10
+ assert_kind_of Float, timer.ms
11
+ end
12
+
13
+ def test_timer_name_can_be_customized
14
+ instrument 'test', timer: 'foo'
15
+
16
+ refute_empty timers
17
+ timer = timers.first
18
+ assert_equal 'foo', timer.name
19
+ end
20
+
21
+ def test_timer_rate_can_be_customized
22
+ instrument 'test', timer: { rate: 0.5, name: 'foo', value: 5 }
23
+
24
+ refute_empty timers
25
+ timer = timers.first
26
+ assert_equal 0.5, timer.rate
27
+ assert_equal 'foo', timer.name
28
+ end
29
+
30
+ def test_passed_value_does_not_override_blocks
31
+ instrument 'test', timer: { rate: 0.5, name: 'foo', value: 5 }
32
+
33
+ refute_empty timers
34
+ timer = timers.first
35
+ refute_equal 5, timer.value
36
+ end
37
+
38
+ def test_does_not_log_timers_on_exception
39
+ begin
40
+ instrument 'test', timer: true do
41
+ raise
42
+ end
43
+ rescue
44
+ end
45
+
46
+ assert_empty timers
47
+ end
48
+
49
+ def test_counters_with_no_value_are_treated_as_increments
50
+ instrument 'test', counter: true
51
+
52
+ refute_empty increments
53
+ counter = increments.first
54
+ assert_equal 'test', counter.name
55
+ end
56
+
57
+ def test_counters_can_have_explicit_values
58
+ instrument 'test', counter: 5
59
+
60
+ refute_empty counters
61
+ counter = counters.first
62
+ assert_equal 'test', counter.name
63
+ end
64
+
65
+ def test_counter_name_can_be_customized
66
+ instrument 'test', counter: 'foo'
67
+
68
+ refute_empty increments
69
+ counter = increments.first
70
+ assert_equal 'foo', counter.name
71
+ end
72
+
73
+ def test_counter_rate_can_be_customized
74
+ instrument 'test', counter: { rate: 0.5, name: 'foo', value: 5 }
75
+
76
+ refute_empty counters
77
+ counter = counters.first
78
+ assert_equal 0.5, counter.rate
79
+ assert_equal 'foo', counter.name
80
+ assert_equal 5, counter.value
81
+ end
82
+
83
+ def test_does_not_log_counters_on_exceptions
84
+ begin
85
+ instrument 'test', counter: true do
86
+ raise
87
+ end
88
+ rescue
89
+ end
90
+
91
+ assert_empty counters
92
+ end
93
+ end