harness 0.9.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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