Module: Wurk::Profiler
- Defined in:
- lib/wurk/profiler.rb
Overview
Job profiling (Sidekiq 8.0+, OSS). When a job is pushed with a ‘profile` option, the processor wraps `perform` in a Vernier capture; the resulting Firefox-profiler (gecko) JSON is gzipped and stored so the dashboard can hand it to profiler.firefox.com for flame-graph inspection.
Redis schema (spec §1.7), wire-compat with Sidekiq’s Profiles pane:
profiles ZSET member = "<token>-<jid>", score = expiry epoch
<token>-<jid> HASH jid, type, token, started_at, elapsed, size,
sid, data (gzipped gecko JSON)
Capture is a no-op unless the ‘vernier` gem is loaded — profiling is an opt-in, dev/staging tool, so vernier stays an optional dependency.
Constant Summary collapse
- TTL =
Stored profiles live this long (score = now + TTL); ProfileSet purges expired members on read.
7 * 24 * 60 * 60
Class Method Summary collapse
-
.call(job_hash) ⇒ Object
Server-side hook called from Processor#dispatch.
- .gunzip(bytes) ⇒ Object
- .gzip(str) ⇒ Object
-
.profile_key(token, jid) ⇒ Object
rubocop:enable Metrics/ParameterLists.
-
.store(jid:, type:, gecko_json:, started_at:, elapsed_ms:, token: SecureRandom.hex(8), sid: Wurk.configuration[:identity], pool: nil) ⇒ Object
Persists a profile.
Class Method Details
.call(job_hash) ⇒ Object
Server-side hook called from Processor#dispatch. Returns the perform result. Only captures when the job opted in AND vernier is present —otherwise it’s a plain ‘yield`. Crucially there is NO blanket rescue here: the job’s own exceptions (a normal failure, or JobRetry::Skip from the interrupt/expiry middleware) must propagate untouched so the retry/skip flow works. Only the storage step is made failure-safe (see #safe_store).
36 37 38 39 40 41 |
# File 'lib/wurk/profiler.rb', line 36 def call(job_hash, &) label = job_hash['profile'] return yield unless label && defined?(::Vernier) capture(job_hash, label, &) end |
.gunzip(bytes) ⇒ Object
75 76 77 |
# File 'lib/wurk/profiler.rb', line 75 def gunzip(bytes) Zlib::GzipReader.new(StringIO.new(bytes)).read end |
.gzip(str) ⇒ Object
67 68 69 70 71 72 73 |
# File 'lib/wurk/profiler.rb', line 67 def gzip(str) io = StringIO.new(+'', 'wb') gz = Zlib::GzipWriter.new(io) gz.write(str) gz.close io.string end |
.profile_key(token, jid) ⇒ Object
rubocop:enable Metrics/ParameterLists
63 64 65 |
# File 'lib/wurk/profiler.rb', line 63 def profile_key(token, jid) "#{token}-#{jid}" end |
.store(jid:, type:, gecko_json:, started_at:, elapsed_ms:, token: SecureRandom.hex(8), sid: Wurk.configuration[:identity], pool: nil) ⇒ Object
Persists a profile. Extracted from capture so it is unit-testable without vernier: tests pass a ready gecko JSON blob. The wide keyword list mirrors the HASH fields one-to-one — collapsing them into an options hash would just hide the schema. rubocop:disable Metrics/ParameterLists
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/wurk/profiler.rb', line 48 def store(jid:, type:, gecko_json:, started_at:, elapsed_ms:, token: SecureRandom.hex(8), sid: Wurk.configuration[:identity], pool: nil) key = profile_key(token, jid) gz = gzip(gecko_json) with_pool(pool) do |conn| conn.call('HSET', key, 'jid', jid, 'type', type, 'token', token, 'started_at', started_at.to_i, 'elapsed', elapsed_ms.to_i, 'size', gz.bytesize, 'sid', sid.to_s, 'data', gz) conn.call('EXPIRE', key, TTL) conn.call('ZADD', Keys::PROFILES, (now + TTL).to_i, key) end key end |