patient_http-sidekiq 1.2.0 → 1.4.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +31 -10
  3. data/CHANGELOG.md +32 -0
  4. data/README.md +126 -2
  5. data/VERSION +1 -1
  6. data/lib/patient_http/sidekiq/configuration.rb +258 -1
  7. data/lib/patient_http/sidekiq/direct_task_handler.rb +46 -0
  8. data/lib/patient_http/sidekiq/processor_observer.rb +162 -21
  9. data/lib/patient_http/sidekiq/redis_pool.rb +88 -0
  10. data/lib/patient_http/sidekiq/request_executor.rb +40 -10
  11. data/lib/patient_http/sidekiq/request_worker.rb +5 -2
  12. data/lib/patient_http/sidekiq/stats.rb +230 -33
  13. data/lib/patient_http/sidekiq/task_handler.rb +9 -3
  14. data/lib/patient_http/sidekiq/task_monitor.rb +410 -121
  15. data/lib/patient_http/sidekiq/task_monitor_thread.rb +41 -7
  16. data/lib/patient_http/sidekiq/web_ui/assets/patient-http/css/patient_http.css +29 -71
  17. data/lib/patient_http/sidekiq/web_ui/locales/ar.yml +10 -5
  18. data/lib/patient_http/sidekiq/web_ui/locales/cs.yml +10 -5
  19. data/lib/patient_http/sidekiq/web_ui/locales/da.yml +10 -5
  20. data/lib/patient_http/sidekiq/web_ui/locales/de.yml +10 -5
  21. data/lib/patient_http/sidekiq/web_ui/locales/el.yml +10 -5
  22. data/lib/patient_http/sidekiq/web_ui/locales/en.yml +10 -5
  23. data/lib/patient_http/sidekiq/web_ui/locales/es.yml +10 -5
  24. data/lib/patient_http/sidekiq/web_ui/locales/fa.yml +10 -5
  25. data/lib/patient_http/sidekiq/web_ui/locales/fr.yml +10 -5
  26. data/lib/patient_http/sidekiq/web_ui/locales/gd.yml +10 -5
  27. data/lib/patient_http/sidekiq/web_ui/locales/he.yml +10 -5
  28. data/lib/patient_http/sidekiq/web_ui/locales/hi.yml +10 -5
  29. data/lib/patient_http/sidekiq/web_ui/locales/it.yml +10 -5
  30. data/lib/patient_http/sidekiq/web_ui/locales/ja.yml +10 -5
  31. data/lib/patient_http/sidekiq/web_ui/locales/ko.yml +10 -5
  32. data/lib/patient_http/sidekiq/web_ui/locales/lt.yml +10 -5
  33. data/lib/patient_http/sidekiq/web_ui/locales/nb.yml +10 -5
  34. data/lib/patient_http/sidekiq/web_ui/locales/nl.yml +10 -5
  35. data/lib/patient_http/sidekiq/web_ui/locales/pl.yml +10 -5
  36. data/lib/patient_http/sidekiq/web_ui/locales/pt-BR.yml +10 -5
  37. data/lib/patient_http/sidekiq/web_ui/locales/pt.yml +10 -5
  38. data/lib/patient_http/sidekiq/web_ui/locales/ru.yml +10 -5
  39. data/lib/patient_http/sidekiq/web_ui/locales/sv.yml +10 -5
  40. data/lib/patient_http/sidekiq/web_ui/locales/ta.yml +10 -5
  41. data/lib/patient_http/sidekiq/web_ui/locales/tr.yml +10 -5
  42. data/lib/patient_http/sidekiq/web_ui/locales/uk.yml +10 -5
  43. data/lib/patient_http/sidekiq/web_ui/locales/ur.yml +10 -5
  44. data/lib/patient_http/sidekiq/web_ui/locales/vi.yml +10 -5
  45. data/lib/patient_http/sidekiq/web_ui/locales/zh-CN.yml +10 -5
  46. data/lib/patient_http/sidekiq/web_ui/locales/zh-TW.yml +10 -5
  47. data/lib/patient_http/sidekiq/web_ui/views/patient_http.html.erb +99 -44
  48. data/lib/patient_http/sidekiq/web_ui.rb +53 -1
  49. data/lib/patient_http/sidekiq.rb +303 -31
  50. data/patient_http-sidekiq.gemspec +1 -1
  51. metadata +6 -4
@@ -31,60 +31,115 @@
31
31
  <span><%= avg_duration %></span>
32
32
  </div>
33
33
  </div>
34
+ <div class="stat-card">
35
+ <div class="stat-label"><%= t('patient_http_max_capacity') %></div>
36
+ <div class="stat-value"><%= number_with_delimiter(max_capacity) %></div>
37
+ </div>
38
+ <div class="stat-card">
39
+ <div class="stat-label"><%= t('patient_http_currently_inflight') %></div>
40
+ <div class="stat-value"><%= number_with_delimiter(current_inflight) %></div>
41
+ </div>
42
+ <div class="stat-card">
43
+ <div class="stat-label"><%= t('patient_http_capacity_utilization') %></div>
44
+ <div class="stat-value"><%= utilization %>%</div>
45
+ </div>
34
46
  </div>
35
47
 
36
- <!-- Capacity Information -->
37
- <div class="capacity-section">
38
- <h3><%= t('patient_http_connection_capacity') %></h3>
39
- <div class="capacity-cards">
40
- <div class="capacity-card">
41
- <div class="capacity-label"><%= t('patient_http_max_capacity') %></div>
42
- <div class="capacity-value"><%= number_with_delimiter(max_capacity) %></div>
43
- <div class="capacity-unit"><%= t('patient_http_connections') %></div>
44
- </div>
45
- <div class="capacity-card">
46
- <div class="capacity-label"><%= t('patient_http_currently_inflight') %></div>
47
- <div class="capacity-value"><%= number_with_delimiter(current_inflight) %></div>
48
- <div class="capacity-unit"><%= t('patient_http_requests') %></div>
49
- </div>
50
- <div class="capacity-card">
51
- <div class="capacity-label"><%= t('patient_http_capacity_utilization') %></div>
52
- <div class="capacity-value"><%= utilization %>%</div>
53
- <div class="capacity-unit"><%= t('patient_http_used') %></div>
48
+ <!-- Processor Breakdown -->
49
+ <% unless processors.empty? %>
50
+ <div class="breakdown-section">
51
+ <h3><%= t('patient_http_processors') %></h3>
52
+ <div class="table-scroll">
53
+ <table class="table">
54
+ <thead>
55
+ <tr>
56
+ <th><%= t('patient_http_processor') %></th>
57
+ <th><%= t('patient_http_inflight') %></th>
58
+ <th><%= t('patient_http_peak_inflight') %></th>
59
+ <th><%= t('patient_http_max_capacity') %></th>
60
+ <th><%= t('patient_http_utilization') %></th>
61
+ <th><%= t('patient_http_total_requests') %></th>
62
+ <th><%= t('patient_http_errors') %></th>
63
+ <th><%= t('patient_http_max_capacity_exceeded') %></th>
64
+ <th><%= t('patient_http_avg_duration') %></th>
65
+ </tr>
66
+ </thead>
67
+ <tbody>
68
+ <% processors.each do |processor| %>
69
+ <tr>
70
+ <td><%= h(processor[:name]) %></td>
71
+ <td><%= number_with_delimiter(processor[:inflight]) %></td>
72
+ <td><%= number_with_delimiter(processor[:peak_inflight]) %></td>
73
+ <td><%= number_with_delimiter(processor[:max_capacity]) %></td>
74
+ <td><%= processor[:utilization] %>%</td>
75
+ <td><%= number_with_delimiter(processor[:requests]) %></td>
76
+ <td><%= number_with_delimiter(processor[:errors]) %></td>
77
+ <td><%= number_with_delimiter(processor[:max_capacity_exceeded]) %></td>
78
+ <td><%= processor[:avg_duration] %></td>
79
+ </tr>
80
+ <% end %>
81
+ </tbody>
82
+ </table>
54
83
  </div>
55
84
  </div>
85
+ <% end %>
56
86
 
57
- <!-- Process Breakdown -->
58
- <div class="process-breakdown">
59
- <h4><%= t('patient_http_per_process_details') %></h4>
60
- <table class="table">
61
- <thead>
62
- <tr>
63
- <th><%= t('patient_http_process') %></th>
64
- <th><%= t('patient_http_inflight') %></th>
65
- <th><%= t('patient_http_max_capacity') %></th>
66
- <th><%= t('patient_http_utilization') %></th>
67
- </tr>
68
- </thead>
69
- <tbody>
70
- <% if processes.empty? %>
71
- <tr class="empty-state">
72
- <td colspan="4"><%= t('patient_http_no_active_processes') %></td>
87
+ <!-- In-Flight Requests -->
88
+ <% unless inflight.empty? %>
89
+ <div class="breakdown-section">
90
+ <h3><%= t('patient_http_inflight_requests') %></h3>
91
+ <div class="table-scroll">
92
+ <table class="table">
93
+ <thead>
94
+ <tr>
95
+ <th><%= t('patient_http_age') %></th>
96
+ <th><%= t('patient_http_method') %></th>
97
+ <th><%= t('patient_http_url') %></th>
98
+ <th><%= t('patient_http_processor') %></th>
99
+ <th><%= t('patient_http_process') %></th>
73
100
  </tr>
74
- <% else %>
75
- <% processes.each do |process_id, info| %>
76
- <% utilization = info[:max_capacity] > 0 ? (info[:inflight].to_f / info[:max_capacity] * 100).round(1) : 0 %>
101
+ </thead>
102
+ <tbody>
103
+ <% inflight.each do |request| %>
77
104
  <tr>
78
- <td><%= h(process_id) %></td>
79
- <td><%= number_with_delimiter(info[:inflight]) %></td>
80
- <td><%= number_with_delimiter(info[:max_capacity]) %></td>
81
- <td><%= utilization %>%</td>
105
+ <td><%= request[:age] %></td>
106
+ <td><%= h(request[:http_method].to_s.upcase) %></td>
107
+ <td class="url"><%= h(request[:url]) %></td>
108
+ <td><%= h(request[:processor]) %></td>
109
+ <td><%= h(request[:process_id]) %></td>
82
110
  </tr>
83
111
  <% end %>
84
- <% end %>
85
- </tbody>
86
- </table>
112
+ </tbody>
113
+ </table>
114
+ </div>
87
115
  </div>
116
+ <% end %>
117
+
118
+ <!-- Sidekiq Process Breakdown -->
119
+ <div class="breakdown-section">
120
+ <h3><%= t('patient_http_sidekiq_processes') %></h3>
121
+ <table class="table">
122
+ <thead>
123
+ <tr>
124
+ <th><%= t('patient_http_process') %></th>
125
+ <th><%= t('patient_http_inflight') %></th>
126
+ </tr>
127
+ </thead>
128
+ <tbody>
129
+ <% if processes.empty? %>
130
+ <tr class="empty-state">
131
+ <td colspan="2"><%= t('patient_http_no_active_processes') %></td>
132
+ </tr>
133
+ <% else %>
134
+ <% processes.each do |process_id, info| %>
135
+ <tr>
136
+ <td><%= h(process_id) %></td>
137
+ <td><%= number_with_delimiter(info[:inflight]) %></td>
138
+ </tr>
139
+ <% end %>
140
+ <% end %>
141
+ </tbody>
142
+ </table>
88
143
  </div>
89
144
 
90
145
  <!-- HTTP Status Codes Breakdown -->
@@ -20,6 +20,10 @@ module PatientHttp
20
20
  ROOT = File.join(__dir__, "web_ui")
21
21
  VIEWS = File.join(ROOT, "views")
22
22
 
23
+ # Number of in-flight requests listed on the dashboard. The oldest are
24
+ # listed, because those are the ones worth looking at.
25
+ INFLIGHT_LIMIT = 50
26
+
23
27
  class << self
24
28
  # Sidekiq resolves symbol view names to different file names depending
25
29
  # on the version (*.erb before 8.1, *.html.erb from 8.1 on), so the
@@ -28,6 +32,42 @@ module PatientHttp
28
32
  @template ||= File.read(File.join(VIEWS, "patient_http.html.erb"))
29
33
  end
30
34
 
35
+ # Build the per-processor rows for the dashboard by combining the
36
+ # cumulative counters with the live capacity each processor reports.
37
+ #
38
+ # Statistics are not recorded per processor when only one is
39
+ # configured, because they would duplicate the overall totals, so the
40
+ # breakdown is empty in that case.
41
+ #
42
+ # @param stats_by_processor [Hash, nil] cumulative counters by processor name
43
+ # @param capacity_by_processor [Hash] inflight and capacity by processor name
44
+ # @return [Array<Hash>] one row per processor, sorted by name
45
+ def processor_rows(stats_by_processor, capacity_by_processor)
46
+ stats_by_processor ||= {}
47
+ return [] if stats_by_processor.empty? && capacity_by_processor.size < 2
48
+
49
+ names = (stats_by_processor.keys | capacity_by_processor.keys).sort
50
+ names.map do |name|
51
+ counts = stats_by_processor[name] || {}
52
+ capacity = capacity_by_processor[name] || {}
53
+ requests = counts["requests"].to_i
54
+ max_capacity = capacity[:max_capacity].to_i
55
+ inflight = capacity[:inflight].to_i
56
+
57
+ {
58
+ name: name,
59
+ inflight: inflight,
60
+ peak_inflight: counts["max_inflight"].to_i,
61
+ max_capacity: max_capacity,
62
+ utilization: (max_capacity > 0) ? (inflight.to_f / max_capacity * 100).round(1) : 0,
63
+ requests: requests,
64
+ errors: counts["errors"].to_i,
65
+ max_capacity_exceeded: counts["max_capacity_exceeded"].to_i,
66
+ avg_duration: (requests > 0) ? (counts["duration"].to_f / requests).round(3) : 0.0
67
+ }
68
+ end
69
+ end
70
+
31
71
  # This method is called by Sidekiq::Web when registering the extension
32
72
  def registered(app)
33
73
  # GET route for the main PatientHttp dashboard page
@@ -47,6 +87,16 @@ module PatientHttp
47
87
  current_inflight = processes.values.sum { |data| data[:inflight] }
48
88
  utilization = (max_capacity > 0) ? (current_inflight.to_f / max_capacity * 100).round(1) : 0
49
89
 
90
+ # Per-processor breakdown, combining the cumulative counters with
91
+ # the capacity each processor reports.
92
+ processors = PatientHttp::Sidekiq::WebUI.processor_rows(
93
+ totals["processors"],
94
+ PatientHttp::Sidekiq::TaskMonitor.inflight_counts_by_processor(processes)
95
+ )
96
+
97
+ # The requests that have been in flight the longest.
98
+ inflight = PatientHttp::Sidekiq::TaskMonitor.inflight_details(limit: INFLIGHT_LIMIT)
99
+
50
100
  erb(PatientHttp::Sidekiq::WebUI.template, locals: {
51
101
  totals: totals,
52
102
  total_requests: total_requests,
@@ -54,7 +104,9 @@ module PatientHttp
54
104
  max_capacity: max_capacity,
55
105
  current_inflight: current_inflight,
56
106
  utilization: utilization,
57
- processes: processes
107
+ processes: processes,
108
+ processors: processors,
109
+ inflight: inflight
58
110
  })
59
111
  end
60
112