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 https://profiler.firefox.com for flame-graph inspection.
Redis schema (spec §1.7, §19.8), field-for-field Sidekiq 8.1's Profiler:
profiles ZSET member = "<token>-<jid>", score = expiry epoch (float)
<token>-<jid> HASH started_at (int epoch), token (the job's
`profile` value), type (wrapped || class), jid,
elapsed (float seconds), size (bytes of data),
data (gzipped gecko JSON)
sid is not written here: it is the profile-store id the Web UI caches
after its first upload, and a value in it makes the UI skip the upload.
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
- EXPIRY =
86_400- DEFAULT_OPTIONS =
{ mode: :wall }.freeze
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
-
.store(jid:, type:, token:, gecko_json:, started_at:, elapsed:, 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. There is NO blanket rescue: the job's
own exceptions (a normal failure, or JobRetry::Skip) must propagate
untouched, and a failed job stores no profile (upstream behaviour).
Only the storage step is made failure-safe (see #safe_store).
No &block parameter: every job passes through here, and declaring one
would reify the caller's block into a Proc even on the yield-straight-
through path that opted-out jobs take.
40 41 42 43 44 |
# File 'lib/wurk/profiler.rb', line 40 def call(job_hash) return yield unless job_hash['profile'] && defined?(::Vernier) capture(job_hash) { yield } # rubocop:disable Style/ExplicitBlockArgument end |
.gunzip(bytes) ⇒ Object
75 76 77 78 |
# File 'lib/wurk/profiler.rb', line 75 def gunzip(bytes) require 'zlib' Zlib::GzipReader.new(StringIO.new(bytes)).read end |
.gzip(str) ⇒ Object
66 67 68 69 70 71 72 73 |
# File 'lib/wurk/profiler.rb', line 66 def gzip(str) require 'zlib' io = StringIO.new(+'', 'wb') gz = Zlib::GzipWriter.new(io) gz.write(str) gz.close io.string end |
.profile_key(token, jid) ⇒ Object
62 63 64 |
# File 'lib/wurk/profiler.rb', line 62 def profile_key(token, jid) "#{token}-#{jid}" end |
.store(jid:, type:, token:, gecko_json:, started_at:, elapsed:, pool: nil) ⇒ Object
Persists a profile. Extracted from capture so it is unit-testable without vernier: tests pass a ready gecko JSON blob.
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/wurk/profiler.rb', line 48 def store(jid:, type:, token:, gecko_json:, started_at:, elapsed:, pool: nil) key = profile_key(token, jid) gz = gzip(gecko_json) with_pool(pool) do |conn| conn.multi do |tx| tx.call('ZADD', Keys::PROFILES, ::Time.now.to_f + EXPIRY, key) tx.call('HSET', key, 'started_at', started_at.to_i, 'token', token, 'type', type, 'jid', jid, 'elapsed', elapsed.to_f, 'data', gz, 'size', gz.bytesize) tx.call('EXPIRE', key, EXPIRY) end end key end |