time_bandits 0.14.0 → 0.14.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/time_bandits/monkey_patches/active_record/log_subscriber.rb +77 -0
- data/lib/time_bandits/monkey_patches/active_record/railties/controller_runtime.rb +29 -0
- data/lib/time_bandits/monkey_patches/active_record/runtime_registry.rb +50 -0
- data/lib/time_bandits/monkey_patches/active_record.rb +5 -157
- data/lib/time_bandits/time_consumers/database.rb +14 -2
- data/lib/time_bandits/version.rb +1 -1
- data/test/unit/database_test.rb +41 -6
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3597bdf1515a72122e85b16fffdcedc74a22c479012fa0fb2f7c42f64e0434de
|
4
|
+
data.tar.gz: a6acc865643fc97ce247b33fd55da8399323488435234d15d75766b26fc853c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1451d6f063d1fd7feb18dd66c0650f10bf8398f7407fd4f3f7a63bef57a4bf52846267e8bf82c4e7295e41f1d999b70a16aadd1a08d1fc44fc9f174e18e74a4
|
7
|
+
data.tar.gz: cb1ff1654db60be898992d20a9b7b4825483033637147e94b8fc0192c2566ef096ce49f14b71f7ffd61a6524fa883030c78a69feb10eb90d012bca20321618a6
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
# This file monkey patches class ActiveRecord::LogSubscriber to count
|
2
|
+
# the number of sql statements being executed and the number of query
|
3
|
+
# cache hits, but is only used for Rails versions before 7.1.0.
|
4
|
+
|
5
|
+
require "active_record/log_subscriber"
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
class LogSubscriber
|
9
|
+
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] unless defined?(IGNORE_PAYLOAD_NAMES)
|
10
|
+
|
11
|
+
def self.call_count=(value)
|
12
|
+
Thread.current.thread_variable_set(:active_record_sql_call_count, value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.call_count
|
16
|
+
Thread.current.thread_variable_get(:active_record_sql_call_count) ||
|
17
|
+
Thread.current.thread_variable_set(:active_record_sql_call_count, 0)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.query_cache_hits=(value)
|
21
|
+
Thread.current.thread_variable_set(:active_record_sql_query_cache_hits, value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.query_cache_hits
|
25
|
+
Thread.current.thread_variable_get(:active_record_sql_query_cache_hits) ||
|
26
|
+
Thread.current.thread_variable_set(:active_record_sql_query_cache_hits, 0)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.reset_call_count
|
30
|
+
calls = call_count
|
31
|
+
self.call_count = 0
|
32
|
+
calls
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.reset_query_cache_hits
|
36
|
+
hits = query_cache_hits
|
37
|
+
self.query_cache_hits = 0
|
38
|
+
hits
|
39
|
+
end
|
40
|
+
|
41
|
+
remove_method :sql
|
42
|
+
def sql(event)
|
43
|
+
payload = event.payload
|
44
|
+
|
45
|
+
self.class.runtime += event.duration
|
46
|
+
self.class.call_count += 1
|
47
|
+
self.class.query_cache_hits += 1 if payload[:cached] || payload[:name] == "CACHE"
|
48
|
+
|
49
|
+
return unless logger.debug?
|
50
|
+
|
51
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
52
|
+
|
53
|
+
log_sql_statement(payload, event)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def log_sql_statement(payload, event)
|
58
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
59
|
+
name = "CACHE #{name}" if payload[:cached]
|
60
|
+
sql = payload[:sql]
|
61
|
+
binds = nil
|
62
|
+
|
63
|
+
unless (payload[:binds] || []).empty?
|
64
|
+
casted_params = type_casted_binds(payload[:type_casted_binds])
|
65
|
+
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
66
|
+
render_bind(attr, value)
|
67
|
+
}.inspect
|
68
|
+
end
|
69
|
+
|
70
|
+
name = colorize_payload_name(name, payload[:name])
|
71
|
+
sql = color(sql, sql_color(sql), true)
|
72
|
+
|
73
|
+
debug " #{name} #{sql}#{binds}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "active_record/railties/controller_runtime"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Railties
|
5
|
+
module ControllerRuntime
|
6
|
+
remove_method :cleanup_view_runtime
|
7
|
+
def cleanup_view_runtime
|
8
|
+
# this method has been redefined to do nothing for activerecord on purpose
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
remove_method :append_info_to_payload
|
13
|
+
def append_info_to_payload(payload)
|
14
|
+
super
|
15
|
+
if ActiveRecord::Base.connected?
|
16
|
+
payload[:db_runtime] = TimeBandits::TimeConsumers::Database.instance.consumed
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# this method has been redefined to do nothing for activerecord on purpose
|
22
|
+
remove_method :log_process_action
|
23
|
+
def log_process_action(payload)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# This file monkey patches class ActiveRecord::RuntimeRegistry to
|
2
|
+
# additionally store call counts and cache hits and subscribes an
|
3
|
+
# event listener to manage those counters.
|
4
|
+
|
5
|
+
require "active_record/runtime_registry"
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module RuntimeRegistry
|
9
|
+
|
10
|
+
def self.call_count
|
11
|
+
ActiveSupport::IsolatedExecutionState[:active_record_sql_call_count] ||= 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.call_count=(value)
|
15
|
+
ActiveSupport::IsolatedExecutionState[:active_record_sql_call_count] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.query_cache_hits
|
19
|
+
ActiveSupport::IsolatedExecutionState[:active_record_sql_query_cache_hits] ||= 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.query_cache_hits=(value)
|
23
|
+
ActiveSupport::IsolatedExecutionState[:active_record_sql_query_cache_hits] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :reset_runtime, :reset
|
27
|
+
alias_method :runtime, :sql_runtime
|
28
|
+
alias_method :runtime=, :sql_runtime=
|
29
|
+
|
30
|
+
def self.reset_call_count
|
31
|
+
calls = call_count
|
32
|
+
self.call_count = 0
|
33
|
+
calls
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.reset_query_cache_hits
|
37
|
+
hits = query_cache_hits
|
38
|
+
self.query_cache_hits = 0
|
39
|
+
hits
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
require "active_support/notifications"
|
46
|
+
|
47
|
+
ActiveSupport::Notifications.subscribe("sql.active_record") do |event|
|
48
|
+
ActiveRecord::RuntimeRegistry.call_count += 1
|
49
|
+
ActiveRecord::RuntimeRegistry.query_cache_hits += 1 if event.payload[:cached]
|
50
|
+
end
|
@@ -1,158 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require "active_record/log_subscriber"
|
7
|
-
|
8
|
-
module ActiveRecord
|
9
|
-
class LogSubscriber
|
10
|
-
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] unless defined?(IGNORE_PAYLOAD_NAMES)
|
11
|
-
|
12
|
-
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.1.0")
|
13
|
-
def self.reset_runtime
|
14
|
-
ActiveRecord::RuntimeRegistry.reset
|
15
|
-
end
|
16
|
-
def self.runtime
|
17
|
-
ActiveRecord::RuntimeRegistry.sql_runtime
|
18
|
-
end
|
19
|
-
def self.runtime=(value)
|
20
|
-
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.call_count=(value)
|
25
|
-
Thread.current.thread_variable_set(:active_record_sql_call_count, value)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.call_count
|
29
|
-
Thread.current.thread_variable_get(:active_record_sql_call_count) ||
|
30
|
-
Thread.current.thread_variable_set(:active_record_sql_call_count, 0)
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.query_cache_hits=(value)
|
34
|
-
Thread.current.thread_variable_set(:active_record_sql_query_cache_hits, value)
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.query_cache_hits
|
38
|
-
Thread.current.thread_variable_get(:active_record_sql_query_cache_hits) ||
|
39
|
-
Thread.current.thread_variable_set(:active_record_sql_query_cache_hits, 0)
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.reset_call_count
|
43
|
-
calls = call_count
|
44
|
-
self.call_count = 0
|
45
|
-
calls
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.reset_query_cache_hits
|
49
|
-
hits = query_cache_hits
|
50
|
-
self.query_cache_hits = 0
|
51
|
-
hits
|
52
|
-
end
|
53
|
-
|
54
|
-
remove_method :sql
|
55
|
-
def sql(event)
|
56
|
-
payload = event.payload
|
57
|
-
|
58
|
-
self.class.runtime += event.duration
|
59
|
-
self.class.call_count += 1
|
60
|
-
self.class.query_cache_hits += 1 if payload[:cached] || payload[:name] == "CACHE"
|
61
|
-
|
62
|
-
return unless logger.debug?
|
63
|
-
|
64
|
-
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
65
|
-
|
66
|
-
log_sql_statement(payload, event)
|
67
|
-
end
|
68
|
-
|
69
|
-
private
|
70
|
-
def log_sql_statement(payload, event)
|
71
|
-
name = if payload[:async]
|
72
|
-
"ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
|
73
|
-
else
|
74
|
-
"#{payload[:name]} (#{event.duration.round(1)}ms)"
|
75
|
-
end
|
76
|
-
name = "CACHE #{name}" if payload[:cached]
|
77
|
-
sql = payload[:sql]
|
78
|
-
binds = render_binds(payload)
|
79
|
-
|
80
|
-
name = colorize_payload_name(name, payload[:name])
|
81
|
-
sql = colorize_sql(sql) if colorize_logging
|
82
|
-
|
83
|
-
debug " #{name} #{sql}#{binds}"
|
84
|
-
end
|
85
|
-
|
86
|
-
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.1.0")
|
87
|
-
def render_binds(payload)
|
88
|
-
binds = nil
|
89
|
-
if payload[:binds]&.any?
|
90
|
-
casted_params = type_casted_binds(payload[:type_casted_binds])
|
91
|
-
|
92
|
-
binds = []
|
93
|
-
payload[:binds].each_with_index do |attr, i|
|
94
|
-
attribute_name = if attr.respond_to?(:name)
|
95
|
-
attr.name
|
96
|
-
elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
|
97
|
-
attr[i].name
|
98
|
-
else
|
99
|
-
nil
|
100
|
-
end
|
101
|
-
|
102
|
-
filtered_params = filter(attribute_name, casted_params[i])
|
103
|
-
|
104
|
-
binds << render_bind(attr, filtered_params)
|
105
|
-
end
|
106
|
-
binds = binds.inspect
|
107
|
-
binds.prepend(" ")
|
108
|
-
end
|
109
|
-
return binds
|
110
|
-
end
|
111
|
-
def colorize_sql(sql)
|
112
|
-
color(sql, sql_color(sql), bold: true)
|
113
|
-
end
|
114
|
-
else
|
115
|
-
def render_binds(payload)
|
116
|
-
binds = nil
|
117
|
-
unless (payload[:binds] || []).empty?
|
118
|
-
casted_params = type_casted_binds(payload[:type_casted_binds])
|
119
|
-
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
120
|
-
render_bind(attr, value)
|
121
|
-
}.inspect
|
122
|
-
end
|
123
|
-
return binds
|
124
|
-
end
|
125
|
-
def colorize_sql(sql)
|
126
|
-
color(sql, sql_color(sql), true)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
require "active_record/railties/controller_runtime"
|
132
|
-
|
133
|
-
module Railties
|
134
|
-
module ControllerRuntime
|
135
|
-
remove_method :cleanup_view_runtime
|
136
|
-
def cleanup_view_runtime
|
137
|
-
# this method has been redefined to do nothing for activerecord on purpose
|
138
|
-
super
|
139
|
-
end
|
140
|
-
|
141
|
-
remove_method :append_info_to_payload
|
142
|
-
def append_info_to_payload(payload)
|
143
|
-
super
|
144
|
-
if ActiveRecord::Base.connected?
|
145
|
-
payload[:db_runtime] = TimeBandits::TimeConsumers::Database.instance.consumed
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
module ClassMethods
|
150
|
-
# this method has been redefined to do nothing for activerecord on purpose
|
151
|
-
remove_method :log_process_action
|
152
|
-
def log_process_action(payload)
|
153
|
-
super
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
1
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("7.1.0")
|
2
|
+
require_relative "active_record/log_subscriber"
|
3
|
+
else
|
4
|
+
require_relative "active_record/runtime_registry"
|
158
5
|
end
|
6
|
+
require_relative "active_record/railties/controller_runtime"
|
@@ -13,6 +13,18 @@ module TimeBandits
|
|
13
13
|
fields :time, :calls, :sql_query_cache_hits
|
14
14
|
format "ActiveRecord: %.3fms(%dq,%dh)", :time, :calls, :sql_query_cache_hits
|
15
15
|
|
16
|
+
class << self
|
17
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.1.0")
|
18
|
+
def metrics_store
|
19
|
+
ActiveRecord::RuntimeRegistry
|
20
|
+
end
|
21
|
+
else
|
22
|
+
def metrics_store
|
23
|
+
ActiveRecord::LogSubscriber
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
16
28
|
def reset
|
17
29
|
reset_stats
|
18
30
|
super
|
@@ -27,13 +39,13 @@ module TimeBandits
|
|
27
39
|
end
|
28
40
|
|
29
41
|
def current_runtime
|
30
|
-
Database.instance.time +
|
42
|
+
Database.instance.time + self.class.metrics_store.runtime
|
31
43
|
end
|
32
44
|
|
33
45
|
private
|
34
46
|
|
35
47
|
def reset_stats
|
36
|
-
s =
|
48
|
+
s = self.class.metrics_store
|
37
49
|
hits = s.reset_query_cache_hits
|
38
50
|
calls = s.reset_call_count
|
39
51
|
time = s.reset_runtime
|
data/lib/time_bandits/version.rb
CHANGED
data/test/unit/database_test.rb
CHANGED
@@ -10,6 +10,15 @@ class DatabaseTest < Test::Unit::TestCase
|
|
10
10
|
TimeBandits.reset
|
11
11
|
@old_logger = ActiveRecord::Base.logger
|
12
12
|
ActiveRecord::Base.logger = Logger.new($stdout)
|
13
|
+
ActiveRecord::Base.logger.level = Logger::DEBUG
|
14
|
+
|
15
|
+
ActiveRecord::Base.establish_connection(
|
16
|
+
adapter: "mysql2",
|
17
|
+
username: "root",
|
18
|
+
encoding: "utf8",
|
19
|
+
host: ENV['MYSQL_HOST'] || "127.0.0.1",
|
20
|
+
port: (ENV['MYSQL_PORT'] || 3601).to_i
|
21
|
+
)
|
13
22
|
end
|
14
23
|
|
15
24
|
def teardown
|
@@ -28,18 +37,18 @@ class DatabaseTest < Test::Unit::TestCase
|
|
28
37
|
end
|
29
38
|
|
30
39
|
test "formatting" do
|
31
|
-
|
32
|
-
|
33
|
-
|
40
|
+
metrics_store.runtime += 1.234
|
41
|
+
metrics_store.call_count += 3
|
42
|
+
metrics_store.query_cache_hits += 1
|
34
43
|
TimeBandits.consumed
|
35
44
|
assert_equal "ActiveRecord: 1.234ms(3q,1h)", TimeBandits.runtime
|
36
45
|
end
|
37
46
|
|
38
47
|
test "accessing current runtime" do
|
39
|
-
|
48
|
+
metrics_store.runtime += 1.234
|
40
49
|
assert_equal 1.234, TimeBandits.consumed
|
41
|
-
assert_equal 0,
|
42
|
-
|
50
|
+
assert_equal 0, metrics_store.runtime
|
51
|
+
metrics_store.runtime += 4.0
|
43
52
|
assert_equal 5.234, bandit.current_runtime
|
44
53
|
assert_equal "ActiveRecord: 1.234ms(0q,0h)", TimeBandits.runtime
|
45
54
|
end
|
@@ -52,12 +61,38 @@ class DatabaseTest < Test::Unit::TestCase
|
|
52
61
|
assert_nil log_subscriber.new.sql(event)
|
53
62
|
end
|
54
63
|
|
64
|
+
test "instrumentation records runtimes at log level debug" do
|
65
|
+
ActiveRecord::Base.logger.stubs(:debug)
|
66
|
+
ActiveRecord::Base.connection.execute "SELECT 1"
|
67
|
+
bandit.consumed
|
68
|
+
assert(bandit.current_runtime > 0)
|
69
|
+
# 2 calls, because one configures the connection
|
70
|
+
assert_equal 2, bandit.calls
|
71
|
+
assert_equal 0, bandit.sql_query_cache_hits
|
72
|
+
end
|
73
|
+
|
74
|
+
test "instrumentation records runtimes at log level error" do
|
75
|
+
skip if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("7.1.0")
|
76
|
+
ActiveRecord::Base.logger.level = Logger::ERROR
|
77
|
+
ActiveRecord::LogSubscriber.expects(:sql).never
|
78
|
+
ActiveRecord::Base.connection.execute "SELECT 1"
|
79
|
+
bandit.consumed
|
80
|
+
assert(bandit.current_runtime > 0)
|
81
|
+
# 2 calls, because one configures the connection
|
82
|
+
assert_equal 2, bandit.calls
|
83
|
+
assert_equal 0, bandit.sql_query_cache_hits
|
84
|
+
end
|
85
|
+
|
55
86
|
private
|
56
87
|
|
57
88
|
def bandit
|
58
89
|
TimeBandits::TimeConsumers::Database.instance
|
59
90
|
end
|
60
91
|
|
92
|
+
def metrics_store
|
93
|
+
TimeBandits::TimeConsumers::Database.metrics_store
|
94
|
+
end
|
95
|
+
|
61
96
|
def log_subscriber
|
62
97
|
ActiveRecord::LogSubscriber
|
63
98
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_bandits
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.14.
|
4
|
+
version: 0.14.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Kaes
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-10-
|
11
|
+
date: 2023-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thread_variables
|
@@ -240,6 +240,9 @@ files:
|
|
240
240
|
- lib/time_bandits.rb
|
241
241
|
- lib/time_bandits/monkey_patches/action_controller.rb
|
242
242
|
- lib/time_bandits/monkey_patches/active_record.rb
|
243
|
+
- lib/time_bandits/monkey_patches/active_record/log_subscriber.rb
|
244
|
+
- lib/time_bandits/monkey_patches/active_record/railties/controller_runtime.rb
|
245
|
+
- lib/time_bandits/monkey_patches/active_record/runtime_registry.rb
|
243
246
|
- lib/time_bandits/monkey_patches/memcache-client.rb
|
244
247
|
- lib/time_bandits/monkey_patches/memcached.rb
|
245
248
|
- lib/time_bandits/monkey_patches/redis.rb
|