Class: RSpecTelemetry::Subscribers::Sql
- Inherits:
-
Object
- Object
- RSpecTelemetry::Subscribers::Sql
- Defined in:
- lib/rspec_telemetry/subscribers/sql.rb
Overview
Aggregates sql.active_record per example instead of writing one event per query: a request spec can fire thousands of queries per example, which would bloat the NDJSON file and slow the suite down. Queries are grouped by payload name ("Merchant Load", ...) and flushed as one sql.group event per (example, name) when the lifecycle calls #example_finished.
Constant Summary collapse
- STACK_KEY =
:rspec_telemetry_sql_stack- FB_STACK_KEY =
Mirrors Subscribers::FactoryBot::STACK_KEY without forcing that file to load; a non-empty stack means this query runs inside a factory.
:rspec_telemetry_fb_stack- SKIP_NAMES =
SCHEMA is Rails introspection noise; TRANSACTION (BEGIN/COMMIT) mostly measures the test's own transaction wrapping. Nameless queries (raw #execute, and BEGIN/COMMIT on Rails < 7.1) are skipped too, because they cannot be told apart from transaction management reliably.
%w[SCHEMA TRANSACTION].freeze
- SQL_TRUNCATE =
500
Instance Method Summary collapse
-
#example_finished ⇒ Object
Called by the lifecycle at example end, while the example id is still set, so the flushed sql.group events attach to the right example.
- #finish(_name, _id, payload) ⇒ Object
-
#initialize(recorder) ⇒ Sql
constructor
A new instance of Sql.
- #start(_name, _id, _payload) ⇒ Object
- #subscribe ⇒ Object
- #unsubscribe ⇒ Object
Constructor Details
#initialize(recorder) ⇒ Sql
Returns a new instance of Sql.
35 36 37 38 39 40 41 42 |
# File 'lib/rspec_telemetry/subscribers/sql.rb', line 35 def initialize(recorder) @recorder = recorder @subscription = nil # Queries can arrive from threads other than the example's (e.g. a # Capybara app server), so buckets are shared and keyed by example_id. @mutex = Mutex.new @buckets = {} end |
Instance Method Details
#example_finished ⇒ Object
Called by the lifecycle at example end, while the example id is still set, so the flushed sql.group events attach to the right example.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/rspec_telemetry/subscribers/sql.rb', line 83 def example_finished RSpecTelemetry.safely("sql#example_finished") do example_id = Thread.current[Recorder::EXAMPLE_ID] groups = @mutex.synchronize { @buckets.delete(example_id) } next unless groups groups.each do |name, g| @recorder.record( "sql.group", name: name, count: g[:count], total_ms: g[:total_ms].round(3), max_ms: g[:max_ms].round(3), in_factory_count: g[:in_factory_count], in_factory_ms: g[:in_factory_ms].round(3), cached_count: g[:cached_count] ) end end end |
#finish(_name, _id, payload) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/rspec_telemetry/subscribers/sql.rb', line 59 def finish(_name, _id, payload) RSpecTelemetry.safely("sql#finish") do started = stack.pop next unless started config = @recorder.config next unless config.enabled && config.capture_sql name = payload[:name] next if name.nil? || SKIP_NAMES.include?(name) now = Process.clock_gettime(Process::CLOCK_MONOTONIC) ms = (now - started) * 1000.0 cached = payload[:cached] ? true : false fb_stack = Thread.current[FB_STACK_KEY] in_factory = fb_stack && !fb_stack.empty? accumulate(name, ms, cached: cached, in_factory: in_factory) record_slow_query(name, ms, payload, in_factory) unless cached end end |
#start(_name, _id, _payload) ⇒ Object
53 54 55 56 57 |
# File 'lib/rspec_telemetry/subscribers/sql.rb', line 53 def start(_name, _id, _payload) RSpecTelemetry.safely("sql#start") do stack.push(Process.clock_gettime(Process::CLOCK_MONOTONIC)) end end |
#subscribe ⇒ Object
44 45 46 |
# File 'lib/rspec_telemetry/subscribers/sql.rb', line 44 def subscribe @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", self) end |
#unsubscribe ⇒ Object
48 49 50 51 |
# File 'lib/rspec_telemetry/subscribers/sql.rb', line 48 def unsubscribe ActiveSupport::Notifications.unsubscribe(@subscription) if @subscription @subscription = nil end |