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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5b75c63570dfa36709209cb0e1abe7fd8842bc795c7e0c5dd15a285af090bf6
4
- data.tar.gz: afb24d0ea48efe0ec1505be1550ece1cb31af9c4b1eb5df55b96e2c1898e90b4
3
+ metadata.gz: 3597bdf1515a72122e85b16fffdcedc74a22c479012fa0fb2f7c42f64e0434de
4
+ data.tar.gz: a6acc865643fc97ce247b33fd55da8399323488435234d15d75766b26fc853c5
5
5
  SHA512:
6
- metadata.gz: 05a7d021d2b0242a5289450fa385dc86d6c3e2512c4a331e40a3428d7d5ab19be347544a32bac8df2a1ba726c898a6ba8b51a698312107a5a7730995a45cfacd
7
- data.tar.gz: 953e21a301535b9384263fd1d686e5dd4eea45ca25bd5b64191f979b6b83dde73e439f38428114e54e6ee83ce7baffc2fa16e7c492cabd070a0dcb29b409a4e3
6
+ metadata.gz: c1451d6f063d1fd7feb18dd66c0650f10bf8398f7407fd4f3f7a63bef57a4bf52846267e8bf82c4e7295e41f1d999b70a16aadd1a08d1fc44fc9f174e18e74a4
7
+ data.tar.gz: cb1ff1654db60be898992d20a9b7b4825483033637147e94b8fc0192c2566ef096ce49f14b71f7ffd61a6524fa883030c78a69feb10eb90d012bca20321618a6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.14.1
4
+
5
+ * Fixed that ActiveReccord metrics where only collected at log level DEBUG.
6
+
3
7
  ## 0.14.0
4
8
 
5
9
  * Support Rails 7.1.0.
@@ -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
- # this file monkey patches class ActiveRecord::LogSubscriber
2
- # to count the number of sql statements being executed
3
- # and the number of query cache hits
4
- # it needs to be adapted to each new rails version
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 + ActiveRecord::LogSubscriber.runtime
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 = ActiveRecord::LogSubscriber
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
@@ -1,3 +1,3 @@
1
1
  module TimeBandits
2
- VERSION = "0.14.0"
2
+ VERSION = "0.14.1"
3
3
  end
@@ -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
- log_subscriber.runtime += 1.234
32
- log_subscriber.call_count += 3
33
- log_subscriber.query_cache_hits += 1
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
- log_subscriber.runtime += 1.234
48
+ metrics_store.runtime += 1.234
40
49
  assert_equal 1.234, TimeBandits.consumed
41
- assert_equal 0, log_subscriber.runtime
42
- log_subscriber.runtime += 4.0
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.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-10-05 00:00:00.000000000 Z
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