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

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