solid_queue_monitor 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1014e5b1b9afceff146efc3a81849dd12866578198b8172452d9b6edf42d7786
4
- data.tar.gz: 0b2c11f5ba47a7e977c93cdb7a048c91d092b9ea97d72af27e908f6f24387fbf
3
+ metadata.gz: bc739f10c21ca234c61c65a5b94d9c548cfc8d332dac0d1db3311d0d113fe035
4
+ data.tar.gz: 4a131b01c6c3e330b46413029278dbcee09e4f6410f1edf44b14da5902ec5b1a
5
5
  SHA512:
6
- metadata.gz: 58bf74bb26fbb518d58e555d1a22695d3175b0ea683529685b4cec6322091722fd7a8eb912bb0fbbfa0fce391663777fc69cde137bf4a4cb0fb1e1cf9d16bbf2
7
- data.tar.gz: 59be98bcc8ee044ac0eac816cab397534c79da53cb98cfb670da1e0747552425f33eaf6dce51e2f906c44f9053b4097a8a79e3f2d6fe30534ddbad6e7f3d3d70
6
+ metadata.gz: 02b79913620ac69eb63c15b2c92a1ba76f44f981036f977eb72fe684faa28ae2057ea187582e8bbd5cb09c0de9f030be89e07b8e8e8e754ee0ffebd6e7c33f84
7
+ data.tar.gz: 853dc16edd433afa568a7cef1efb78b295d54595a72e1e7c177e7991ff50b3e396d80ab1092754e65972dbe0fa9072e2a4563ab68b4785ebe7b5d77b5373e086
data/README.md CHANGED
@@ -72,7 +72,7 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
72
72
  Add this line to your application's Gemfile:
73
73
 
74
74
  ```ruby
75
- gem 'solid_queue_monitor', '~> 1.1'
75
+ gem 'solid_queue_monitor', '~> 1.2'
76
76
  ```
77
77
 
78
78
  Then execute:
@@ -118,9 +118,22 @@ SolidQueueMonitor.setup do |config|
118
118
 
119
119
  # Auto-refresh interval in seconds (default: 30)
120
120
  config.auto_refresh_interval = 30
121
+
122
+ # Disable the chart on the overview page to skip chart queries entirely
123
+ # config.show_chart = true
121
124
  end
122
125
  ```
123
126
 
127
+ ### Performance at Scale
128
+
129
+ SolidQueueMonitor is optimized for large datasets (millions of rows in `solid_queue_jobs`). All dashboard queries are designed to stay fast regardless of table size.
130
+
131
+ If you don't need the job activity chart, disable it to skip chart queries entirely:
132
+
133
+ ```ruby
134
+ config.show_chart = false
135
+ ```
136
+
124
137
  ### Authentication
125
138
 
126
139
  By default, Solid Queue Monitor does not require authentication to access the dashboard. This makes it easy to get started in development environments.
@@ -91,17 +91,13 @@ module SolidQueueMonitor
91
91
  when 'completed'
92
92
  relation = relation.where.not(finished_at: nil)
93
93
  when 'failed'
94
- failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
95
- relation = relation.where(id: failed_job_ids)
94
+ relation = relation.where(id: SolidQueue::FailedExecution.select(:job_id))
96
95
  when 'scheduled'
97
- scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
98
- relation = relation.where(id: scheduled_job_ids)
96
+ relation = relation.where(id: SolidQueue::ScheduledExecution.select(:job_id))
99
97
  when 'pending'
100
- # Pending jobs are those that are not completed, failed, or scheduled
101
- failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
102
- scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
103
98
  relation = relation.where(finished_at: nil)
104
- .where.not(id: failed_job_ids + scheduled_job_ids)
99
+ .where.not(id: SolidQueue::FailedExecution.select(:job_id))
100
+ .where.not(id: SolidQueue::ScheduledExecution.select(:job_id))
105
101
  end
106
102
  end
107
103
 
@@ -117,16 +113,13 @@ module SolidQueueMonitor
117
113
  return relation unless params[:class_name].present? || params[:queue_name].present? || params[:arguments].present?
118
114
 
119
115
  if params[:class_name].present?
120
- job_ids = SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").pluck(:id)
121
- relation = relation.where(job_id: job_ids)
116
+ relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
122
117
  end
123
118
 
124
119
  relation = relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%") if params[:queue_name].present?
125
120
 
126
- # Add arguments filtering
127
121
  if params[:arguments].present?
128
- job_ids = SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").pluck(:id)
129
- relation = relation.where(job_id: job_ids)
122
+ relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
130
123
  end
131
124
 
132
125
  relation
@@ -136,16 +129,13 @@ module SolidQueueMonitor
136
129
  return relation unless params[:class_name].present? || params[:queue_name].present? || params[:arguments].present?
137
130
 
138
131
  if params[:class_name].present?
139
- job_ids = SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").pluck(:id)
140
- relation = relation.where(job_id: job_ids)
132
+ relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
141
133
  end
142
134
 
143
135
  relation = relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%") if params[:queue_name].present?
144
136
 
145
- # Add arguments filtering
146
137
  if params[:arguments].present?
147
- job_ids = SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").pluck(:id)
148
- relation = relation.where(job_id: job_ids)
138
+ relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
149
139
  end
150
140
 
151
141
  relation
@@ -170,25 +160,19 @@ module SolidQueueMonitor
170
160
  return relation unless params[:class_name].present? || params[:queue_name].present? || params[:arguments].present?
171
161
 
172
162
  if params[:class_name].present?
173
- job_ids = SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").pluck(:id)
174
- relation = relation.where(job_id: job_ids)
163
+ relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
175
164
  end
176
165
 
177
166
  if params[:queue_name].present?
178
- # Check if FailedExecution has queue_name column
179
- if relation.column_names.include?('queue_name')
180
- relation = relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%")
181
- else
182
- # If not, filter by job's queue_name
183
- job_ids = SolidQueue::Job.where('queue_name LIKE ?', "%#{params[:queue_name]}%").pluck(:id)
184
- relation = relation.where(job_id: job_ids)
185
- end
167
+ relation = if relation.column_names.include?('queue_name')
168
+ relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%")
169
+ else
170
+ relation.where(job_id: SolidQueue::Job.where('queue_name LIKE ?', "%#{params[:queue_name]}%").select(:id))
171
+ end
186
172
  end
187
173
 
188
- # Add arguments filtering
189
174
  if params[:arguments].present?
190
- job_ids = SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").pluck(:id)
191
- relation = relation.where(job_id: job_ids)
175
+ relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
192
176
  end
193
177
 
194
178
  relation
@@ -22,13 +22,11 @@ module SolidQueueMonitor
22
22
  return relation if params[:class_name].blank? && params[:arguments].blank?
23
23
 
24
24
  if params[:class_name].present?
25
- job_ids = SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").pluck(:id)
26
- relation = relation.where(job_id: job_ids)
25
+ relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
27
26
  end
28
27
 
29
28
  if params[:arguments].present?
30
- job_ids = SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").pluck(:id)
31
- relation = relation.where(job_id: job_ids)
29
+ relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
32
30
  end
33
31
 
34
32
  relation
@@ -6,7 +6,7 @@ module SolidQueueMonitor
6
6
 
7
7
  def index
8
8
  @stats = SolidQueueMonitor::StatsCalculator.calculate
9
- @chart_data = SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate
9
+ @chart_data = SolidQueueMonitor.show_chart ? SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate : nil
10
10
 
11
11
  recent_jobs_query = SolidQueue::Job.limit(100)
12
12
  sorted_query = apply_sorting(filter_jobs(recent_jobs_query), SORTABLE_COLUMNS, 'created_at', :desc)
@@ -29,13 +29,13 @@ module SolidQueueMonitor
29
29
  end
30
30
 
31
31
  def generate_overview_content
32
- SolidQueueMonitor::StatsPresenter.new(@stats).render +
33
- SolidQueueMonitor::ChartPresenter.new(@chart_data).render +
34
- SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
35
- current_page: @recent_jobs[:current_page],
36
- total_pages: @recent_jobs[:total_pages],
37
- filters: filter_params,
38
- sort: sort_params).render
32
+ html = SolidQueueMonitor::StatsPresenter.new(@stats).render
33
+ html += SolidQueueMonitor::ChartPresenter.new(@chart_data).render if @chart_data
34
+ html + SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
35
+ current_page: @recent_jobs[:current_page],
36
+ total_pages: @recent_jobs[:total_pages],
37
+ filters: filter_params,
38
+ sort: sort_params).render
39
39
  end
40
40
  end
41
41
  end
@@ -10,8 +10,13 @@ module SolidQueueMonitor
10
10
  .select('queue_name, COUNT(*) as job_count')
11
11
  @queues = apply_queue_sorting(base_query)
12
12
  @paused_queues = QueuePauseService.paused_queues
13
+ @queue_stats = aggregate_queue_stats
13
14
 
14
- render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues, @paused_queues, sort: sort_params).render)
15
+ render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(
16
+ @queues, @paused_queues,
17
+ queue_stats: @queue_stats,
18
+ sort: sort_params
19
+ ).render)
15
20
  end
16
21
 
17
22
  def show
@@ -57,6 +62,15 @@ module SolidQueueMonitor
57
62
 
58
63
  private
59
64
 
65
+ def aggregate_queue_stats
66
+ {
67
+ ready: SolidQueue::ReadyExecution.group(:queue_name).count,
68
+ scheduled: SolidQueue::ScheduledExecution.group(:queue_name).count,
69
+ failed: SolidQueue::FailedExecution.joins(:job)
70
+ .group('solid_queue_jobs.queue_name').count
71
+ }
72
+ end
73
+
60
74
  def calculate_queue_counts(queue_name)
61
75
  {
62
76
  total: SolidQueue::Job.where(queue_name: queue_name).count,
@@ -77,17 +91,13 @@ module SolidQueueMonitor
77
91
  when 'completed'
78
92
  relation = relation.where.not(finished_at: nil)
79
93
  when 'failed'
80
- failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
81
- relation = relation.where(id: failed_job_ids)
94
+ relation = relation.where(id: SolidQueue::FailedExecution.select(:job_id))
82
95
  when 'scheduled'
83
- scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
84
- relation = relation.where(id: scheduled_job_ids)
96
+ relation = relation.where(id: SolidQueue::ScheduledExecution.select(:job_id))
85
97
  when 'pending'
86
- ready_job_ids = SolidQueue::ReadyExecution.pluck(:job_id)
87
- relation = relation.where(id: ready_job_ids)
98
+ relation = relation.where(id: SolidQueue::ReadyExecution.select(:job_id))
88
99
  when 'in_progress'
89
- claimed_job_ids = SolidQueue::ClaimedExecution.pluck(:job_id)
90
- relation = relation.where(id: claimed_job_ids)
100
+ relation = relation.where(id: SolidQueue::ClaimedExecution.select(:job_id))
91
101
  end
92
102
  end
93
103
 
@@ -2,10 +2,11 @@
2
2
 
3
3
  module SolidQueueMonitor
4
4
  class QueuesPresenter < BasePresenter
5
- def initialize(records, paused_queues = [], sort: {})
6
- @records = records
5
+ def initialize(records, paused_queues = [], sort: {}, queue_stats: {})
6
+ @records = records
7
7
  @paused_queues = paused_queues
8
- @sort = sort
8
+ @sort = sort
9
+ @queue_stats = queue_stats
9
10
  end
10
11
 
11
12
  def render
@@ -39,16 +40,16 @@ module SolidQueueMonitor
39
40
 
40
41
  def generate_row(queue)
41
42
  queue_name = queue.queue_name || 'default'
42
- paused = @paused_queues.include?(queue_name)
43
+ paused = @paused_queues.include?(queue_name)
43
44
 
44
45
  <<-HTML
45
46
  <tr class="#{paused ? 'queue-paused' : ''}">
46
47
  <td>#{queue_link(queue_name)}</td>
47
48
  <td>#{status_badge(paused)}</td>
48
49
  <td>#{queue.job_count}</td>
49
- <td>#{ready_jobs_count(queue_name)}</td>
50
- <td>#{scheduled_jobs_count(queue_name)}</td>
51
- <td>#{failed_jobs_count(queue_name)}</td>
50
+ <td>#{@queue_stats.dig(:ready, queue_name) || 0}</td>
51
+ <td>#{@queue_stats.dig(:scheduled, queue_name) || 0}</td>
52
+ <td>#{@queue_stats.dig(:failed, queue_name) || 0}</td>
52
53
  <td class="actions-cell">#{action_button(queue_name, paused)}</td>
53
54
  </tr>
54
55
  HTML
@@ -84,19 +85,5 @@ module SolidQueueMonitor
84
85
  HTML
85
86
  end
86
87
  end
87
-
88
- def ready_jobs_count(queue_name)
89
- SolidQueue::ReadyExecution.where(queue_name: queue_name).count
90
- end
91
-
92
- def scheduled_jobs_count(queue_name)
93
- SolidQueue::ScheduledExecution.where(queue_name: queue_name).count
94
- end
95
-
96
- def failed_jobs_count(queue_name)
97
- SolidQueue::FailedExecution.joins(:job)
98
- .where(solid_queue_jobs: { queue_name: queue_name })
99
- .count
100
- end
101
88
  end
102
89
  end
@@ -11,13 +11,12 @@ module SolidQueueMonitor
11
11
  <div class="stats-container">
12
12
  <h3>Queue Statistics</h3>
13
13
  <div class="stats">
14
- #{generate_stat_card('Total Jobs', @stats[:total_jobs])}
14
+ #{generate_stat_card('Active Jobs', @stats[:active_jobs])}
15
15
  #{generate_stat_card('Ready', @stats[:ready])}
16
16
  #{generate_stat_card('In Progress', @stats[:in_progress])}
17
17
  #{generate_stat_card('Scheduled', @stats[:scheduled])}
18
18
  #{generate_stat_card('Recurring', @stats[:recurring])}
19
19
  #{generate_stat_card('Failed', @stats[:failed])}
20
- #{generate_stat_card('Completed', @stats[:completed])}
21
20
  </div>
22
21
  </div>
23
22
  HTML
@@ -5,47 +5,42 @@ module SolidQueueMonitor
5
5
  TIME_RANGES = {
6
6
  '15m' => { duration: 15.minutes, buckets: 15, label_format: '%H:%M', label: 'Last 15 minutes' },
7
7
  '30m' => { duration: 30.minutes, buckets: 15, label_format: '%H:%M', label: 'Last 30 minutes' },
8
- '1h' => { duration: 1.hour, buckets: 12, label_format: '%H:%M', label: 'Last 1 hour' },
9
- '3h' => { duration: 3.hours, buckets: 18, label_format: '%H:%M', label: 'Last 3 hours' },
10
- '6h' => { duration: 6.hours, buckets: 24, label_format: '%H:%M', label: 'Last 6 hours' },
8
+ '1h' => { duration: 1.hour, buckets: 12, label_format: '%H:%M', label: 'Last 1 hour' },
9
+ '3h' => { duration: 3.hours, buckets: 18, label_format: '%H:%M', label: 'Last 3 hours' },
10
+ '6h' => { duration: 6.hours, buckets: 24, label_format: '%H:%M', label: 'Last 6 hours' },
11
11
  '12h' => { duration: 12.hours, buckets: 24, label_format: '%H:%M', label: 'Last 12 hours' },
12
- '1d' => { duration: 1.day, buckets: 24, label_format: '%H:%M', label: 'Last 24 hours' },
13
- '3d' => { duration: 3.days, buckets: 36, label_format: '%m/%d %H:%M', label: 'Last 3 days' },
14
- '1w' => { duration: 7.days, buckets: 28, label_format: '%m/%d', label: 'Last 7 days' }
12
+ '1d' => { duration: 1.day, buckets: 24, label_format: '%H:%M', label: 'Last 24 hours' },
13
+ '3d' => { duration: 3.days, buckets: 36, label_format: '%m/%d %H:%M', label: 'Last 3 days' },
14
+ '1w' => { duration: 7.days, buckets: 28, label_format: '%m/%d', label: 'Last 7 days' }
15
15
  }.freeze
16
16
 
17
17
  DEFAULT_TIME_RANGE = '1d'
18
18
 
19
19
  def initialize(time_range: DEFAULT_TIME_RANGE)
20
20
  @time_range = TIME_RANGES.key?(time_range) ? time_range : DEFAULT_TIME_RANGE
21
- @config = TIME_RANGES[@time_range]
21
+ @config = TIME_RANGES[@time_range]
22
22
  end
23
23
 
24
24
  def calculate
25
- end_time = Time.current
26
- start_time = end_time - @config[:duration]
27
- bucket_duration = @config[:duration] / @config[:buckets]
25
+ end_time = Time.current
26
+ start_time = end_time - @config[:duration]
27
+ bucket_seconds = (@config[:duration] / @config[:buckets]).to_i
28
+ buckets = build_buckets(start_time, bucket_seconds)
28
29
 
29
- buckets = build_buckets(start_time, bucket_duration)
30
+ created_data = bucket_counts(SolidQueue::Job, :created_at, start_time, end_time, bucket_seconds)
31
+ completed_data = bucket_counts(SolidQueue::Job, :finished_at, start_time, end_time, bucket_seconds, exclude_nil: true)
32
+ failed_data = bucket_counts(SolidQueue::FailedExecution, :created_at, start_time, end_time, bucket_seconds)
30
33
 
31
- created_counts = fetch_created_counts(start_time, end_time)
32
- completed_counts = fetch_completed_counts(start_time, end_time)
33
- failed_counts = fetch_failed_counts(start_time, end_time)
34
-
35
- created_data = assign_to_buckets(created_counts, buckets, bucket_duration)
36
- completed_data = assign_to_buckets(completed_counts, buckets, bucket_duration)
37
- failed_data = assign_to_buckets(failed_counts, buckets, bucket_duration)
34
+ created_arr = fill_buckets(buckets, created_data)
35
+ completed_arr = fill_buckets(buckets, completed_data)
36
+ failed_arr = fill_buckets(buckets, failed_data)
38
37
 
39
38
  {
40
39
  labels: buckets.map { |b| b[:label] }, # rubocop:disable Rails/Pluck
41
- created: created_data,
42
- completed: completed_data,
43
- failed: failed_data,
44
- totals: {
45
- created: created_data.sum,
46
- completed: completed_data.sum,
47
- failed: failed_data.sum
48
- },
40
+ created: created_arr,
41
+ completed: completed_arr,
42
+ failed: failed_arr,
43
+ totals: { created: created_arr.sum, completed: completed_arr.sum, failed: failed_arr.sum },
49
44
  time_range: @time_range,
50
45
  time_range_label: @config[:label],
51
46
  available_ranges: TIME_RANGES.transform_values { |v| v[:label] }
@@ -54,47 +49,48 @@ module SolidQueueMonitor
54
49
 
55
50
  private
56
51
 
57
- def build_buckets(start_time, bucket_duration)
52
+ def build_buckets(start_time, bucket_seconds)
58
53
  @config[:buckets].times.map do |i|
59
- bucket_start = start_time + (i * bucket_duration)
60
- {
61
- start: bucket_start,
62
- end: bucket_start + bucket_duration,
63
- label: bucket_start.strftime(@config[:label_format])
64
- }
54
+ bucket_start = start_time + (i * bucket_seconds)
55
+ { index: i, start: bucket_start, label: bucket_start.strftime(@config[:label_format]) }
65
56
  end
66
57
  end
67
58
 
68
- def fetch_created_counts(start_time, end_time)
69
- SolidQueue::Job
70
- .where(created_at: start_time..end_time)
71
- .pluck(:created_at)
72
- end
73
-
74
- def fetch_completed_counts(start_time, end_time)
75
- SolidQueue::Job
76
- .where(finished_at: start_time..end_time)
77
- .where.not(finished_at: nil)
78
- .pluck(:finished_at)
59
+ # Returns a Hash of { bucket_index => count } using SQL GROUP BY.
60
+ # The bucket index is computed as: (epoch(column) - epoch(start_time)) / interval
61
+ # This works identically on PostgreSQL and SQLite.
62
+ def bucket_counts(model, column, start_time, end_time, interval, exclude_nil: false)
63
+ start_epoch = start_time.to_i
64
+ expr = bucket_index_expr(column, start_epoch, interval)
65
+
66
+ scope = model.where(column => start_time..end_time)
67
+ scope = scope.where.not(column => nil) if exclude_nil
68
+
69
+ # rubocop:disable Style/HashTransformKeys -- pluck returns Array<Array>, not Hash
70
+ scope
71
+ .group(Arel.sql(expr))
72
+ .pluck(Arel.sql("#{expr} AS bucket_idx, COUNT(*) AS cnt"))
73
+ .to_h { |idx, cnt| [idx.to_i, cnt] }
74
+ # rubocop:enable Style/HashTransformKeys
79
75
  end
80
76
 
81
- def fetch_failed_counts(start_time, end_time)
82
- SolidQueue::FailedExecution
83
- .where(created_at: start_time..end_time)
84
- .pluck(:created_at)
77
+ def fill_buckets(buckets, index_counts)
78
+ buckets.map { |b| index_counts.fetch(b[:index], 0) }
85
79
  end
86
80
 
87
- def assign_to_buckets(timestamps, buckets, _bucket_duration)
88
- counts = Array.new(buckets.size, 0)
89
-
90
- timestamps.each do |timestamp|
91
- bucket_index = buckets.find_index do |bucket|
92
- timestamp >= bucket[:start] && timestamp < bucket[:end]
93
- end
94
- counts[bucket_index] += 1 if bucket_index
81
+ # Cross-DB bucket index expression.
82
+ # PostgreSQL: CAST((EXTRACT(EPOCH FROM col) - start) / interval AS INTEGER)
83
+ # SQLite: CAST((CAST(strftime('%s', col) AS INTEGER) - start) / interval AS INTEGER)
84
+ def bucket_index_expr(column, start_epoch, interval_seconds)
85
+ if sqlite?
86
+ "CAST((CAST(strftime('%s', #{column}) AS INTEGER) - #{start_epoch}) / #{interval_seconds} AS INTEGER)"
87
+ else
88
+ "CAST((EXTRACT(EPOCH FROM #{column}) - #{start_epoch}) / #{interval_seconds} AS INTEGER)"
95
89
  end
90
+ end
96
91
 
97
- counts
92
+ def sqlite?
93
+ ActiveRecord::Base.connection.adapter_name.downcase.include?('sqlite')
98
94
  end
99
95
  end
100
96
  end
@@ -3,15 +3,19 @@
3
3
  module SolidQueueMonitor
4
4
  class StatsCalculator
5
5
  def self.calculate
6
+ scheduled = SolidQueue::ScheduledExecution.count
7
+ ready = SolidQueue::ReadyExecution.count
8
+ failed = SolidQueue::FailedExecution.count
9
+ in_progress = SolidQueue::ClaimedExecution.count
10
+ recurring = SolidQueue::RecurringTask.count
11
+
6
12
  {
7
- total_jobs: SolidQueue::Job.count,
8
- unique_queues: SolidQueue::Job.distinct.count(:queue_name),
9
- scheduled: SolidQueue::ScheduledExecution.count,
10
- ready: SolidQueue::ReadyExecution.count,
11
- failed: SolidQueue::FailedExecution.count,
12
- in_progress: SolidQueue::ClaimedExecution.count,
13
- completed: SolidQueue::Job.where.not(finished_at: nil).count,
14
- recurring: SolidQueue::RecurringTask.count
13
+ active_jobs: ready + scheduled + in_progress + failed,
14
+ scheduled: scheduled,
15
+ ready: ready,
16
+ failed: failed,
17
+ in_progress: in_progress,
18
+ recurring: recurring
15
19
  }
16
20
  end
17
21
  end
@@ -20,4 +20,7 @@ SolidQueueMonitor.setup do |config|
20
20
 
21
21
  # Auto-refresh interval in seconds (default: 30)
22
22
  # config.auto_refresh_interval = 30
23
+
24
+ # Disable the chart on the overview page to skip chart queries entirely.
25
+ # config.show_chart = true
23
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueMonitor
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -7,7 +7,7 @@ module SolidQueueMonitor
7
7
  class Error < StandardError; end
8
8
  class << self
9
9
  attr_accessor :username, :password, :jobs_per_page, :authentication_enabled,
10
- :auto_refresh_enabled, :auto_refresh_interval
10
+ :auto_refresh_enabled, :auto_refresh_interval, :show_chart
11
11
  end
12
12
 
13
13
  @username = 'admin'
@@ -16,6 +16,7 @@ module SolidQueueMonitor
16
16
  @authentication_enabled = false
17
17
  @auto_refresh_enabled = true
18
18
  @auto_refresh_interval = 30 # seconds
19
+ @show_chart = true
19
20
 
20
21
  def self.setup
21
22
  yield self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vishal Sadriya