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.
- checksums.yaml +4 -4
- data/.travis.yml +17 -5
- data/Appraisals +2 -8
- data/README.md +29 -6
- data/Rakefile +1 -0
- data/docker-compose.yml +4 -4
- data/gemfiles/activesupport_5.2.4.3.gemfile +8 -0
- data/gemfiles/activesupport_5.2.4.3.gemfile.lock +81 -0
- data/gemfiles/activesupport_6.0.3.2.gemfile +8 -0
- data/gemfiles/activesupport_6.0.3.2.gemfile.lock +81 -0
- data/lib/time_bandits.rb +2 -6
- data/lib/time_bandits/monkey_patches/action_controller.rb +2 -67
- data/lib/time_bandits/monkey_patches/active_record.rb +21 -101
- data/lib/time_bandits/rack/logger.rb +46 -19
- data/lib/time_bandits/railtie.rb +2 -6
- data/lib/time_bandits/time_consumers/dalli.rb +0 -12
- data/lib/time_bandits/time_consumers/garbage_collection.rb +5 -29
- data/lib/time_bandits/version.rb +1 -1
- data/test/test_helper.rb +5 -10
- data/test/unit/database_test.rb +15 -0
- data/test/unit/gc_consumer_test.rb +2 -3
- data/test/unit/sequel_test.rb +5 -1
- data/time_bandits.gemspec +6 -3
- metadata +20 -33
- data/gemfiles/activesupport_4.1.16.gemfile +0 -8
- data/gemfiles/activesupport_4.1.16.gemfile.lock +0 -91
- data/gemfiles/activesupport_4.2.8.gemfile +0 -8
- data/gemfiles/activesupport_4.2.8.gemfile.lock +0 -89
- data/gemfiles/activesupport_4.2.9.gemfile +0 -8
- data/gemfiles/activesupport_4.2.9.gemfile.lock +0 -89
- data/gemfiles/activesupport_5.0.3.gemfile +0 -8
- data/gemfiles/activesupport_5.0.3.gemfile.lock +0 -88
- data/gemfiles/activesupport_5.0.4.gemfile +0 -8
- data/gemfiles/activesupport_5.0.4.gemfile.lock +0 -88
- data/gemfiles/activesupport_5.1.1.gemfile +0 -8
- data/gemfiles/activesupport_5.1.1.gemfile.lock +0 -88
- data/gemfiles/activesupport_5.1.2.gemfile +0 -8
- data/gemfiles/activesupport_5.1.2.gemfile.lock +0 -88
- data/lib/time_bandits/monkey_patches/active_support_cache_store.rb +0 -18
- data/lib/time_bandits/rack/logger40.rb +0 -94
- 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
|
-
|
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] ||
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
135
|
-
|
71
|
+
name = colorize_payload_name(name, payload[:name])
|
72
|
+
sql = color(sql, sql_color(sql), true)
|
136
73
|
|
137
|
-
|
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
|
6
|
-
@taggers
|
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
|
-
|
27
|
+
protected
|
20
28
|
|
21
29
|
def call_app(request, env)
|
22
30
|
start_time = Time.now
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
54
|
-
status =
|
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
|
data/lib/time_bandits/railtie.rb
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
module TimeBandits
|
2
2
|
|
3
3
|
module Rack
|
4
|
-
|
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
|
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
|
-
|
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
|
-
|
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?(:
|
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;
|
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;
|
55
|
+
def live_data_set_size; GC.stat(:heap_live_slots); end
|
80
56
|
end
|
81
57
|
|
82
58
|
def reset
|
data/lib/time_bandits/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'minitest'
|
2
|
-
require 'mocha/
|
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.
|
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
|
data/test/unit/database_test.rb
CHANGED
@@ -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
|
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
|
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]
|