# frozen_string_literal: true require 'socket' module Intranet module System # Provides system statistics for a Linux system. class LinuxStatsProvider # @!visibility protected CPUINFO_FILE = '/proc/cpuinfo' # @!visibility protected LOADAVG_FILE = '/proc/loadavg' # @!visibility protected MEMINFO_FILE = '/proc/meminfo' # @!visibility protected SWAPS_FILE = '/proc/swaps' # The system statistics refresh delay, in seconds. # Under Linux, it is not necessary to refresh statistics more frequently than 5s since this is # the refresh rate of the load average. STATS_REFRESH_DELAY_S = 5 # Initializes a new Linux statistics provider. def initialize @stats = Hash[ hostname: Socket.gethostname, loadavg: nil, cpu: { model: cpu_model, nb_cores: nb_cores }, ram: { total: ram_available }, swaps: {}, partitions: {}] @stats_date = Time.now - STATS_REFRESH_DELAY_S end # Returns the latest system statistics. System statistics are updated if they are outdated. # @return [Hash] The system statistics, see {Responder#generate_page} for the structure. def stats now = Time.now if outdated?(now) @stats[:loadavg] = load_average @stats[:ram][:used] = ram_used @stats[:swaps] = swaps_usage @stats[:partitions] = partitions_usage @stats_date = now end @stats end private def outdated?(at) (at - @stats_date) >= STATS_REFRESH_DELAY_S end def cpu_model File.readlines(CPUINFO_FILE).grep(/model name/)[0].split(/:/)[1].strip end def nb_cores File.readlines(CPUINFO_FILE).grep(/^processor/).size end def load_average File.read(LOADAVG_FILE).split.first(3).map(&:to_f) end def ram_available # /proc/meminfo & /proc/swaps display Kib values despite indicating 'Kb'. File.readlines(MEMINFO_FILE).grep(/^MemTotal/)[0].split[1].to_i * 1024 end def ram_used free = File.readlines(MEMINFO_FILE).grep(/^MemAvailable/)[0].split[1].to_i * 1024 @stats[:ram][:total] - free end def swaps_usage File.readlines(SWAPS_FILE).grep(%r{^/}).map do |swap| infos = swap.split { infos[0] => { total: infos[2].to_i * 1024, used: infos[3].to_i * 1024 } } end.reduce({}, :update) end def partitions_usage `df -lB1`.lines.grep(%r{^/}).sort.map do |partition| infos = partition.split { infos[5] => { total: infos[1].to_i, used: infos[2].to_i } } end.reduce({}, :update) end end end end