From 29785458615acd59300191abb87cd4564581681f Mon Sep 17 00:00:00 2001 From: empty-codes Date: Thu, 19 Dec 2024 00:13:06 +0100 Subject: [PATCH] Creates first version of UI consisting of queue details and sidekiq stats: - Adds route for UI to config - Controller fetches metrics from service class - Service class renamed and fetches data and manipulates it as needed - View created using haml template to display information - Adds descriptions of queues / metrics in locales en.yml --- app/controllers/system_status_controller.rb | 7 +- app/services/get_system_metrics.rb | 82 +++++++++++++-------- app/views/system_status/index.html.haml | 66 +++++++++++++++++ config/locales/en.yml | 28 +++++++ config/routes.rb | 2 + 5 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 app/views/system_status/index.html.haml diff --git a/app/controllers/system_status_controller.rb b/app/controllers/system_status_controller.rb index 28062a9315..fadb95ee54 100644 --- a/app/controllers/system_status_controller.rb +++ b/app/controllers/system_status_controller.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true class SystemStatusController < ApplicationController - # service = SystemMetricsService.new + def index + system_metrics = GetSystemMetrics.new + + @sidekiq_stats = system_metrics.fetch_sidekiq_stats + @queue_metrics = system_metrics.fetch_queue_management_metrics + end end diff --git a/app/services/get_system_metrics.rb b/app/services/get_system_metrics.rb index f1abb74c85..7d68ecff90 100644 --- a/app/services/get_system_metrics.rb +++ b/app/services/get_system_metrics.rb @@ -2,51 +2,73 @@ require 'sidekiq/api' -class SystemMetricsService +class GetSystemMetrics def initialize @queues = YAML.load_file('config/sidekiq.yml')[:queues] + .reject { |queue_name| queue_name == 'very_long_update' } fetch_sidekiq_stats end def fetch_sidekiq_stats stats = Sidekiq::Stats.new - - @processed_jobs_stat = stats.processed - @failed_jobs_stat = stats.failed # Failed Job Retention - @enqueued_jobs_stat = stats.enqueued - @scheduled_jobs_stat = stats.scheduled_size - - # also allow stats from selected dates to be viewed - # using Stats::History + { + enqueued_jobs: stats.enqueued, + active_jobs: stats.processes_size + } end - # def fetch_system_health_metrics - # end + def fetch_queue_management_metrics + queues = [] + paused_queues = [] + all_operational = true - # def fetch_reliability_metrics - # end + @queues.each do |queue_name| + queue = Sidekiq::Queue.new(queue_name) + queues << get_queue_data(queue) - # def fetch_availability_metrics - # end + if queue.paused? + all_operational = false + paused_queues << queue_name + end + end - # def fetch_processing_efficiency_metrics - # end + { + queues:, + paused_queues:, + all_operational: + } + end - # def fetch_resource_utilization_metrics - # end + def get_queue_data(queue) + { + name: queue.name, + size: queue.size, + status: queue.size.zero? ? 'No pending jobs' : 'Pending jobs', + latency: convert_latency(queue.latency) + } + end - def fetch_queue_management_metrics - # Queue latency; how long jobs wait in the queue before processing - @queues.map do |queue_name| - queue = Sidekiq::Queue.new(queue_name) - { - name: queue.name, - size: queue.size, - latency: queue.latency - } + def convert_latency(seconds) + case seconds + when 0...60 + "#{seconds.to_i} second#{'s' unless seconds == 1}" + when 60...3600 + format_time(seconds, 60, 'minute', 'second') + when 3600...86400 + format_time(seconds, 3600, 'hour', 'minute') + else + format_time(seconds, 86400, 'day', 'hour') end end - # def fetch_data_freshness_metrics - # end + def format_time(seconds, unit, main_unit_name, sub_unit_name) + main_unit = (seconds / unit).to_i + remaining_seconds = (seconds % unit).to_i + result = "#{main_unit} #{main_unit_name}#{'s' unless main_unit == 1}" + if remaining_seconds.positive? + sub_unit_value = (remaining_seconds / (unit / 60)).to_i + result += " #{sub_unit_value} #{sub_unit_name}#{'s' unless sub_unit_value == 1}" + end + result + end end diff --git a/app/views/system_status/index.html.haml b/app/views/system_status/index.html.haml new file mode 100644 index 0000000000..4e5886531d --- /dev/null +++ b/app/views/system_status/index.html.haml @@ -0,0 +1,66 @@ +.container.queues + .module + .section-header + %h3 Queues Overview + .notification + .container + - if @queue_metrics[:all_operational] + %p All Queues Operational + - else + %p All Queues Operational except: + .notifications + .notice + .container + - @queue_metrics[:paused_queues].each do |queue_name| + %p= queue_name.humanize + %br/ + + %table.table.table--hoverable + %thead + %tr + %th Queue + %th Purpose + %th Status + %th + .tooltip-trigger + = t("status.size") + %span.tooltip-indicator + .tooltip.dark + %p= t("status.size_doc") + %th + .tooltip-trigger + = t("status.latency") + %span.tooltip-indicator + .tooltip.dark + %p= t("status.latency_doc") + %tbody + - @queue_metrics[:queues].each do |queue| + %tr{ class: queue[:size].zero? ? "table-row--success" : "table-row--warning" } + %td + .tooltip-trigger + = t("status.#{queue[:name]}") + %span.tooltip-indicator + .tooltip.dark + %p= t("status.#{queue[:name]}_description") + %td= t("status.#{queue[:name]}_doc") + %td= queue[:status] + %td= queue[:size] + %td= queue[:latency] + +.container.sidekiq_stats + %br/ + %h3 Sidekiq Stats + .stat-display + - @sidekiq_stats.each do |key, value| + .stat-display__stat.tooltip-trigger + .stat-display__value= value + %small= key.to_s.humanize + + .tooltip.dark + - case key + - when :enqueued_jobs + %p= t("status.enqueued_jobs_doc") + - when :active_jobs + %p= t("status.active_jobs_doc") + - else + %p No additional info available diff --git a/config/locales/en.yml b/config/locales/en.yml index 590544a53c..b6223ba017 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1294,6 +1294,34 @@ en: already_is_not: "%{username} is not a Special User!" demote_success: "%{username} is now just a user." + status: + active_jobs: Active jobs + active_jobs_doc: The number of currently processing jobs. + constant_update: Constant update + constant_update_description: Constant updates are independent of the main course stats, pulling in revision metadata, generating alerts, and doing other data and network-intensive tasks, for all current courses. + constant_update_doc: Handles transactional jobs like wiki edits and sending email. + daily_update: Daily update + daily_update_description: This pulls in additional data and performs other tasks that do not need to be done many times per day. + daily_update_doc: Handles once-daily long-running data update tasks. + default: Default + default_description: Schedule course updates by sorting courses into queues depending on how long they run. + default_doc: Handles frequently-run tasks like adding courses to the update queues. + enqueued_jobs: Enqueued jobs + enqueued_jobs_doc: The number of currently enqueued jobs in all queues. + latency: Latency + latency_doc: The waiting time for jobs to start processing in the queue. High latency may indicate a busy system or processing delays. + long_update: Long update + long_update_description: Long updates process courses with more than 10,000 revisions. + long_update_doc: Handles updates for large courses. + medium_update: Medium update + medium_update_description: Medium updates process courses with fewer than 10,000 revisions. + medium_update_doc: Handles updates for typical-sized courses. + short_update: Short update + short_update_description: Short updates process courses with fewer than 1,000 revisions. + short_update_doc: Handles updates for small courses. + size: Size + size_doc: The number of jobs within a queue. + # Suggestions source: https://en.wikipedia.org/wiki/Template:Grading_scheme suggestions: editing: Editing Suggestions diff --git a/config/routes.rb b/config/routes.rb index ddccc8e33e..24cc9efca8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -474,6 +474,8 @@ get '/private_information' => 'about_this_site#private_information' get '/styleguide' => 'styleguide#index' + get '/status' => 'system_status#index' + # Errors match '/404', to: 'errors#file_not_found', via: :all match '/422', to: 'errors#unprocessable', via: :all