Class: Sidekiq::Metrics::Histogram
- Inherits:
-
Object
- Object
- Sidekiq::Metrics::Histogram
- Includes:
- Enumerable
- Defined in:
- lib/sidekiq/metrics/shared.rb
Overview
Implements space-efficient but statistically useful histogram storage. A precise time histogram stores every time. Instead we break times into a set of known buckets and increment counts of the associated time bucket. Even if we call the histogram a million times, we’ll still only store 26 buckets. NB: needs to be thread-safe or resiliant to races.
To store this data, we use Redis’ BITFIELD command to store unsigned 16-bit counters per bucket per klass per minute. It’s unlikely that most people will be executing more than 1000 job/sec for a full minute of a specific type.
Constant Summary collapse
- BUCKET_INTERVALS =
This number represents the maximum milliseconds for this bucket. 20 means all job executions up to 20ms, e.g. if a job takes 280ms, it’ll increment bucket. Note we can track job executions up to about 5.5 minutes. After that, it’s assumed you’re probably not too concerned with its performance.
[ 20, 30, 45, 65, 100, 150, 225, 335, 500, 750, 1100, 1700, 2500, 3800, 5750, 8500, 13000, 20000, 30000, 45000, 65000, 100000, 150000, 225000, 335000, 1e20 # the "maybe your job is too long" bucket ].freeze
- LABELS =
[ "20ms", "30ms", "45ms", "65ms", "100ms", "150ms", "225ms", "335ms", "500ms", "750ms", "1.1s", "1.7s", "2.5s", "3.8s", "5.75s", "8.5s", "13s", "20s", "30s", "45s", "65s", "100s", "150s", "225s", "335s", "Slow" ].freeze
- FETCH =
"GET u16 #0 GET u16 #1 GET u16 #2 GET u16 #3 \ GET u16 #4 GET u16 #5 GET u16 #6 GET u16 #7 \ GET u16 #8 GET u16 #9 GET u16 #10 GET u16 #11 \ GET u16 #12 GET u16 #13 GET u16 #14 GET u16 #15 \ GET u16 #16 GET u16 #17 GET u16 #18 GET u16 #19 \ GET u16 #20 GET u16 #21 GET u16 #22 GET u16 #23 \ GET u16 #24 GET u16 #25".split
- HISTOGRAM_TTL =
8 * 60 * 60
Instance Attribute Summary collapse
-
#buckets ⇒ Object
readonly
Returns the value of attribute buckets.
Instance Method Summary collapse
- #each ⇒ Object
- #fetch(conn, now = Time.now) ⇒ Object
-
#initialize(klass) ⇒ Histogram
constructor
A new instance of Histogram.
- #label(idx) ⇒ Object
- #persist(conn, now = Time.now) ⇒ Object
- #record_time(ms) ⇒ Object
Constructor Details
#initialize(klass) ⇒ Histogram
Returns a new instance of Histogram.
71 72 73 74 |
# File 'lib/sidekiq/metrics/shared.rb', line 71 def initialize(klass) @klass = klass @buckets = Array.new(BUCKET_INTERVALS.size) { Counter.new } end |
Instance Attribute Details
#buckets ⇒ Object (readonly)
Returns the value of attribute buckets.
70 71 72 |
# File 'lib/sidekiq/metrics/shared.rb', line 70 def buckets @buckets end |
Instance Method Details
#each ⇒ Object
62 63 64 |
# File 'lib/sidekiq/metrics/shared.rb', line 62 def each buckets.each { |counter| yield counter.value } end |
#fetch(conn, now = Time.now) ⇒ Object
84 85 86 87 88 |
# File 'lib/sidekiq/metrics/shared.rb', line 84 def fetch(conn, now = Time.now) window = now.utc.strftime("%d-%H:%-M") key = "#{@klass}-#{window}" conn.bitfield_ro(key, *FETCH) end |
#label(idx) ⇒ Object
66 67 68 |
# File 'lib/sidekiq/metrics/shared.rb', line 66 def label(idx) LABELS[idx] end |
#persist(conn, now = Time.now) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/sidekiq/metrics/shared.rb', line 90 def persist(conn, now = Time.now) buckets, @buckets = @buckets, [] window = now.utc.strftime("%d-%H:%-M") key = "#{@klass}-#{window}" cmd = [key, "OVERFLOW", "SAT"] buckets.each_with_index do |counter, idx| val = counter.value cmd << "INCRBY" << "u16" << "##{idx}" << val.to_s if val > 0 end conn.bitfield(*cmd) if cmd.size > 3 conn.expire(key, HISTOGRAM_TTL) key end |
#record_time(ms) ⇒ Object
76 77 78 79 80 81 82 |
# File 'lib/sidekiq/metrics/shared.rb', line 76 def record_time(ms) index_to_use = BUCKET_INTERVALS.each_index do |idx| break idx if ms < BUCKET_INTERVALS[idx] end @buckets[index_to_use].increment end |