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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e14e3f3974575197ad97649241f327dbf69824c354044bde8bd55ff421d9c21a
4
- data.tar.gz: 4115bb4f9820a3227a62d6b4ea66c5f045c59cacb019fd89be71ff9da6b2f790
3
+ metadata.gz: 3597bdf1515a72122e85b16fffdcedc74a22c479012fa0fb2f7c42f64e0434de
4
+ data.tar.gz: a6acc865643fc97ce247b33fd55da8399323488435234d15d75766b26fc853c5
5
5
  SHA512:
6
- metadata.gz: abc74cb6ae11bdf9388f8d113a3df89d61e6403cd614ebdd8d73aaa5be84c8a2d9d50d746b57a2eb4079140e1a34c9e1e3c172428e1065d15893ad94e99330b6
7
- data.tar.gz: bd2347a6f49613abd7359524c1a08cb609acf501180b1deeb5b093318b664ff330c7a0bd8b2a577b1dee303d62ea8481162485b704d4f9041000cc58b3dd77a2
6
+ metadata.gz: c1451d6f063d1fd7feb18dd66c0650f10bf8398f7407fd4f3f7a63bef57a4bf52846267e8bf82c4e7295e41f1d999b70a16aadd1a08d1fc44fc9f174e18e74a4
7
+ data.tar.gz: cb1ff1654db60be898992d20a9b7b4825483033637147e94b8fc0192c2566ef096ce49f14b71f7ffd61a6524fa883030c78a69feb10eb90d012bca20321618a6
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  strategy:
15
15
  matrix:
16
- ruby-version: [3.2.0, 3.1.3, 3.0.5, 2.7.7]
16
+ ruby-version: [3.2.2, 3.1.4, 3.0.6, 2.7.8]
17
17
 
18
18
  steps:
19
19
  - uses: actions/checkout@v3
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
@@ -4,6 +4,3 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "hiredis-client"
7
-
8
- # Use patched appraisal gem until it is fixed upstream.
9
- gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git", ref: "0c855ae0da89fec74b4d1a01801c55b0e72496d4"
data/docker-compose.yml CHANGED
@@ -3,7 +3,7 @@ version: '2'
3
3
  services:
4
4
  mysql:
5
5
  container_name: mysql
6
- image: mysql:5.7
6
+ image: mysql:8
7
7
  ports:
8
8
  - "3601:3306"
9
9
  environment:
@@ -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
- # 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
- 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 + 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.13.1"
2
+ VERSION = "0.14.1"
3
3
  end
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
@@ -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.13.1
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-01-28 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
@@ -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.1
295
+ rubygems_version: 3.4.10
293
296
  signing_key:
294
297
  specification_version: 4
295
298
  summary: Custom performance logging for Rails