rails_console_pro 0.1.3 → 0.1.4
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/.rspec_status +259 -232
- data/CHANGELOG.md +3 -0
- data/QUICK_START.md +9 -0
- data/README.md +27 -0
- data/docs/MODEL_INTROSPECTION.md +371 -0
- data/docs/QUERY_BUILDER.md +385 -0
- data/lib/rails_console_pro/commands/compare_command.rb +151 -0
- data/lib/rails_console_pro/commands/introspect_command.rb +220 -0
- data/lib/rails_console_pro/commands/query_builder_command.rb +43 -0
- data/lib/rails_console_pro/commands.rb +15 -0
- data/lib/rails_console_pro/compare_result.rb +81 -0
- data/lib/rails_console_pro/configuration.rb +12 -0
- data/lib/rails_console_pro/format_exporter.rb +24 -0
- data/lib/rails_console_pro/global_methods.rb +12 -0
- data/lib/rails_console_pro/initializer.rb +18 -1
- data/lib/rails_console_pro/introspect_result.rb +101 -0
- data/lib/rails_console_pro/printers/compare_printer.rb +138 -0
- data/lib/rails_console_pro/printers/introspect_printer.rb +282 -0
- data/lib/rails_console_pro/printers/query_builder_printer.rb +81 -0
- data/lib/rails_console_pro/query_builder.rb +197 -0
- data/lib/rails_console_pro/query_builder_result.rb +66 -0
- data/lib/rails_console_pro/serializers/compare_serializer.rb +66 -0
- data/lib/rails_console_pro/serializers/introspect_serializer.rb +99 -0
- data/lib/rails_console_pro/serializers/query_builder_serializer.rb +35 -0
- data/lib/rails_console_pro/services/introspection_collector.rb +420 -0
- data/lib/rails_console_pro/snippets/collection_result.rb +1 -0
- data/lib/rails_console_pro/snippets.rb +1 -0
- data/lib/rails_console_pro/version.rb +1 -1
- metadata +17 -1
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Query Builder & Comparator
|
|
2
|
+
|
|
3
|
+
Compare different query strategies and build optimized ActiveRecord queries interactively.
|
|
4
|
+
|
|
5
|
+
## Query Comparison
|
|
6
|
+
|
|
7
|
+
Compare multiple query approaches side-by-side to find the optimal strategy.
|
|
8
|
+
|
|
9
|
+
### Basic Usage
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Compare different query strategies
|
|
13
|
+
compare do |c|
|
|
14
|
+
c.run("Eager loading") { User.includes(:posts).to_a }
|
|
15
|
+
c.run("N+1") { User.all.map(&:posts) }
|
|
16
|
+
c.run("Select specific") { User.select(:id, :email).to_a }
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### What Gets Compared
|
|
21
|
+
|
|
22
|
+
The comparison tracks:
|
|
23
|
+
- **Execution Time**: Wall-clock time in milliseconds
|
|
24
|
+
- **Query Count**: Number of SQL queries executed
|
|
25
|
+
- **Memory Usage**: Memory consumption (platform-dependent)
|
|
26
|
+
- **SQL Queries**: All SQL queries with their execution times
|
|
27
|
+
- **Errors**: Any exceptions that occur during execution
|
|
28
|
+
|
|
29
|
+
### Example Output
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
═══════════════════════════════════════════════════════════
|
|
33
|
+
⚖️ QUERY COMPARISON
|
|
34
|
+
═══════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
📊 Summary:
|
|
37
|
+
Total Strategies: 3
|
|
38
|
+
Fastest: Eager loading
|
|
39
|
+
Slowest: N+1
|
|
40
|
+
Performance Ratio: 5.2x slower
|
|
41
|
+
|
|
42
|
+
📈 Detailed Results:
|
|
43
|
+
|
|
44
|
+
#1 Eager loading
|
|
45
|
+
⏱️ Duration: 45.23ms
|
|
46
|
+
🔢 Queries: 2
|
|
47
|
+
💾 Memory: 1.2 MB
|
|
48
|
+
📝 SQL Queries (2 total):
|
|
49
|
+
1. SELECT "users".* FROM "users" (12.5ms)
|
|
50
|
+
2. SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) (32.7ms)
|
|
51
|
+
|
|
52
|
+
#2 Select specific
|
|
53
|
+
⏱️ Duration: 28.15ms
|
|
54
|
+
🔢 Queries: 1
|
|
55
|
+
💾 Memory: 0.8 MB
|
|
56
|
+
📝 SQL Queries (1 total):
|
|
57
|
+
1. SELECT "users"."id", "users"."email" FROM "users" (28.1ms)
|
|
58
|
+
|
|
59
|
+
#3 N+1
|
|
60
|
+
⏱️ Duration: 234.67ms
|
|
61
|
+
🔢 Queries: 101
|
|
62
|
+
💾 Memory: 2.5 MB
|
|
63
|
+
📝 SQL Queries (101 total):
|
|
64
|
+
1. SELECT "users".* FROM "users" (15.2ms)
|
|
65
|
+
2. SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 (2.1ms)
|
|
66
|
+
... and 99 more
|
|
67
|
+
|
|
68
|
+
🏆 Winner: Select specific
|
|
69
|
+
This strategy is 8.3x faster than the slowest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Advanced Comparison
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# Compare complex scenarios
|
|
76
|
+
compare do |c|
|
|
77
|
+
c.run("With joins") do
|
|
78
|
+
User.joins(:posts)
|
|
79
|
+
.where(posts: { published: true })
|
|
80
|
+
.distinct
|
|
81
|
+
.to_a
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
c.run("With includes") do
|
|
85
|
+
User.includes(:posts)
|
|
86
|
+
.where(posts: { published: true })
|
|
87
|
+
.to_a
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
c.run("Subquery") do
|
|
91
|
+
User.where(id: Post.published.select(:user_id)).to_a
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Error Handling
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Comparisons continue even if one strategy fails
|
|
100
|
+
compare do |c|
|
|
101
|
+
c.run("Valid query") { User.all.to_a }
|
|
102
|
+
c.run("Invalid query") { User.where(nonexistent: true).to_a }
|
|
103
|
+
c.run("Another valid") { User.limit(10).to_a }
|
|
104
|
+
end
|
|
105
|
+
# Shows errors for failed strategies but continues with others
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Export Comparison Results
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
result = compare do |c|
|
|
112
|
+
c.run("Strategy 1") { User.includes(:posts).to_a }
|
|
113
|
+
c.run("Strategy 2") { User.all.map(&:posts) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Export to JSON
|
|
117
|
+
result.to_json
|
|
118
|
+
result.export_to_file('comparison.json')
|
|
119
|
+
|
|
120
|
+
# Export to YAML
|
|
121
|
+
result.to_yaml
|
|
122
|
+
result.export_to_file('comparison.yaml')
|
|
123
|
+
|
|
124
|
+
# Export to HTML
|
|
125
|
+
result.to_html
|
|
126
|
+
result.export_to_file('comparison.html')
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Interactive Query Builder
|
|
130
|
+
|
|
131
|
+
Build and analyze ActiveRecord queries using a fluent DSL.
|
|
132
|
+
|
|
133
|
+
### Basic Usage
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
# Build a query
|
|
137
|
+
query User do
|
|
138
|
+
where(active: true)
|
|
139
|
+
includes(:posts)
|
|
140
|
+
order(:created_at)
|
|
141
|
+
limit(10)
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Analyze Query Performance
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# Get SQL + explain plan
|
|
149
|
+
query User do
|
|
150
|
+
where(active: true)
|
|
151
|
+
includes(:posts)
|
|
152
|
+
order(:created_at)
|
|
153
|
+
limit(10)
|
|
154
|
+
end.analyze
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This shows:
|
|
158
|
+
- Generated SQL query
|
|
159
|
+
- Query execution plan (EXPLAIN)
|
|
160
|
+
- Index usage
|
|
161
|
+
- Performance recommendations
|
|
162
|
+
- Statistics
|
|
163
|
+
|
|
164
|
+
### Example Output
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
═══════════════════════════════════════════════════════════
|
|
168
|
+
🔧 QUERY BUILDER: User
|
|
169
|
+
═══════════════════════════════════════════════════════════
|
|
170
|
+
|
|
171
|
+
📝 Generated SQL:
|
|
172
|
+
SELECT "users".* FROM "users"
|
|
173
|
+
WHERE "users"."active" = $1
|
|
174
|
+
ORDER BY "users"."created_at" ASC
|
|
175
|
+
LIMIT $2
|
|
176
|
+
|
|
177
|
+
📊 Statistics:
|
|
178
|
+
Model User
|
|
179
|
+
Table users
|
|
180
|
+
|
|
181
|
+
🔬 Query Analysis:
|
|
182
|
+
═══════════════════════════════════════════════════════════
|
|
183
|
+
🔬 SQL EXPLAIN ANALYSIS
|
|
184
|
+
═══════════════════════════════════════════════════════════
|
|
185
|
+
|
|
186
|
+
📝 Query:
|
|
187
|
+
SELECT "users".* FROM "users" WHERE "users"."active" = $1 ORDER BY "users"."created_at" ASC LIMIT $2
|
|
188
|
+
|
|
189
|
+
⏱️ Execution Time: 12.45ms
|
|
190
|
+
|
|
191
|
+
📊 Query Plan:
|
|
192
|
+
✅ Index Scan using index_users_on_active on users
|
|
193
|
+
Index Cond: (active = true)
|
|
194
|
+
Sort Key: created_at
|
|
195
|
+
Rows: 10
|
|
196
|
+
|
|
197
|
+
🔍 Index Analysis:
|
|
198
|
+
✅ Indexes used:
|
|
199
|
+
• index_users_on_active
|
|
200
|
+
|
|
201
|
+
💡 Recommendations:
|
|
202
|
+
• Query is optimized with index usage
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Available Query Methods
|
|
206
|
+
|
|
207
|
+
The query builder supports all ActiveRecord::Relation methods:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
query User do
|
|
211
|
+
# Filtering
|
|
212
|
+
where(active: true)
|
|
213
|
+
where("created_at > ?", 1.week.ago)
|
|
214
|
+
where.not(deleted: true)
|
|
215
|
+
|
|
216
|
+
# Associations
|
|
217
|
+
includes(:posts, :comments)
|
|
218
|
+
joins(:posts)
|
|
219
|
+
left_joins(:profile)
|
|
220
|
+
|
|
221
|
+
# Selection
|
|
222
|
+
select(:id, :email, :name)
|
|
223
|
+
distinct
|
|
224
|
+
|
|
225
|
+
# Ordering
|
|
226
|
+
order(:created_at)
|
|
227
|
+
order(created_at: :desc)
|
|
228
|
+
order("created_at DESC, name ASC")
|
|
229
|
+
|
|
230
|
+
# Pagination
|
|
231
|
+
limit(10)
|
|
232
|
+
offset(20)
|
|
233
|
+
|
|
234
|
+
# Grouping
|
|
235
|
+
group(:status)
|
|
236
|
+
having("COUNT(*) > ?", 5)
|
|
237
|
+
|
|
238
|
+
# Other
|
|
239
|
+
readonly
|
|
240
|
+
lock
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Execute the Query
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# Build and execute
|
|
248
|
+
result = query User do
|
|
249
|
+
where(active: true)
|
|
250
|
+
limit(10)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Execute and get results
|
|
254
|
+
result.execute # Returns the relation
|
|
255
|
+
result.to_a # Returns array of records
|
|
256
|
+
result.count # Returns count
|
|
257
|
+
result.exists? # Returns boolean
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Chain Methods
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
# You can chain methods naturally
|
|
264
|
+
query User do
|
|
265
|
+
where(active: true)
|
|
266
|
+
includes(:posts)
|
|
267
|
+
order(:created_at)
|
|
268
|
+
limit(10)
|
|
269
|
+
end.analyze.to_a
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Without Block
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
# Build query programmatically
|
|
276
|
+
builder = query User
|
|
277
|
+
builder.where(active: true)
|
|
278
|
+
builder.includes(:posts)
|
|
279
|
+
builder.analyze
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Export Query Builder Results
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
result = query User do
|
|
286
|
+
where(active: true)
|
|
287
|
+
includes(:posts)
|
|
288
|
+
end.analyze
|
|
289
|
+
|
|
290
|
+
# Export to JSON
|
|
291
|
+
result.to_json
|
|
292
|
+
result.export_to_file('query.json')
|
|
293
|
+
|
|
294
|
+
# Export to YAML
|
|
295
|
+
result.to_yaml
|
|
296
|
+
result.export_to_file('query.yaml')
|
|
297
|
+
|
|
298
|
+
# Export to HTML
|
|
299
|
+
result.to_html
|
|
300
|
+
result.export_to_file('query.html')
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Use Cases
|
|
304
|
+
|
|
305
|
+
### Finding N+1 Problems
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
compare do |c|
|
|
309
|
+
c.run("N+1 Problem") do
|
|
310
|
+
User.all.map { |u| u.posts.count }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
c.run("Eager Loading") do
|
|
314
|
+
User.includes(:posts).map { |u| u.posts.count }
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
c.run("Counter Cache") do
|
|
318
|
+
User.select(:id, :posts_count).map(&:posts_count)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Optimizing Complex Queries
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
# Compare different approaches to the same problem
|
|
327
|
+
compare do |c|
|
|
328
|
+
c.run("Multiple Includes") do
|
|
329
|
+
User.includes(:posts, :comments, :profile).to_a
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
c.run("Nested Includes") do
|
|
333
|
+
User.includes(posts: :comments).to_a
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
c.run("Preload") do
|
|
337
|
+
User.preload(:posts, :comments, :profile).to_a
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Testing Query Performance
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
# Build and analyze before deploying
|
|
346
|
+
query User do
|
|
347
|
+
joins(:posts)
|
|
348
|
+
.where(posts: { published: true })
|
|
349
|
+
.group(:id)
|
|
350
|
+
.having("COUNT(posts.id) > ?", 5)
|
|
351
|
+
.order("COUNT(posts.id) DESC")
|
|
352
|
+
.limit(10)
|
|
353
|
+
end.analyze
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Features
|
|
357
|
+
|
|
358
|
+
- **Side-by-Side Comparison**: Compare multiple query strategies simultaneously
|
|
359
|
+
- **Performance Metrics**: Track execution time, query count, and memory usage
|
|
360
|
+
- **SQL Analysis**: See all SQL queries executed with their timings
|
|
361
|
+
- **Error Resilience**: Failed strategies don't stop the comparison
|
|
362
|
+
- **Fluent DSL**: Chain query methods naturally
|
|
363
|
+
- **Query Analysis**: Integrated EXPLAIN plan analysis
|
|
364
|
+
- **Export Support**: Export results to JSON, YAML, or HTML
|
|
365
|
+
- **Winner Detection**: Automatically identifies the fastest strategy
|
|
366
|
+
|
|
367
|
+
## Tips
|
|
368
|
+
|
|
369
|
+
1. **Warm up the database**: Run queries once before comparing to avoid cold cache effects
|
|
370
|
+
2. **Use realistic data**: Test with production-like data volumes
|
|
371
|
+
3. **Compare apples to apples**: Ensure all strategies return the same data
|
|
372
|
+
4. **Check query count**: Lower query count usually means better performance
|
|
373
|
+
5. **Review SQL queries**: Look at the actual SQL to understand what's happening
|
|
374
|
+
6. **Use analyze**: Always use `.analyze` to see the execution plan
|
|
375
|
+
|
|
376
|
+
## Configuration
|
|
377
|
+
|
|
378
|
+
```ruby
|
|
379
|
+
# Disable query builder
|
|
380
|
+
RailsConsolePro.configure do |c|
|
|
381
|
+
c.query_builder_command_enabled = false
|
|
382
|
+
c.compare_command_enabled = false
|
|
383
|
+
end
|
|
384
|
+
```
|
|
385
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Commands
|
|
5
|
+
# Command for comparing different query strategies
|
|
6
|
+
class CompareCommand < BaseCommand
|
|
7
|
+
def execute(&block)
|
|
8
|
+
return disabled_message unless enabled?
|
|
9
|
+
return pastel.red('No block provided') unless block_given?
|
|
10
|
+
|
|
11
|
+
comparator = Comparator.new(config)
|
|
12
|
+
comparator.compare(&block)
|
|
13
|
+
rescue => e
|
|
14
|
+
RailsConsolePro::ErrorHandler.handle(e, context: :compare)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def enabled?
|
|
20
|
+
RailsConsolePro.config.enabled && RailsConsolePro.config.compare_command_enabled
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def disabled_message
|
|
24
|
+
pastel.yellow('Compare command is disabled. Enable it via RailsConsolePro.configure { |c| c.compare_command_enabled = true }')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def config
|
|
28
|
+
RailsConsolePro.config
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Internal comparator that runs and measures query strategies
|
|
33
|
+
class Comparator
|
|
34
|
+
SQL_EVENT = 'sql.active_record'
|
|
35
|
+
IGNORED_SQL_NAMES = %w[SCHEMA CACHE EXPLAIN TRANSACTION].freeze
|
|
36
|
+
|
|
37
|
+
attr_reader :config
|
|
38
|
+
|
|
39
|
+
def initialize(config = RailsConsolePro.config)
|
|
40
|
+
@config = config
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def compare(&block)
|
|
44
|
+
runner = Runner.new(config)
|
|
45
|
+
runner.instance_eval(&block)
|
|
46
|
+
runner.build_result
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Internal runner that collects comparison data
|
|
50
|
+
class Runner
|
|
51
|
+
attr_reader :config, :comparisons
|
|
52
|
+
|
|
53
|
+
def initialize(config)
|
|
54
|
+
@config = config
|
|
55
|
+
@comparisons = []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run(name, &block)
|
|
59
|
+
return unless block_given?
|
|
60
|
+
|
|
61
|
+
comparison = execute_comparison(name, block)
|
|
62
|
+
@comparisons << comparison
|
|
63
|
+
comparison
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def build_result
|
|
67
|
+
winner = @comparisons.reject { |c| c.error }.min_by { |c| c.duration_ms || Float::INFINITY }
|
|
68
|
+
CompareResult.new(comparisons: @comparisons, winner: winner)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def execute_comparison(name, block)
|
|
74
|
+
sql_queries = []
|
|
75
|
+
error = nil
|
|
76
|
+
result = nil
|
|
77
|
+
|
|
78
|
+
subscription = subscribe_to_sql_events(sql_queries)
|
|
79
|
+
|
|
80
|
+
wall_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
81
|
+
memory_before = memory_usage
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
result = block.call
|
|
85
|
+
rescue => e
|
|
86
|
+
error = e
|
|
87
|
+
ensure
|
|
88
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - wall_start) * 1000.0).round(2)
|
|
89
|
+
memory_after = memory_usage
|
|
90
|
+
memory_usage_kb = memory_after - memory_before
|
|
91
|
+
|
|
92
|
+
ActiveSupport::Notifications.unsubscribe(subscription) if subscription
|
|
93
|
+
|
|
94
|
+
# Get query_count from collected queries
|
|
95
|
+
query_count = sql_queries.size
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
CompareResult::Comparison.new(
|
|
99
|
+
name: name.to_s,
|
|
100
|
+
duration_ms: duration_ms,
|
|
101
|
+
query_count: query_count,
|
|
102
|
+
result: result,
|
|
103
|
+
error: error,
|
|
104
|
+
sql_queries: sql_queries.dup,
|
|
105
|
+
memory_usage_kb: memory_usage_kb
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def subscribe_to_sql_events(sql_queries)
|
|
110
|
+
ActiveSupport::Notifications.subscribe(SQL_EVENT) do |*args|
|
|
111
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
112
|
+
payload = event.payload
|
|
113
|
+
sql = payload[:sql].to_s
|
|
114
|
+
name = payload[:name].to_s
|
|
115
|
+
|
|
116
|
+
next if sql.empty?
|
|
117
|
+
next if IGNORED_SQL_NAMES.any? { |ignored| name.start_with?(ignored) }
|
|
118
|
+
next if sql =~ /\A\s*(BEGIN|COMMIT|ROLLBACK)/i
|
|
119
|
+
|
|
120
|
+
sql_queries << {
|
|
121
|
+
sql: sql,
|
|
122
|
+
duration_ms: event.duration.round(2),
|
|
123
|
+
name: name,
|
|
124
|
+
cached: payload[:cached] || false
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def memory_usage
|
|
130
|
+
# Try to get memory usage if available (works on Linux)
|
|
131
|
+
if defined?(RSS) && Process.respond_to?(:memory)
|
|
132
|
+
Process.memory / 1024.0 # Convert to KB
|
|
133
|
+
elsif File.exist?('/proc/self/status')
|
|
134
|
+
# Linux proc filesystem
|
|
135
|
+
status = File.read('/proc/self/status')
|
|
136
|
+
if match = status.match(/VmRSS:\s+(\d+)\s+kB/)
|
|
137
|
+
match[1].to_f
|
|
138
|
+
else
|
|
139
|
+
0.0
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
0.0
|
|
143
|
+
end
|
|
144
|
+
rescue
|
|
145
|
+
0.0
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|