Module: Sixty::Instrument::Pg
- Defined in:
- lib/sixty/instrument/pg.rb
Overview
The pg gem, patched directly.
── Who this is for ───────────────────────────────────────────────────────
A Rails application is covered by sql.active_record, which is a public
interface every adapter emits and every version has — strictly better than
patching, so when ActiveRecord is in the process this is not installed at
all. What it covers is everything else: Sinatra, Roda, a Sidekiq worker
that talks to Postgres directly, Sequel and ROM (both of which run their
queries through this class), and scripts.
── What is captured beyond timing ───────────────────────────────────────
rows : `ntuples` for a read, `cmd_tuples` for a write. The 30 → 30,000
signal, and the one thing a slow-query log cannot show you.
fields : column count, which is how `select *` creeping into a hot path
becomes visible.
bytes : a sampled estimate of the result's size.
Never the SQL text, and never a parameter: only the normalized shape.
── No query plans on this path ──────────────────────────────────────────
The ActiveRecord instrumentation captures plans because it can borrow a
connection from a pool that is designed to be borrowed from. Here there is
no pool — only the caller's own PG::Connection, which is not thread-safe
and may be mid-query on another thread when the flush thread wakes up. The
alternatives are worse: running the EXPLAIN inline puts a second round trip
in front of a user, and opening a connection of our own means an
observability agent quietly consuming a slot in somebody's connection
limit. So plans are a Rails feature for now, and this path reports
everything else.
Defined Under Namespace
Modules: Patch
Constant Summary collapse
- TEXT_FIRST =
exec,queryandasync_execare the same method under three names in modern pg, andsuperresolves by name — so a call toexecreaches the originalexecrather than this module'sasync_exec, and nothing is counted twice.sync_*are separate implementations and need their own wrapper. %i[exec query async_exec sync_exec exec_params async_exec_params sync_exec_params].freeze
- MAX_PREPARED =
Prepared statements name a query somewhere other than the call site.
exec_prepared('find_user', [id])has no SQL in it at all, so the text is remembered when the statement is prepared and looked up when it is run. Bounded, because a process that has prepared a thousand distinct statements is not going to learn much from the next one. 500
Class Attribute Summary collapse
-
.config ⇒ Object
readonly
Returns the value of attribute config.
Class Method Summary collapse
- .install(config = nil) ⇒ Object
- .installed? ⇒ Boolean
-
.measure(sql) ⇒ Object
One span for one statement.
- .prepared_sql(name) ⇒ Object
- .remember_prepared(name, sql) ⇒ Object
- .reset! ⇒ Object
Class Attribute Details
.config ⇒ Object (readonly)
Returns the value of attribute config.
64 65 66 |
# File 'lib/sixty/instrument/pg.rb', line 64 def config @config end |
Class Method Details
.install(config = nil) ⇒ Object
51 52 53 54 55 56 57 58 |
# File 'lib/sixty/instrument/pg.rb', line 51 def install(config = nil) return false if @installed return false unless defined?(::PG::Connection) @config = config ::PG::Connection.prepend(Patch) @installed = true end |
.installed? ⇒ Boolean
60 61 62 |
# File 'lib/sixty/instrument/pg.rb', line 60 def installed? @installed == true end |
.measure(sql) ⇒ Object
One span for one statement. Shared by every wrapper below so that the rules — never raise, always emit, capture frames once — live in one place rather than in seven.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/sixty/instrument/pg.rb', line 93 def measure(sql) return yield unless Sixty.enabled? && sql.is_a?(String) && !sql.empty? normalized = Sql.normalize_sql(sql) started = Tracer.monotonic_ms begin result = yield rescue StandardError => e record(sql, normalized, Tracer.monotonic_ms - started, nil, e) raise end record(sql, normalized, Tracer.monotonic_ms - started, result, nil) result end |
.prepared_sql(name) ⇒ Object
81 82 83 |
# File 'lib/sixty/instrument/pg.rb', line 81 def prepared_sql(name) @prepared&.[](name) end |
.remember_prepared(name, sql) ⇒ Object
73 74 75 76 77 78 79 |
# File 'lib/sixty/instrument/pg.rb', line 73 def remember_prepared(name, sql) return unless name.is_a?(String) && sql.is_a?(String) @prepared ||= {} @prepared.clear if @prepared.size >= MAX_PREPARED @prepared[name] = sql end |
.reset! ⇒ Object
85 86 87 88 |
# File 'lib/sixty/instrument/pg.rb', line 85 def reset! @installed = false @prepared = nil end |