activerecord-db-metrics 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a297908043fb841e5a7f8a38722644bd73143ee7160bc21c044c1383825c4564
4
+ data.tar.gz: 6c6b6b134bc84e2cb4446179bf685dee8c153a4afb64abf1828bf3583b8af85c
5
+ SHA512:
6
+ metadata.gz: 97c52f7e25b80bf5088d3742ef16d2fac1978b5c4c7d6c58193549e1c60eeb2ffe7ca75ec0aa647519d541ca315c3087af444b1c7498a119c70deb38659ef47f
7
+ data.tar.gz: 2e46c1a3b9ec8d25f1bccdbe54776fafa2d76db4aeead57618b5a04043ea17080a7428be973d9549f4bba2402ad844ea9864b9b5fca02c99182a62697db2a57e
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-12-07
9
+
10
+ ### Added
11
+ - Initial release
12
+ - Track database queries per request
13
+ - Breakdown by table and CRUD operation type
14
+ - Support for bulk operations (insert_all, upsert_all)
15
+ - Controller helper for easy integration
16
+ - Manual collector for custom usage
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Masato Kato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # ActiveRecord::Db::Metrics
2
+
3
+ A lightweight gem to monitor and measure database queries in Rails applications. Track CRUD operations per table with support for bulk operations like `insert_all`.
4
+
5
+ ## Features
6
+
7
+ - 📊 Track database queries per request
8
+ - 🔍 Breakdown by table and operation type (INSERT, SELECT, UPDATE, DELETE)
9
+ - đŸ’Ē Support for bulk operations (`insert_all`, `upsert_all`)
10
+ - đŸĒļ Lightweight with minimal performance overhead
11
+ - đŸŽ¯ Easy integration with Rails controllers
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'activerecord-db-metrics'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ ```bash
24
+ bundle install
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Basic Setup
30
+
31
+ Include the helper module in your controller and add the `around_action`:
32
+
33
+ ```ruby
34
+ class ApplicationController < ActionController::Base
35
+ include ActiveRecord::Db::Metrics::ControllerHelper
36
+ around_action :measure_db_operations
37
+ end
38
+ ```
39
+
40
+ Now, every request will log database metrics like this:
41
+
42
+ ```
43
+ --- DB Metrics for Request ---
44
+ Total DB Queries: 15
45
+ [USERS] I:1, S:10, U:2, D:0 (Total: 13)
46
+ [POSTS] I:0, S:2, U:0, D:0 (Total: 2)
47
+ ------------------------------
48
+ ```
49
+
50
+ ### Selective Monitoring
51
+
52
+ You can apply metrics to specific controllers or actions:
53
+
54
+ ```ruby
55
+ class UsersController < ApplicationController
56
+ include ActiveRecord::Db::Metrics::ControllerHelper
57
+ around_action :measure_db_operations, only: [:index, :show]
58
+ end
59
+ ```
60
+
61
+ ### Custom Logging
62
+
63
+ Override the `log_db_metrics` method to customize logging behavior:
64
+
65
+ ```ruby
66
+ class ApplicationController < ActionController::Base
67
+ include ActiveRecord::Db::Metrics::ControllerHelper
68
+ around_action :measure_db_operations
69
+
70
+ private
71
+
72
+ def log_db_metrics(results)
73
+ # Send to your monitoring service
74
+ StatsD.gauge('db.queries.total', results[:total_queries])
75
+
76
+ results[:crud_operations_by_table].each do |table, counts|
77
+ counts.each do |operation, count|
78
+ StatsD.gauge("db.queries.#{table}.#{operation.downcase}", count)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ ```
84
+
85
+ ### Manual Usage
86
+
87
+ You can also use the collector directly without the controller helper:
88
+
89
+ ```ruby
90
+ collector = ActiveRecord::Db::Metrics::Collector.new
91
+ collector.start_monitoring
92
+
93
+ # Your database operations here
94
+ User.all
95
+ User.create(name: "Alice")
96
+
97
+ collector.stop_monitoring
98
+ results = collector.results
99
+
100
+ # => {
101
+ # total_queries: 2,
102
+ # crud_operations_by_table: {
103
+ # users: { SELECT: 1, INSERT: 1 }
104
+ # }
105
+ # }
106
+ ```
107
+
108
+ ## How It Works
109
+
110
+ The gem subscribes to `ActiveSupport::Notifications` for SQL events and tracks:
111
+
112
+ 1. **Total query count**: How many SQL queries were executed
113
+ 2. **Operations by table**: Breakdown of INSERT, SELECT, UPDATE, DELETE per table
114
+ 3. **Accurate row counts**: Uses Rails 7.2+ `payload[:row_count]` for SELECT queries and `payload[:affected_rows]` for INSERT/UPDATE/DELETE operations
115
+
116
+ ### Bulk Operations
117
+
118
+ This gem correctly counts the actual rows affected in bulk operations:
119
+
120
+ ```ruby
121
+ User.insert_all([{name: "A"}, {name: "B"}, {name: "C"}])
122
+ # => Counted as INSERT: 3 ✅
123
+ ```
124
+
125
+ ## Requirements
126
+
127
+ - Ruby >= 3.4.0
128
+ - Rails >= 7.2
129
+ - ActiveRecord >= 7.2
130
+
131
+ This gem requires Rails 7.2+ to utilize the `row_count` and `affected_rows` fields in `sql.active_record` notifications ([PR #50887](https://github.com/rails/rails/pull/50887)).
132
+
133
+ ## Development
134
+
135
+ After checking out the repo, run:
136
+
137
+ ```bash
138
+ bundle install
139
+ ```
140
+
141
+ To run tests:
142
+
143
+ ```bash
144
+ rake spec
145
+ ```
146
+
147
+ To build the gem:
148
+
149
+ ```bash
150
+ gem build activerecord-db-metrics.gemspec
151
+ ```
152
+
153
+ ## Contributing
154
+
155
+ Bug reports and pull requests are welcome on GitHub.
156
+
157
+ ## License
158
+
159
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Db
5
+ module Metrics
6
+ # Collects and tracks database operation metrics during a request lifecycle
7
+ class Collector
8
+ CRUD_KEYWORDS = %w[INSERT UPDATE DELETE SELECT].freeze
9
+
10
+ def initialize
11
+ @query_count = 0
12
+ # Track CRUD operations per table
13
+ # Example: { users: { SELECT: 5, INSERT: 1 }, posts: { SELECT: 2 } }
14
+ @crud_counts_by_table = Hash.new { |hash, key| hash[key] = Hash.new(0) }
15
+ end
16
+
17
+ # Subscribe to ActiveRecord SQL notifications
18
+ def start_monitoring
19
+ @subscriber = ActiveSupport::Notifications.subscribe('sql.active_record', log_subscriber)
20
+ end
21
+
22
+ # Unsubscribe from ActiveRecord SQL notifications
23
+ def stop_monitoring
24
+ ActiveSupport::Notifications.unsubscribe(@subscriber) if @subscriber
25
+ end
26
+
27
+ # Get collected metrics
28
+ # @return [Hash] Hash containing :total_queries and :crud_operations_by_table
29
+ def results
30
+ {
31
+ total_queries: @query_count,
32
+ crud_operations_by_table: @crud_counts_by_table
33
+ }
34
+ end
35
+
36
+ private
37
+
38
+ def log_subscriber
39
+ lambda do |_name, _start, _finish, _id, payload|
40
+ next unless payload[:sql] && payload[:name] != 'SCHEMA'
41
+
42
+ @query_count += 1
43
+
44
+ # 1. Identify operation type (INSERT, SELECT, etc.)
45
+ operation = payload[:sql].strip.upcase.split.first
46
+
47
+ next unless CRUD_KEYWORDS.include?(operation)
48
+
49
+ # 2. Identify table name
50
+ table_name = payload[:table_name] || extract_table_name_from_sql(payload[:sql], operation)
51
+
52
+ next unless table_name
53
+
54
+ # 3. Get actual row count and record it
55
+ row_count = extract_row_count(payload, operation)
56
+ @crud_counts_by_table[table_name.to_sym][operation.to_sym] += row_count
57
+ end
58
+ end
59
+
60
+ # Extract actual row count from payload
61
+ # Rails 7.2+ provides:
62
+ # - payload[:row_count] for SELECT (number of rows returned)
63
+ # - payload[:affected_rows] for INSERT/UPDATE/DELETE (number of rows affected)
64
+ def extract_row_count(payload, operation)
65
+ case operation
66
+ when 'SELECT'
67
+ payload[:row_count] || 0
68
+ when 'INSERT', 'UPDATE', 'DELETE'
69
+ payload[:affected_rows] || 0
70
+ else
71
+ 0
72
+ end
73
+ end
74
+
75
+ # Extract table name from SQL as fallback when payload[:table_name] is unavailable
76
+ def extract_table_name_from_sql(sql, operation)
77
+ sql_downcase = sql.downcase
78
+
79
+ case operation
80
+ when 'SELECT'
81
+ # Pattern: SELECT "users".* FROM "users"
82
+ match = sql_downcase.match(/from\s+["`]?(\w+)["`]?/)
83
+ match[1] if match
84
+ when 'INSERT'
85
+ # Pattern: INSERT INTO "users"
86
+ match = sql_downcase.match(/insert\s+into\s+["`]?(\w+)["`]?/)
87
+ match[1] if match
88
+ when 'UPDATE'
89
+ # Pattern: UPDATE "users" SET
90
+ match = sql_downcase.match(/update\s+["`]?(\w+)["`]?\s+set/)
91
+ match[1] if match
92
+ when 'DELETE'
93
+ # Pattern: DELETE FROM "users"
94
+ match = sql_downcase.match(/delete\s+from\s+["`]?(\w+)["`]?/)
95
+ match[1] if match
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Db
5
+ module Metrics
6
+ # Helper module to be included in Rails controllers
7
+ # Provides convenient methods to measure database operations
8
+ #
9
+ # @example Basic usage
10
+ # class ApplicationController < ActionController::Base
11
+ # include ActiveRecord::Db::Metrics::ControllerHelper
12
+ # around_action :measure_db_operations
13
+ # end
14
+ module ControllerHelper
15
+ extend ActiveSupport::Concern
16
+
17
+ private
18
+
19
+ # Measure database operations for the current action
20
+ # This method should be used as an around_action filter
21
+ def measure_db_operations
22
+ metrics = Collector.new
23
+ metrics.start_monitoring
24
+
25
+ yield # Execute the action
26
+
27
+ metrics.stop_monitoring
28
+ results = metrics.results
29
+
30
+ log_db_metrics(results)
31
+ end
32
+
33
+ # Log database metrics
34
+ # Override this method in your controller to customize logging behavior
35
+ #
36
+ # @param results [Hash] Metrics results containing :total_queries and :crud_operations_by_table
37
+ def log_db_metrics(results)
38
+ Rails.logger.info '--- DB Metrics for Request ---'
39
+ Rails.logger.info "Total DB Queries: #{results[:total_queries]}"
40
+
41
+ # Show breakdown by table
42
+ results[:crud_operations_by_table].each do |table, counts|
43
+ operations = %i[INSERT SELECT UPDATE DELETE].map do |op|
44
+ "#{op.to_s[0]}:#{counts[op]}"
45
+ end.join(', ')
46
+ total = counts.values.sum
47
+ Rails.logger.info " [#{table.upcase}] #{operations} (Total: #{total})"
48
+ end
49
+
50
+ Rails.logger.info '------------------------------'
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Db
5
+ module Metrics
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_record'
5
+
6
+ require_relative 'activerecord/db/metrics/version'
7
+ require_relative 'activerecord/db/metrics/collector'
8
+ require_relative 'activerecord/db/metrics/controller_helper'
9
+
10
+ module ActiveRecord
11
+ module Db
12
+ module Metrics
13
+ class Error < StandardError; end
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-db-metrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Masato Kato
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.2'
40
+ description: A lightweight gem to monitor and measure database queries in Rails applications,
41
+ providing detailed metrics on CRUD operations per table including support for bulk
42
+ operations like insert_all.
43
+ email:
44
+ - masatokato0000@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - LICENSE.txt
51
+ - README.md
52
+ - lib/activerecord-db-metrics.rb
53
+ - lib/activerecord/db/metrics/collector.rb
54
+ - lib/activerecord/db/metrics/controller_helper.rb
55
+ - lib/activerecord/db/metrics/version.rb
56
+ homepage: https://github.com/00Masato/activerecord-db-metrics
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://github.com/00Masato/activerecord-db-metrics
61
+ source_code_uri: https://github.com/00Masato/activerecord-db-metrics
62
+ changelog_uri: https://github.com/00Masato/activerecord-db-metrics/blob/main/CHANGELOG.md
63
+ rubygems_mfa_required: 'true'
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.4.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.7
79
+ specification_version: 4
80
+ summary: Track and analyze ActiveRecord database operations per request
81
+ test_files: []