time_bandits 0.10.9 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +17 -5
  3. data/Appraisals +2 -8
  4. data/README.md +29 -6
  5. data/Rakefile +1 -0
  6. data/docker-compose.yml +4 -4
  7. data/gemfiles/activesupport_5.2.4.3.gemfile +8 -0
  8. data/gemfiles/activesupport_5.2.4.3.gemfile.lock +81 -0
  9. data/gemfiles/activesupport_6.0.3.2.gemfile +8 -0
  10. data/gemfiles/activesupport_6.0.3.2.gemfile.lock +81 -0
  11. data/lib/time_bandits.rb +2 -6
  12. data/lib/time_bandits/monkey_patches/action_controller.rb +2 -67
  13. data/lib/time_bandits/monkey_patches/active_record.rb +21 -101
  14. data/lib/time_bandits/rack/logger.rb +46 -19
  15. data/lib/time_bandits/railtie.rb +2 -6
  16. data/lib/time_bandits/time_consumers/dalli.rb +0 -12
  17. data/lib/time_bandits/time_consumers/garbage_collection.rb +5 -29
  18. data/lib/time_bandits/version.rb +1 -1
  19. data/test/test_helper.rb +5 -10
  20. data/test/unit/database_test.rb +15 -0
  21. data/test/unit/gc_consumer_test.rb +2 -3
  22. data/test/unit/sequel_test.rb +5 -1
  23. data/time_bandits.gemspec +6 -3
  24. metadata +20 -33
  25. data/gemfiles/activesupport_4.1.16.gemfile +0 -8
  26. data/gemfiles/activesupport_4.1.16.gemfile.lock +0 -91
  27. data/gemfiles/activesupport_4.2.8.gemfile +0 -8
  28. data/gemfiles/activesupport_4.2.8.gemfile.lock +0 -89
  29. data/gemfiles/activesupport_4.2.9.gemfile +0 -8
  30. data/gemfiles/activesupport_4.2.9.gemfile.lock +0 -89
  31. data/gemfiles/activesupport_5.0.3.gemfile +0 -8
  32. data/gemfiles/activesupport_5.0.3.gemfile.lock +0 -88
  33. data/gemfiles/activesupport_5.0.4.gemfile +0 -8
  34. data/gemfiles/activesupport_5.0.4.gemfile.lock +0 -88
  35. data/gemfiles/activesupport_5.1.1.gemfile +0 -8
  36. data/gemfiles/activesupport_5.1.1.gemfile.lock +0 -88
  37. data/gemfiles/activesupport_5.1.2.gemfile +0 -8
  38. data/gemfiles/activesupport_5.1.2.gemfile.lock +0 -88
  39. data/lib/time_bandits/monkey_patches/active_support_cache_store.rb +0 -18
  40. data/lib/time_bandits/rack/logger40.rb +0 -94
  41. data/rails/init.rb +0 -1
@@ -3,9 +3,6 @@
3
3
  # and the number of query cache hits
4
4
  # it needs to be adapted to each new rails version
5
5
 
6
- raise "time_bandits ActiveRecord monkey patch is not compatible with your rails version" unless
7
- Rails::VERSION::STRING =~ /^(3\.[012]|4\.[012])|5\.[01]/
8
-
9
6
  require "active_record/log_subscriber"
10
7
 
11
8
  module ActiveRecord
@@ -42,119 +39,39 @@ module ActiveRecord
42
39
  hits
43
40
  end
44
41
 
45
- # Rails 4.1 uses method_added to automatically subscribe newly
46
- # added methods. Since :render_bind and :sql are already defined,
47
- # the net effect is that sql gets called twice. Therefore, we
48
- # temporarily switch to protected mode and change it back later to
49
- # public.
50
-
51
- # Note that render_bind was added for Rails 4.0, and the implementation
52
- # has changed since then, so we are careful to only redefine it if necessary.
53
- unless instance_methods.include?(:render_bind)
54
- protected
55
- def render_bind(column, value)
56
- if column
57
- if column.type == :binary
58
- value = "<#{value.bytesize} bytes of binary data>"
59
- end
60
- [column.name, value]
61
- else
62
- [nil, value]
63
- end
64
- end
65
- public :render_bind
66
- public
67
- end
68
-
69
- protected
42
+ remove_method :sql
70
43
  def sql(event)
44
+ payload = event.payload
45
+
71
46
  self.class.runtime += event.duration
72
47
  self.class.call_count += 1
73
- self.class.query_cache_hits += 1 if payload[:cached] || event.payload[:name] == "CACHE"
48
+ self.class.query_cache_hits += 1 if payload[:cached] || payload[:name] == "CACHE"
74
49
 
75
50
  return unless logger.debug?
76
51
 
77
- payload = event.payload
78
-
79
52
  return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
80
53
 
81
54
  log_sql_statement(payload, event)
82
55
  end
83
- public :sql
84
- public
85
56
 
86
57
  private
87
- if Rails::VERSION::STRING >= "5.1.5"
88
- def log_sql_statement(payload, event)
89
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
90
- name = "CACHE #{name}" if payload[:cached]
91
- sql = payload[:sql]
92
- binds = nil
93
-
94
- unless (payload[:binds] || []).empty?
95
- casted_params = type_casted_binds(payload[:type_casted_binds])
96
- binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
97
- render_bind(attr, value)
98
- }.inspect
99
- end
100
-
101
- name = colorize_payload_name(name, payload[:name])
102
- sql = color(sql, sql_color(sql), true)
103
-
104
- debug " #{name} #{sql}#{binds}"
58
+ def log_sql_statement(payload, event)
59
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
60
+ name = "CACHE #{name}" if payload[:cached]
61
+ sql = payload[:sql]
62
+ binds = nil
63
+
64
+ unless (payload[:binds] || []).empty?
65
+ casted_params = type_casted_binds(payload[:type_casted_binds])
66
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
67
+ render_bind(attr, value)
68
+ }.inspect
105
69
  end
106
- elsif Rails::VERSION::STRING >= "5.0.3"
107
- def log_sql_statement(payload, event)
108
- name = '%s (%.1fms)' % [payload[:name], event.duration]
109
- sql = payload[:sql].squeeze(' ')
110
- binds = nil
111
-
112
- unless (payload[:binds] || []).empty?
113
- casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
114
- binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
115
- render_bind(attr, value)
116
- }.inspect
117
- end
118
-
119
- name = colorize_payload_name(name, payload[:name])
120
- sql = color(sql, sql_color(sql), true)
121
-
122
- debug " #{name} #{sql}#{binds}"
123
- end
124
- elsif Rails::VERSION::STRING >= "5.0"
125
- def log_sql_statement(payload, event)
126
- name = '%s (%.1fms)' % [payload[:name], event.duration]
127
- sql = payload[:sql].squeeze(' ')
128
- binds = nil
129
-
130
- unless (payload[:binds] || []).empty?
131
- binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
132
- end
133
70
 
134
- name = colorize_payload_name(name, payload[:name])
135
- sql = color(sql, sql_color(sql), true)
71
+ name = colorize_payload_name(name, payload[:name])
72
+ sql = color(sql, sql_color(sql), true)
136
73
 
137
- debug " #{name} #{sql}#{binds}"
138
- end
139
- else
140
- def log_sql_statement(payload, event)
141
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
142
- sql = payload[:sql]
143
- binds = nil
144
-
145
- unless (payload[:binds] || []).empty?
146
- binds = " " + payload[:binds].map { |col,v| render_bind(col, v) }.inspect
147
- end
148
-
149
- if odd?
150
- name = color(name, CYAN, true)
151
- sql = color(sql, nil, true)
152
- else
153
- name = color(name, MAGENTA, true)
154
- end
155
-
156
- debug " #{name} #{sql}#{binds}"
157
- end
74
+ debug " #{name} #{sql}#{binds}"
158
75
  end
159
76
  end
160
77
 
@@ -162,11 +79,13 @@ module ActiveRecord
162
79
 
163
80
  module Railties
164
81
  module ControllerRuntime
82
+ remove_method :cleanup_view_runtime
165
83
  def cleanup_view_runtime
166
84
  # this method has been redefined to do nothing for activerecord on purpose
167
85
  super
168
86
  end
169
87
 
88
+ remove_method :append_info_to_payload
170
89
  def append_info_to_payload(payload)
171
90
  super
172
91
  if ActiveRecord::Base.connected?
@@ -176,6 +95,7 @@ module ActiveRecord
176
95
 
177
96
  module ClassMethods
178
97
  # this method has been redefined to do nothing for activerecord on purpose
98
+ remove_method :log_process_action
179
99
  def log_process_action(payload)
180
100
  super
181
101
  end
@@ -1,9 +1,17 @@
1
+ require 'active_support/core_ext/time/conversions'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/log_subscriber'
4
+ require 'action_dispatch/http/request'
5
+ require 'rack/body_proxy'
6
+
1
7
  module TimeBandits
2
8
  module Rack
9
+ # Sets log tags, logs the request, calls the app, and flushes the logs.
3
10
  class Logger < ActiveSupport::LogSubscriber
4
- def initialize(app, taggers=nil)
5
- @app = app
6
- @taggers = taggers || Rails.application.config.log_tags || []
11
+ def initialize(app, taggers = nil)
12
+ @app = app
13
+ @taggers = taggers || Rails.application.config.log_tags || []
14
+ @instrumenter = ActiveSupport::Notifications.instrumenter
7
15
  end
8
16
 
9
17
  def call(env)
@@ -16,15 +24,29 @@ module TimeBandits
16
24
  end
17
25
  end
18
26
 
19
- protected
27
+ protected
20
28
 
21
29
  def call_app(request, env)
22
30
  start_time = Time.now
23
- before_dispatch(request, env, start_time)
24
- result = @app.call(env)
31
+ start(request, start_time)
32
+ resp = @app.call(env)
33
+ resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
34
+ resp
35
+ rescue
36
+ finish(request)
37
+ raise
25
38
  ensure
26
- run_time = Time.now - start_time
27
- after_dispatch(env, result, run_time)
39
+ completed(request, (Time.now - start_time) * 1000, resp)
40
+ ActiveSupport::LogSubscriber.flush_all!
41
+ end
42
+
43
+ # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700
44
+ def started_request_message(request, start_time=Time.now)
45
+ 'Started %s "%s" for %s at %s' % [
46
+ request.request_method,
47
+ request.filtered_path,
48
+ request.ip,
49
+ start_time.to_default_s ]
28
50
  end
29
51
 
30
52
  def compute_tags(request)
@@ -40,28 +62,33 @@ module TimeBandits
40
62
  end
41
63
  end
42
64
 
43
- def before_dispatch(request, env, start_time)
65
+ private
66
+
67
+ def start(request, start_time)
44
68
  TimeBandits.reset
45
69
  Thread.current.thread_variable_set(:time_bandits_completed_info, nil)
70
+ @instrumenter.start 'action_dispatch.request', request: request
46
71
 
47
- path = request.filtered_path
48
-
49
- debug ""
50
- info "Started #{request.request_method} \"#{path}\" for #{request.ip} at #{start_time.to_default_s}"
72
+ logger.debug ""
73
+ logger.info started_request_message(request, start_time)
51
74
  end
52
75
 
53
- def after_dispatch(env, result, run_time)
54
- status = result ? result.first.to_i : 500
76
+ def completed(request, run_time, resp)
77
+ status = resp ? resp.first.to_i : 500
55
78
  completed_info = Thread.current.thread_variable_get(:time_bandits_completed_info)
56
79
  additions = completed_info[1] if completed_info
57
-
58
80
  message = "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in %.1fms" % run_time
59
81
  message << " (#{additions.join(' | ')})" unless additions.blank?
60
- info message
61
- ensure
62
- ActiveSupport::LogSubscriber.flush_all!
82
+ logger.info message
63
83
  end
64
84
 
85
+ def finish(request)
86
+ @instrumenter.finish 'action_dispatch.request', request: request
87
+ end
88
+
89
+ def logger
90
+ @logger ||= Rails.logger
91
+ end
65
92
  end
66
93
  end
67
94
  end
@@ -1,11 +1,7 @@
1
1
  module TimeBandits
2
2
 
3
3
  module Rack
4
- if Rails::VERSION::STRING >= "4.0"
5
- autoload :Logger, 'time_bandits/rack/logger40'
6
- else
7
- autoload :Logger, 'time_bandits/rack/logger'
8
- end
4
+ autoload :Logger, 'time_bandits/rack/logger'
9
5
  end
10
6
 
11
7
  class Railtie < Rails::Railtie
@@ -19,7 +15,7 @@ module TimeBandits
19
15
  # Rails 5 may trigger the on_load event several times.
20
16
  next if included_modules.include?(ActionController::TimeBanditry)
21
17
  # For some magic reason, the test above is always false, but I'll leave it in
22
- # here, should rails every decide to change this behavior.
18
+ # here, should the Rails team ever decide to change this behavior.
23
19
 
24
20
  include ActionController::TimeBanditry
25
21
 
@@ -1,21 +1,9 @@
1
- if Rails::VERSION::STRING =~ /\A4.[0123]/
2
- require "time_bandits/monkey_patches/active_support_cache_store"
3
- end
4
-
5
1
  module TimeBandits::TimeConsumers
6
2
  class Dalli < BaseConsumer
7
3
  prefix :memcache
8
4
  fields :time, :calls, :misses, :reads, :writes
9
5
  format "Dalli: %.3f(%dr,%dm,%dw,%dc)", :time, :reads, :misses, :writes, :calls
10
6
 
11
- if Rails::VERSION::STRING >= "4.0" && Rails::VERSION::STRING < "4.2" && Rails.cache.class.respond_to?(:instrument=)
12
- # Rails 4 mem_cache_store (which uses dalli internally), unlike dalli_store, is not instrumented by default
13
- def reset
14
- Rails.cache.class.instrument = true
15
- super
16
- end
17
- end
18
-
19
7
  class Subscriber < ActiveSupport::LogSubscriber
20
8
  # cache events are: read write fetch_hit generate delete read_multi increment decrement clear
21
9
  def cache_read(event)
@@ -33,27 +33,11 @@ module TimeBandits
33
33
  def _get_gc_time; 0; end
34
34
  end
35
35
 
36
- if GC.respond_to?(:collections)
37
- def _get_collections; GC.collections; end
38
- elsif GC.respond_to?(:count)
39
- def _get_collections; GC.count; end
40
- else
41
- def _get_collections; 0; end
42
- end
36
+ def _get_collections; GC.count; end
43
37
 
44
- if ObjectSpace.respond_to?(:allocated_objects)
45
- def _get_allocated_objects; ObjectSpace.allocated_objects; end
46
- elsif GC.respond_to?(:stat) && RUBY_VERSION >= "2.2.0"
47
- def _get_allocated_objects; GC.stat(:total_allocated_objects); end
48
- elsif GC.respond_to?(:stat) && RUBY_VERSION >= "2.1.0"
49
- def _get_allocated_objects; GC.stat(:total_allocated_object); end
50
- else
51
- def _get_allocated_objects; 0; end
52
- end
38
+ def _get_allocated_objects; GC.stat(:total_allocated_objects); end
53
39
 
54
- if GC.respond_to?(:allocated_size)
55
- def _get_allocated_size; GC.allocated_size; end
56
- elsif GC.respond_to?(:total_malloced_bytes)
40
+ if GC.respond_to?(:total_malloced_bytes)
57
41
  def _get_allocated_size; GC.total_malloced_bytes; end
58
42
  else
59
43
  def _get_allocated_size; 0; end
@@ -61,22 +45,14 @@ module TimeBandits
61
45
 
62
46
  if GC.respond_to?(:heap_slots)
63
47
  def _get_heap_slots; GC.heap_slots; end
64
- elsif GC.respond_to?(:stat) && RUBY_VERSION >= "2.2.0"
65
- def _get_heap_slots; GC.stat(:heap_live_slots) + GC.stat(:heap_free_slots) + GC.stat(:heap_final_slots); end
66
- elsif GC.respond_to?(:stat) && RUBY_VERSION >= "2.1.0"
67
- def _get_heap_slots; GC.stat(:heap_live_slot) + GC.stat(:heap_free_slot) + GC.stat(:heap_final_slot); end
68
48
  else
69
- def _get_heap_slots; 0; end
49
+ def _get_heap_slots; GC.stat(:heap_live_slots) + GC.stat(:heap_free_slots) + GC.stat(:heap_final_slots); end
70
50
  end
71
51
 
72
52
  if GC.respond_to?(:heap_slots_live_after_last_gc)
73
53
  def live_data_set_size; GC.heap_slots_live_after_last_gc; end
74
- elsif GC.respond_to?(:stat) && RUBY_VERSION >= "2.2.0"
75
- def live_data_set_size; GC.stat(:heap_live_slots); end
76
- elsif GC.respond_to?(:stat) && RUBY_VERSION >= "2.1.0"
77
- def live_data_set_size; GC.stat(:heap_live_slot); end
78
54
  else
79
- def live_data_set_size; 0; end
55
+ def live_data_set_size; GC.stat(:heap_live_slots); end
80
56
  end
81
57
 
82
58
  def reset
@@ -1,3 +1,3 @@
1
1
  module TimeBandits
2
- VERSION = "0.10.9"
2
+ VERSION = "0.12.0"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  require 'minitest'
2
- require 'mocha/setup'
2
+ require 'mocha/minitest'
3
3
  require 'minitest/pride'
4
4
  require 'minitest/autorun'
5
5
 
@@ -18,20 +18,15 @@ end
18
18
  require_relative '../lib/time_bandits'
19
19
  require "byebug"
20
20
 
21
- ActiveSupport::LogSubscriber.class_eval do
22
- # need a logger, otherwise no data will be collected
23
- def logger
24
- @logger ||= ::Logger.new("/dev/null")
25
- end
26
- end
21
+ ActiveSupport::LogSubscriber.logger =::Logger.new("/dev/null")
27
22
 
28
23
  # fake Rails
29
24
  module Rails
30
25
  extend self
31
- module VERSION
32
- STRING = ActiveSupport::VERSION::STRING
33
- end
34
26
  def cache
35
27
  @cache ||= ActiveSupport::Cache.lookup_store(:mem_cache_store)
36
28
  end
29
+ def env
30
+ "test"
31
+ end
37
32
  end
@@ -3,10 +3,17 @@ require 'active_record'
3
3
  require 'time_bandits/monkey_patches/active_record'
4
4
 
5
5
  class DatabaseTest < Test::Unit::TestCase
6
+
6
7
  def setup
7
8
  TimeBandits.time_bandits = []
8
9
  TimeBandits.add TimeBandits::TimeConsumers::Database
9
10
  TimeBandits.reset
11
+ @old_logger = ActiveRecord::Base.logger
12
+ ActiveRecord::Base.logger = Logger.new($stdout)
13
+ end
14
+
15
+ def teardown
16
+ ActiveRecord::Base.logger = @old_logger
10
17
  end
11
18
 
12
19
  test "getting metrics" do
@@ -37,6 +44,14 @@ class DatabaseTest < Test::Unit::TestCase
37
44
  assert_equal "ActiveRecord: 1.234ms(0q,0h)", TimeBandits.runtime
38
45
  end
39
46
 
47
+ test "sql can be executed" do
48
+ event = mock('event')
49
+ event.stubs(:payload).returns({name: "MURKS", sql: "SELECT 1"})
50
+ event.stubs(:duration).returns(0.1)
51
+ ActiveRecord::Base.logger.expects(:debug)
52
+ assert_nil log_subscriber.new.sql(event)
53
+ end
54
+
40
55
  private
41
56
 
42
57
  def bandit
@@ -49,11 +49,10 @@ class GCConsumerTest < Test::Unit::TestCase
49
49
  def check_work
50
50
  GC.start
51
51
  m = TimeBandits.metrics
52
- number_class = RUBY_VERSION >= "2.4.0" ? Integer : Fixnum
53
52
  if GC.respond_to?(:time)
54
53
  assert_operator 0, :<, m[:gc_calls]
55
54
  assert_operator 0, :<, m[:gc_time]
56
- assert_instance_of number_class, m[:heap_growth]
55
+ assert_instance_of Integer, m[:heap_growth]
57
56
  assert_operator 0, :<, m[:heap_size]
58
57
  assert_operator 0, :<, m[:allocated_objects]
59
58
  assert_operator 0, :<, m[:allocated_bytes]
@@ -61,7 +60,7 @@ class GCConsumerTest < Test::Unit::TestCase
61
60
  else
62
61
  assert_operator 0, :<, m[:gc_calls]
63
62
  assert_operator 0, :<=, m[:gc_time]
64
- assert_instance_of number_class, m[:heap_growth]
63
+ assert_instance_of Integer, m[:heap_growth]
65
64
  assert_operator 0, :<, m[:heap_size]
66
65
  assert_operator 0, :<, m[:allocated_objects]
67
66
  assert_operator 0, :<=, m[:allocated_bytes]