diff --git a/app/controllers/status_controller.rb b/app/controllers/status_controller.rb new file mode 100644 index 000000000..4cd509491 --- /dev/null +++ b/app/controllers/status_controller.rb @@ -0,0 +1,8 @@ +class StatusController < ApplicationController + respond_to :html, :json, :xml + + def show + @status = ServerStatus.new + respond_with(@status) + end +end diff --git a/app/logical/server_status.rb b/app/logical/server_status.rb new file mode 100644 index 000000000..197582bf7 --- /dev/null +++ b/app/logical/server_status.rb @@ -0,0 +1,109 @@ +class ServerStatus + extend Memoist + include ActiveModel::Serializers::JSON + include ActiveModel::Serializers::Xml + + def serializable_hash(*options) + { + status: { + hostname: hostname, + uptime: uptime, + loadavg: loadavg, + ruby_version: RUBY_VERSION, + distro_version: distro_version, + kernel_version: kernel_version, + libvips_version: libvips_version, + ffmpeg_version: ffmpeg_version, + mkvmerge_version: mkvmerge_version, + redis_version: redis_version, + postgres_version: postgres_version, + }, + postgres: { + connection_stats: postgres_connection_stats, + }, + redis: { + info: redis_info, + } + } + end + + concerning :InfoMethods do + def hostname + Socket.gethostname + end + + def uptime + seconds = File.read("/proc/uptime").split[0].to_f + "#{seconds.seconds.in_days.round} days" + end + + def loadavg + File.read("/proc/loadavg").chomp + end + + def kernel_version + File.read("/proc/version").chomp + end + + def distro_version + `source /etc/os-release; echo "$NAME $VERSION"`.chomp + end + + def libvips_version + Vips::LIBRARY_VERSION + end + + def ffmpeg_version + version = `ffmpeg -version` + version[/ffmpeg version ([0-9.]+)/, 1] + end + + def mkvmerge_version + `mkvmerge --version`.chomp + end + end + + concerning :RedisMethods do + def redis_info + return {} if Rails.cache.try(:redis).nil? + Rails.cache.redis.info + end + + def redis_used_memory + redis_info["used_memory_rss_human"] + end + + def redis_version + redis_info["redis_version"] + end + end + + concerning :PostgresMethods do + def postgres_version + ApplicationRecord.connection.select_value("SELECT version()") + end + + def postgres_active_connections + ApplicationRecord.connection.select_value("SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active'") + end + + def postgres_connection_stats + run_query("SELECT pid, state, query_start, state_change, xact_start, backend_start, backend_type FROM pg_stat_activity ORDER BY state, query_start DESC, backend_type") + end + + def run_query(query) + result = ApplicationRecord.connection.select_all(query) + serialize_result(result) + end + + def serialize_result(result) + result.rows.map do |row| + row.each_with_index.map do |col, i| + [result.columns[i], col] + end.to_h + end + end + end + + memoize :redis_info +end diff --git a/app/views/status/show.html.erb b/app/views/status/show.html.erb new file mode 100644 index 000000000..c0f138359 --- /dev/null +++ b/app/views/status/show.html.erb @@ -0,0 +1,32 @@ +
+
+

Status

+ +
+ + Server: <%= @status.hostname %> + + <%= render "list", hash: @status.serializable_hash[:status] %> +
+ +

Postgres

+ +
+ + <%= pluralize @status.postgres_active_connections, "active connection" %>. + + + <%= render "table", rows: @status.serializable_hash[:postgres][:connection_stats] %> +
+ +

Redis

+ +
+ + <%= @status.redis_used_memory %> memory used. + + + <%= render "list", hash: @status.serializable_hash[:redis][:info] %> +
+
+
diff --git a/config/routes.rb b/config/routes.rb index 11d7e7d28..9b916feaf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -227,6 +227,7 @@ Rails.application.routes.draw do get :sign_out, on: :collection end resource :source, :only => [:show] + resource :status, only: [:show], controller: "status" resources :tags do collection do get :autocomplete diff --git a/test/functional/status_controller_test.rb b/test/functional/status_controller_test.rb new file mode 100644 index 000000000..b05467262 --- /dev/null +++ b/test/functional/status_controller_test.rb @@ -0,0 +1,20 @@ +require 'test_helper' + +class StatusControllerTest < ActionDispatch::IntegrationTest + context "The status controller" do + should "work for a html response" do + get status_path + assert_response :success + end + + should "work for a json response" do + get status_path(format: :json) + assert_response :success + end + + should "work for an xml response" do + get status_path(format: :json) + assert_response :success + end + end +end