Skip to content

Commit

Permalink
feat: add event stats for sidekiq and sq
Browse files Browse the repository at this point in the history
Similar to Karafka's stats event, this adds the collected cluster based
stats as a wide event for Sidekiq and SolidQueue. The previous
aggregated metrics can be toggled on, while the new events can be
toggled off.

```
sidekiq:
  insights:
    events: false
solid_queue:
  insights:
    events: false

sidekiq:
  insights:
    metrics: true
solid_queue:
  insights:
    metrics: true
```

In addition, the polling interval has been decreased to 5 seconds to
allow for up to date readings.
  • Loading branch information
roelbondoc committed Nov 12, 2024
1 parent 6a64bf7 commit a5b0cb5
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 62 deletions.
47 changes: 31 additions & 16 deletions lib/honeybadger/config/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -344,41 +344,56 @@ class Boolean; end
default: true,
type: Boolean
},
:'sidekiq.insights.enabled' => {
description: 'Enable automatic data collection for Sidekiq.',
default: true,
type: Boolean
},
:'sidekiq.insights.events' => {
description: 'Enable automatic event capturing for Sidekiq.',
default: true,
type: Boolean
},
:'sidekiq.insights.metrics' => {
description: 'Enable automatic metric data collection for Sidekiq.',
default: false,
type: Boolean
},
:'sidekiq.insights.cluster_collection' => {
description: 'Collect cluster based metrics for Sidekiq.',
default: true,
type: Boolean
},
:'sidekiq.insights.collection_interval' => {
description: 'The frequency in which Sidekiq cluster metrics are sampled.',
default: 60,
default: 5,
type: Integer
},
:'solid_queue.insights.cluster_collection' => {
description: 'Collect cluster based metrics for SolidQueue.',
:'solid_queue.insights.enabled' => {
description: 'Enable automatic data collection for SolidQueue.',
default: true,
type: Boolean
},
:'solid_queue.insights.collection_interval' => {
description: 'The frequency in which SolidQueue cluster metrics are sampled.',
default: 60,
type: Integer
},
:'sidekiq.insights.enabled' => {
description: 'Enable automatic data collection for Sidekiq.',
:'solid_queue.insights.events' => {
description: 'Enable automatic event capturing for SolidQueue.',
default: true,
type: Boolean
},
:'sidekiq.insights.events' => {
description: 'Enable automatic event capturing for Sidekiq.',
default: true,
:'solid_queue.insights.metrics' => {
description: 'Enable automatic metric data collection for SolidQueue.',
default: false,
type: Boolean
},
:'sidekiq.insights.metrics' => {
description: 'Enable automatic metric data collection for Sidekiq.',
default: false,
:'solid_queue.insights.cluster_collection' => {
description: 'Collect cluster based metrics for SolidQueue.',
default: true,
type: Boolean
},
:'solid_queue.insights.collection_interval' => {
description: 'The frequency in which SolidQueue cluster metrics are sampled.',
default: 5,
type: Integer
},
:'rails.insights.enabled' => {
description: 'Enable automatic data collection for Ruby on Rails.',
default: true,
Expand Down
88 changes: 54 additions & 34 deletions lib/honeybadger/plugins/sidekiq.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,47 +167,67 @@ def collect?
end
end

collect_sidekiq_stats = -> do
stats = ::Sidekiq::Stats.new
data = stats.as_json
data[:queues] = {}

::Sidekiq::Queue.all.each do |queue|
data[:queues][queue.name] ||= {}
data[:queues][queue.name][:latency] = (queue.latency * 1000).ceil
data[:queues][queue.name][:depth] = queue.size
end

Hash.new(0).tap do |busy_counts|
::Sidekiq::Workers.new.each do |_pid, _tid, work|
payload = work.respond_to?(:payload) ? work.payload : work["payload"]
payload = JSON.parse(payload) if payload.is_a?(String)
busy_counts[payload["queue"]] += 1
end
end.each do |queue_name, busy_count|
data[:queues][queue_name] ||= {}
data[:queues][queue_name][:busy] = busy_count
end

processes = ::Sidekiq::ProcessSet.new.to_enum(:each).to_a
data[:capacity] = processes.map { |process| process["concurrency"] }.sum

process_utilizations = processes.map do |process|
next unless process["concurrency"].to_f > 0
process["busy"] / process["concurrency"].to_f
end.compact

if process_utilizations.any?
utilization = process_utilizations.sum / process_utilizations.length.to_f
data[:utilization] = utilization
end

data
end

collect do
if config.cluster_collection?(:sidekiq) && (leader_checker.nil? || leader_checker.collect?)
metric_source 'sidekiq'

stats = ::Sidekiq::Stats.new

gauge 'active_workers', ->{ stats.workers_size }
gauge 'active_processes', ->{ stats.processes_size }
gauge 'jobs_processed', ->{ stats.processed }
gauge 'jobs_failed', ->{ stats.failed }
gauge 'jobs_scheduled', ->{ stats.scheduled_size }
gauge 'jobs_enqueued', ->{ stats.enqueued }
gauge 'jobs_dead', ->{ stats.dead_size }
gauge 'jobs_retry', ->{ stats.retry_size }

::Sidekiq::Queue.all.each do |queue|
gauge 'queue_latency', { queue: queue.name }, ->{ (queue.latency * 1000).ceil }
gauge 'queue_depth', { queue: queue.name }, ->{ queue.size }
end
stats = collect_sidekiq_stats.call

Hash.new(0).tap do |busy_counts|
::Sidekiq::Workers.new.each do |_pid, _tid, work|
payload = work.respond_to?(:payload) ? work.payload : work["payload"]
payload = JSON.parse(payload) if payload.is_a?(String)
busy_counts[payload["queue"]] += 1
end
end.each do |queue_name, busy_count|
gauge 'queue_busy', { queue: queue_name }, ->{ busy_count }
if Honeybadger.config.load_plugin_insights_events?(:sidekiq)
Honeybadger.event('stats.sidekiq', stats.except('stats').merge(stats['stats']))
end

processes = ::Sidekiq::ProcessSet.new.to_enum(:each).to_a
gauge 'capacity', ->{ processes.map { |process| process["concurrency"] }.sum }
if Honeybadger.config.load_plugin_insights_metrics?(:sidekiq)
metric_source 'sidekiq'

stats['stats'].each do |name, value|
gauge name, value: value
end

process_utilizations = processes.map do |process|
next unless process["concurrency"].to_f > 0
process["busy"] / process["concurrency"].to_f
end.compact
stats[:queues].each do |queue_name, data|
data.each do |key, value|
gauge "queue_#{key}", queue: queue_name, value: value
end
end

if process_utilizations.any?
utilization = process_utilizations.sum / process_utilizations.length.to_f
gauge 'utilization', ->{ utilization }
gauge 'capacity', value: stats[:capacity] if stats[:capacity]
gauge 'utilization', value: stats[:utilization] if stats[:utilization]
end
end
end
Expand Down
50 changes: 38 additions & 12 deletions lib/honeybadger/plugins/solid_queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,46 @@ module SolidQueue
Plugin.register :solid_queue do
requirement { config.load_plugin_insights?(:solid_queue) && defined?(::SolidQueue) }

collect_solid_queue_stats = -> do
data = {}
data[:stats] = {
jobs_in_progress: ::SolidQueue::ClaimedExecution.count,
jobs_blocked: ::SolidQueue::BlockedExecution.count,
jobs_failed: ::SolidQueue::FailedExecution.count,
jobs_scheduled: ::SolidQueue::ScheduledExecution.count,
jobs_processed: ::SolidQueue::Job.where.not(finished_at: nil).count,
active_workers: ::SolidQueue::Process.where(kind: "Worker").count,
active_dispatchers: ::SolidQueue::Process.where(kind: "Dispatcher").count
}

data[:queues] = {}

::SolidQueue::Queue.all.each do |queue|
data[:queues][queue.name] = { depth: queue.size }
end

data
end

collect do
stats = collect_solid_queue_stats.call

if config.cluster_collection?(:solid_queue)
metric_source 'solid_queue'

gauge 'jobs_in_progress', ->{ ::SolidQueue::ClaimedExecution.count }
gauge 'jobs_blocked', ->{ ::SolidQueue::BlockedExecution.count }
gauge 'jobs_failed', ->{ ::SolidQueue::FailedExecution.count }
gauge 'jobs_scheduled', ->{ ::SolidQueue::ScheduledExecution.count }
gauge 'jobs_processed', ->{ ::SolidQueue::Job.where.not(finished_at: nil).count }
gauge 'active_workers', ->{ ::SolidQueue::Process.where(kind: "Worker").count }
gauge 'active_dispatchers', ->{ ::SolidQueue::Process.where(kind: "Dispatcher").count }

::SolidQueue::Queue.all.each do |queue|
gauge 'queue_depth', { queue: queue.name }, ->{ queue.size }
if Honeybadger.config.load_plugin_insights_events?(:solid_queue)
Honeybadger.event('stats.solid_queue', stats.except(:stats).merge(stats[:stats]))
end

if Honeybadger.config.load_plugin_insights_metrics?(:solid_queue)
metric_source 'solid_queue'
stats[:stats].each do |stat_name, value|
gauge stat_name, value: value
end

stats[:queues].each do |queue_name, data|
data.each do |key, value|
gauge "queue_#{key}", queue: queue_name, value: value
end
end
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions spec/unit/honeybadger/plugins/sidekiq_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ def scheduled_size; end
def enqueued; end
def dead_size; end
def retry_size; end
def as_json
{}
end
end
end

Expand Down

0 comments on commit a5b0cb5

Please sign in to comment.