time_bandits 0.13.1 → 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/.github/workflows/run-tests.yml +1 -1
- data/Appraisals +4 -3
- data/CHANGELOG.md +8 -0
- data/Gemfile +0 -3
- data/docker-compose.yml +1 -1
- 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 -104
- data/lib/time_bandits/time_consumers/database.rb +14 -2
- data/lib/time_bandits/version.rb +1 -1
- data/test/test_helper.rb +1 -0
- data/test/unit/database_test.rb +41 -6
- metadata +6 -3
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/Appraisals
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
appraisals = [
|
2
|
-
"6.0.6",
|
3
|
-
"6.1.6",
|
2
|
+
"6.0.6.1",
|
3
|
+
"6.1.7.6",
|
4
|
+
"7.0.8",
|
5
|
+
"7.1.0"
|
4
6
|
]
|
5
7
|
|
6
8
|
appraisals.insert(0, "5.2.8.1") if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0.0")
|
7
|
-
appraisals << "7.0.4" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
|
8
9
|
|
9
10
|
appraisals.each do |rails_version|
|
10
11
|
%w(4.0 5.0).each do |redis_version|
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.14.1
|
4
|
+
|
5
|
+
* Fixed that ActiveReccord metrics where only collected at log level DEBUG.
|
6
|
+
|
7
|
+
## 0.14.0
|
8
|
+
|
9
|
+
* Support Rails 7.1.0.
|
10
|
+
|
3
11
|
## 0.13.1
|
4
12
|
* Fixed last place that tried to log a monotonic clock timestamp
|
5
13
|
instead of a Time instance.
|
data/Gemfile
CHANGED
data/docker-compose.yml
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,105 +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
|
-
def self.call_count=(value)
|
13
|
-
Thread.current.thread_variable_set(:active_record_sql_call_count, value)
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.call_count
|
17
|
-
Thread.current.thread_variable_get(:active_record_sql_call_count) ||
|
18
|
-
Thread.current.thread_variable_set(:active_record_sql_call_count, 0)
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.query_cache_hits=(value)
|
22
|
-
Thread.current.thread_variable_set(:active_record_sql_query_cache_hits, value)
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.query_cache_hits
|
26
|
-
Thread.current.thread_variable_get(:active_record_sql_query_cache_hits) ||
|
27
|
-
Thread.current.thread_variable_set(:active_record_sql_query_cache_hits, 0)
|
28
|
-
end
|
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
|
-
remove_method :sql
|
43
|
-
def sql(event)
|
44
|
-
payload = event.payload
|
45
|
-
|
46
|
-
self.class.runtime += event.duration
|
47
|
-
self.class.call_count += 1
|
48
|
-
self.class.query_cache_hits += 1 if payload[:cached] || payload[:name] == "CACHE"
|
49
|
-
|
50
|
-
return unless logger.debug?
|
51
|
-
|
52
|
-
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
53
|
-
|
54
|
-
log_sql_statement(payload, event)
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
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
|
69
|
-
end
|
70
|
-
|
71
|
-
name = colorize_payload_name(name, payload[:name])
|
72
|
-
sql = color(sql, sql_color(sql), true)
|
73
|
-
|
74
|
-
debug " #{name} #{sql}#{binds}"
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
require "active_record/railties/controller_runtime"
|
79
|
-
|
80
|
-
module Railties
|
81
|
-
module ControllerRuntime
|
82
|
-
remove_method :cleanup_view_runtime
|
83
|
-
def cleanup_view_runtime
|
84
|
-
# this method has been redefined to do nothing for activerecord on purpose
|
85
|
-
super
|
86
|
-
end
|
87
|
-
|
88
|
-
remove_method :append_info_to_payload
|
89
|
-
def append_info_to_payload(payload)
|
90
|
-
super
|
91
|
-
if ActiveRecord::Base.connected?
|
92
|
-
payload[:db_runtime] = TimeBandits::TimeConsumers::Database.instance.consumed
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
module ClassMethods
|
97
|
-
# this method has been redefined to do nothing for activerecord on purpose
|
98
|
-
remove_method :log_process_action
|
99
|
-
def log_process_action(payload)
|
100
|
-
super
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
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"
|
105
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/test_helper.rb
CHANGED
@@ -23,6 +23,7 @@ ActiveSupport::LogSubscriber.logger =::Logger.new("/dev/null")
|
|
23
23
|
# fake Rails
|
24
24
|
module Rails
|
25
25
|
extend self
|
26
|
+
ActiveSupport::Cache.format_version = 7.1 if Gem::Version.new(ActiveSupport::VERSION::STRING) >= Gem::Version.new("7.1.0")
|
26
27
|
def cache
|
27
28
|
@cache ||= ActiveSupport::Cache.lookup_store(:mem_cache_store)
|
28
29
|
end
|
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.
|
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-
|
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
|
@@ -289,7 +292,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
289
292
|
- !ruby/object:Gem::Version
|
290
293
|
version: '0'
|
291
294
|
requirements: []
|
292
|
-
rubygems_version: 3.4.
|
295
|
+
rubygems_version: 3.4.10
|
293
296
|
signing_key:
|
294
297
|
specification_version: 4
|
295
298
|
summary: Custom performance logging for Rails
|