time_bandits 0.14.0 → 0.14.1
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.
- 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
|