que-view 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d07972f8adf809f3f10f495824376c64724e1526e7afd6d85a05da93632741e0
4
- data.tar.gz: 95d6ce6e4d1340dcb550a1ae4b85560f19a18de0b9c0dec1eea5ce5a44726ddf
3
+ metadata.gz: 1b7b44890b63b063c130537e5fcc117f40bf1236dd89b156ca48e87a78d71248
4
+ data.tar.gz: 629fd230eb265fdbe7a41f0aec19e37f4047d797750eb635612687cceb7d5af9
5
5
  SHA512:
6
- metadata.gz: 9553edb70775fc2fa268ad2ec3e14d3ab51c3858b54363efbb0d6b8aae85f00d450d99e925cd576dc7117f1ff784bbbe664d763305123598acc35bda57e38e8b
7
- data.tar.gz: ce8bc2cbfef7844814f60125bbad375bf676bc2ef14180d5a2bdc2b4a9fd5cc3ac1920f5b5f40b540a2e600ee4107c247c3b89f90d3e7662725de1a6539c2461
6
+ metadata.gz: 1412a8a378aaa977fd92b40c79dad153267cbf50d1e7ac7409380469106269e5b96484d12193ac57ee78187ad172e79a61783e54e0948aff9242e4947bf6d381
7
+ data.tar.gz: ac32754839e4105584d330cc34a19cb5fccb48f8ce60afd58c1c3f5bb9b933a7ab4bd8616e109d7a2a5b91b384dff878b4a5699ee576f5d5fa1822c60d4392b0
data/README.md CHANGED
@@ -4,12 +4,13 @@ SQL queries came from Que::Web, some styling from there too.
4
4
 
5
5
  Benefits for using this one:
6
6
  - no Sinatra (que-web based on Sinatra),
7
- - no Foundation for styles,
8
- - no jQuery,
7
+ - no Foundation for styles (and no external styles at all),
8
+ - no jQuery (and no java scripts at all),
9
9
  - Que::Web was last updated 2 years ago.
10
10
 
11
- <img width="1735" alt="Снимок экрана 2024-02-21 в 15 10 35" src="https://github.com/kortirso/que-view/assets/6195394/cd2812c7-abb0-48d9-92d5-4dbef93bcd9e">
12
- <img width="1735" alt="Снимок экрана 2024-02-21 в 15 11 12" src="https://github.com/kortirso/que-view/assets/6195394/8af01e7f-a002-4ef1-aeff-f96fd27c639f">
11
+ <img width="1731" alt="Снимок экрана 2024-03-03 в 08 28 09" src="https://github.com/kortirso/que-view/assets/6195394/fdaf315d-6c6e-40ee-a60f-cd740cc7ec93">
12
+ <img width="1734" alt="Снимок экрана 2024-03-03 в 08 27 47" src="https://github.com/kortirso/que-view/assets/6195394/e45d334e-7637-41f8-af16-d6a9ac35f263">
13
+ <img width="1730" alt="Снимок экрана 2024-03-03 в 23 09 57" src="https://github.com/kortirso/que-view/assets/6195394/d790066f-3e96-4775-afd6-018750d0afd3">
13
14
 
14
15
 
15
16
  ## Installation
@@ -29,6 +29,10 @@ body {
29
29
  background: #fafafa;
30
30
  }
31
31
 
32
+ h1 {
33
+ margin-top: 0;
34
+ }
35
+
32
36
  .row {
33
37
  display: flex;
34
38
  }
@@ -112,7 +116,7 @@ body {
112
116
  }
113
117
 
114
118
  .pagination {
115
- margin: 1rem auto;
119
+ margin: 0 auto 1rem;
116
120
  }
117
121
 
118
122
  .pagination .pagination-link {
@@ -181,6 +185,10 @@ body {
181
185
  background: #e7e5e4;
182
186
  }
183
187
 
188
+ .search-form {
189
+ margin-bottom: 1rem;
190
+ }
191
+
184
192
  .search-form .form-select {
185
193
  margin-right: 1rem;
186
194
  padding: .5rem 1rem;
@@ -221,6 +229,19 @@ table p {
221
229
  margin: 0;
222
230
  }
223
231
 
232
+ .lockers {
233
+ margin-top: 2rem;
234
+ }
235
+
236
+ .lockers .locker-process {
237
+ display: flex;
238
+ flex-direction: column;
239
+ }
240
+
241
+ .metrics a {
242
+ color: #000;
243
+ }
244
+
224
245
  @media screen and (max-width: 768px) {
225
246
  .dashboard-row {
226
247
  flex-direction: column;
@@ -84,7 +84,7 @@ module Que
84
84
 
85
85
  def find_jobs(params)
86
86
  case params[:status]&.to_sym
87
- when :running then ::Que::View.fetch_running_jobs(params)
87
+ when :running then ::Que::View.fetch_running_jobs
88
88
  when :failing then ::Que::View.fetch_failing_jobs(PER_PAGE, offset, params)
89
89
  when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, params)
90
90
  when :finished then ::Que::View.fetch_finished_jobs(PER_PAGE, offset, params)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ module View
5
+ class QueueMetricsController < Que::View::ApplicationController
6
+ before_action :find_queue_metrics, only: %i[index]
7
+
8
+ def index; end
9
+
10
+ private
11
+
12
+ def find_queue_metrics
13
+ @queue_metrics = ::Que::View.fetch_queue_metrics
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,6 +5,7 @@ module Que
5
5
  class WelcomeController < Que::View::ApplicationController
6
6
  def index
7
7
  @dashboard_stats = ::Que::View.fetch_dashboard_stats[0]
8
+ @lockers = ::Que::View.fetch_que_lockers
8
9
  end
9
10
  end
10
11
  end
@@ -26,6 +26,9 @@
26
26
  <li class="<%= 'active' if params[:status] == 'expired' %>">
27
27
  <%= link_to 'Expired', jobs_path(status: 'expired') %>
28
28
  </li>
29
+ <li class="<%= 'active' if current_page?(queue_metrics_path) %>">
30
+ <%= link_to 'Queue metrics', queue_metrics_path %>
31
+ </li>
29
32
  </ul>
30
33
  <ul class="version">
31
34
  <li>Que <%= Que::VERSION %></li>
@@ -1,3 +1,4 @@
1
+ <h1><%= params[:status].capitalize %> jobs</h1>
1
2
  <%= form_with url: jobs_path, method: :get, class: 'search-form' do |form| %>
2
3
  <%= form.hidden_field :status, value: params[:status] %>
3
4
  <div class="row">
@@ -6,77 +7,73 @@
6
7
  <%= form.submit 'Search', class: 'btn-primary' %>
7
8
  </div>
8
9
  <% end %>
9
- <% if @jobs.any? %>
10
- <% if @pagination %>
11
- <div class="row pagination">
12
- <% if @pagination.previous_page? %>
13
- <%= link_to 'First', jobs_path(status: params[:status], page: 1), class: 'pagination-link' %>
14
- <%= link_to '< Previous', jobs_path(status: params[:status], page: @pagination.previous_page), class: 'pagination-link' %>
15
- <% else %>
16
- <p class="pagination-link disabled">First</p>
17
- <p class="pagination-link disabled">&#60; Previous</p>
10
+ <% if @pagination %>
11
+ <div class="row pagination">
12
+ <% if @pagination.previous_page? %>
13
+ <%= link_to 'First', jobs_path(status: params[:status], page: 1), class: 'pagination-link' %>
14
+ <%= link_to '< Previous', jobs_path(status: params[:status], page: @pagination.previous_page), class: 'pagination-link' %>
15
+ <% else %>
16
+ <p class="pagination-link disabled">First</p>
17
+ <p class="pagination-link disabled">&#60; Previous</p>
18
+ <% end %>
19
+ <p class="total-pages"><%= "Page #{@pagination.page} of #{@pagination.total_pages}" %></p>
20
+ <% if @pagination.next_page? %>
21
+ <%= link_to 'Next >', jobs_path(status: params[:status], page: @pagination.next_page), class: 'pagination-link' %>
22
+ <%= link_to 'Last', jobs_path(status: params[:status], page: @pagination.total_pages), class: 'pagination-link' %>
23
+ <% else %>
24
+ <p class="pagination-link disabled">Next &#62;</p>
25
+ <p class="pagination-link disabled">Last </p>
26
+ <% end %>
27
+ </div>
28
+ <% end %>
29
+ <table cellspacing="0" class="jobs-list">
30
+ <thead>
31
+ <tr>
32
+ <th>ID</th>
33
+ <th>Job</th>
34
+ <th>Queue</th>
35
+ <th>Run at</th>
36
+ <th>Arguments</th>
37
+ <% if params[:status] == 'failing' %>
38
+ <th>Failures</th>
39
+ <th>Error</th>
18
40
  <% end %>
19
- <p class="total-pages"><%= "Page #{@pagination.page} of #{@pagination.total_pages}" %></p>
20
- <% if @pagination.next_page? %>
21
- <%= link_to 'Next >', jobs_path(status: params[:status], page: @pagination.next_page), class: 'pagination-link' %>
22
- <%= link_to 'Last', jobs_path(status: params[:status], page: @pagination.total_pages), class: 'pagination-link' %>
23
- <% else %>
24
- <p class="pagination-link disabled">Next &#62;</p>
25
- <p class="pagination-link disabled">Last </p>
41
+ <% if %w[failing scheduled].include?(params[:status]) %>
42
+ <th>Actions</th>
26
43
  <% end %>
27
- </div>
28
- <% end %>
29
- <table cellspacing="0" class="jobs-list">
30
- <thead>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ <% @jobs.each do |job| %>
31
48
  <tr>
32
- <th>ID</th>
33
- <th>Job</th>
34
- <th>Queue</th>
35
- <th>Run at</th>
36
- <th>Arguments</th>
49
+ <td><%= link_to job[:id], job_path(job[:id]) %></td>
50
+ <td><%= humanized_job_class(job) %></td>
51
+ <td><%= job[:queue] %></td>
52
+ <td><%= job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
53
+ <td>
54
+ <% job.dig(:args, 0, :arguments).each do |argument| %>
55
+ <p><%= argument %></p>
56
+ <% end %>
57
+ </td>
37
58
  <% if params[:status] == 'failing' %>
38
- <th>Failures</th>
39
- <th>Error</th>
59
+ <td><%= job[:error_count] %></td>
60
+ <td><%= format_error(job) %></td>
40
61
  <% end %>
41
62
  <% if %w[failing scheduled].include?(params[:status]) %>
42
- <th>Actions</th>
43
- <% end %>
44
- </tr>
45
- </thead>
46
- <tbody>
47
- <% @jobs.each do |job| %>
48
- <tr>
49
- <td><%= link_to job[:id], job_path(job[:id]) %></td>
50
- <td><%= humanized_job_class(job) %></td>
51
- <td><%= job[:queue] %></td>
52
- <td><%= job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
53
63
  <td>
54
- <% job.dig(:args, 0, :arguments).each do |argument| %>
55
- <p><%= argument %></p>
56
- <% end %>
64
+ <div class="actions">
65
+ <%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
66
+ <%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
67
+ </div>
57
68
  </td>
58
- <% if params[:status] == 'failing' %>
59
- <td><%= job[:error_count] %></td>
60
- <td><%= format_error(job) %></td>
61
- <% end %>
62
- <% if %w[failing scheduled].include?(params[:status]) %>
63
- <td>
64
- <div class="actions">
65
- <%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
66
- <%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
67
- </div>
68
- </td>
69
- <% end %>
70
- </tr>
71
- <% end %>
72
- </tbody>
73
- </table>
69
+ <% end %>
70
+ </tr>
71
+ <% end %>
72
+ </tbody>
73
+ </table>
74
+ <% if @pagination && %w[failing scheduled].include?(params[:status]) %>
74
75
  <div class="row">
75
76
  <%= button_to 'Run All', reschedule_all_jobs_path(status: params[:status]), class: 'btn-danger', method: :post, onclick: "return confirm('Are you sure you wish to reschedule all jobs?')" %>
76
77
  <%= button_to 'Delete All', destroy_all_jobs_path(status: params[:status]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete all jobs?')" %>
77
78
  </div>
78
- <% else %>
79
- <div class="row">
80
- <p>No jobs found</p>
81
- </div>
82
79
  <% end %>
@@ -0,0 +1,29 @@
1
+ <div class="metrics">
2
+ <h1>Queue Metrics</h1>
3
+ <table cellspacing="0">
4
+ <thead>
5
+ <tr>
6
+ <th>Queue</th>
7
+ <th>Scheduled</th>
8
+ <th>Failing</th>
9
+ <th>Running</th>
10
+ <th>Finished</th>
11
+ <th>Expired</th>
12
+ <th>Latency</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% @queue_metrics.each do |queue, values| %>
17
+ <tr>
18
+ <td><%= queue %></td>
19
+ <td><%= link_to values[:scheduled], jobs_path(status: 'scheduled', queue_name: queue) %></td>
20
+ <td><%= link_to values[:failing], jobs_path(status: 'failing', queue_name: queue) %></td>
21
+ <td><%= link_to values[:running], jobs_path(status: 'running', queue_name: queue) %></td>
22
+ <td><%= link_to values[:finished], jobs_path(status: 'finished', queue_name: queue) %></td>
23
+ <td><%= link_to values[:expired], jobs_path(status: 'expired', queue_name: queue) %></td>
24
+ <td></td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
29
+ </div>
@@ -58,3 +58,29 @@
58
58
  <% end %>
59
59
  </div>
60
60
  </div>
61
+ <div class="lockers">
62
+ <h1>Processes</h1>
63
+ <table cellspacing="0">
64
+ <thead>
65
+ <tr>
66
+ <th>ID</th>
67
+ <th>Worker count</th>
68
+ <th>Worker priorities</th>
69
+ <th>Listening</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ <% @lockers.each.with_index do |locker, index| %>
74
+ <tr>
75
+ <td class="locker-process">
76
+ <span>Process <%= index + 1 %></span>
77
+ <span><strong>Queues:</strong> <%= locker[:queues][1..-2].split(',').join(', ') %></span>
78
+ </td>
79
+ <td><%= locker[:worker_count] %></td>
80
+ <td><%= locker[:worker_priorities] %></td>
81
+ <td><%= locker[:listening] %></td>
82
+ </tr>
83
+ <% end %>
84
+ </tbody>
85
+ </table>
86
+ </div>
data/config/routes.rb CHANGED
@@ -5,6 +5,7 @@ Que::View::Engine.routes.draw do
5
5
  post :reschedule_all, on: :collection
6
6
  delete :destroy_all, on: :collection
7
7
  end
8
+ resources :queue_metrics, only: %i[index]
8
9
 
9
10
  root 'welcome#index'
10
11
  end
data/lib/que/view/dsl.rb CHANGED
@@ -8,6 +8,17 @@ module Que
8
8
  execute(fetch_dashboard_stats_sql)
9
9
  end
10
10
 
11
+ def fetch_que_lockers
12
+ execute(fetch_que_lockers_sql)
13
+ end
14
+
15
+ def fetch_queue_metrics
16
+ execute(fetch_queue_metrics_sql).each_with_object({}) { |element, acc|
17
+ acc[element[:queue_name].to_sym] ||= basis_queue_stats
18
+ acc[element[:queue_name].to_sym][element[:status].to_sym] = element[:count_all]
19
+ }
20
+ end
21
+
11
22
  def fetch_queue_names
12
23
  execute(fetch_queue_names_sql).map { |queues_data|
13
24
  ["#{queues_data[:queue_name]} (#{queues_data[:count_all]})", queues_data[:queue_name]]
@@ -20,7 +31,7 @@ module Que
20
31
  }
21
32
  end
22
33
 
23
- def fetch_running_jobs(...)
34
+ def fetch_running_jobs
24
35
  Que.job_states
25
36
  end
26
37
 
@@ -70,6 +81,10 @@ module Que
70
81
 
71
82
  private
72
83
 
84
+ def basis_queue_stats
85
+ { scheduled: 0, failing: 0, running: 0, finished: 0, expired: 0 }
86
+ end
87
+
73
88
  # rubocop: disable Metrics/MethodLength
74
89
  def fetch_dashboard_stats_sql
75
90
  <<-SQL.squish
@@ -88,6 +103,40 @@ module Que
88
103
  SQL
89
104
  end
90
105
 
106
+ def fetch_que_lockers_sql
107
+ <<-SQL.squish
108
+ SELECT *
109
+ FROM que_lockers
110
+ SQL
111
+ end
112
+
113
+ def fetch_queue_metrics_sql
114
+ <<-SQL.squish
115
+ SELECT COUNT(*) AS count_all, queue AS queue_name,
116
+ CASE
117
+ WHEN expired_at IS NOT NULL THEN 'expired'
118
+ WHEN finished_at IS NOT NULL THEN 'finished'
119
+ WHEN locks.job_id IS NULL AND error_count > 0 THEN 'failing'
120
+ WHEN locks.job_id IS NULL AND error_count = 0 THEN 'scheduled'
121
+ ELSE 'running'
122
+ END status
123
+ FROM que_jobs
124
+ LEFT JOIN (
125
+ SELECT (classid::bigint << 32) + objid::bigint AS job_id
126
+ FROM pg_locks
127
+ WHERE locktype = 'advisory'
128
+ ) locks ON (que_jobs.id=locks.job_id)
129
+ GROUP BY queue,
130
+ CASE
131
+ WHEN expired_at IS NOT NULL THEN 'expired'
132
+ WHEN finished_at IS NOT NULL THEN 'finished'
133
+ WHEN locks.job_id IS NULL AND error_count > 0 THEN 'failing'
134
+ WHEN locks.job_id IS NULL AND error_count = 0 THEN 'scheduled'
135
+ ELSE 'running'
136
+ END
137
+ SQL
138
+ end
139
+
91
140
  def fetch_queue_names_sql
92
141
  <<-SQL.squish
93
142
  SELECT COUNT(*) AS count_all, queue AS queue_name
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Que
4
4
  module View
5
- VERSION = '0.3.1'
5
+ VERSION = '0.3.2'
6
6
  end
7
7
  end
data/lib/que/view.rb CHANGED
@@ -34,7 +34,7 @@ module Que
34
34
  end
35
35
 
36
36
  # Public: All the methods delegated to instance. These should match the interface of Que::View::DSL.
37
- def_delegators :instance, :fetch_dashboard_stats,
37
+ def_delegators :instance, :fetch_dashboard_stats, :fetch_que_lockers, :fetch_queue_metrics,
38
38
  :fetch_queue_names, :fetch_job_names,
39
39
  :fetch_running_jobs, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_finished_jobs,
40
40
  :fetch_expired_jobs, :fetch_job,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: que-view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bogdanov Anton
@@ -164,11 +164,13 @@ files:
164
164
  - app/assets/stylesheets/que/view/application.css
165
165
  - app/controllers/que/view/application_controller.rb
166
166
  - app/controllers/que/view/jobs_controller.rb
167
+ - app/controllers/que/view/queue_metrics_controller.rb
167
168
  - app/controllers/que/view/welcome_controller.rb
168
169
  - app/helpers/que/view/application_helper.rb
169
170
  - app/views/layouts/que/view/application.html.erb
170
171
  - app/views/que/view/jobs/index.html.erb
171
172
  - app/views/que/view/jobs/show.html.erb
173
+ - app/views/que/view/queue_metrics/index.html.erb
172
174
  - app/views/que/view/welcome/index.html.erb
173
175
  - config/routes.rb
174
176
  - lib/que/view.rb