time_bandits 0.13.1 → 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/.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
|