class Btop
Constants
- BAR_WIDTH
- DISK_MOUNTS
- FIELD_SEPARATOR
- IRC_CTRL
- IRC_RESET
- METRIC_FILL_STYLES
- METRIC_ORDER
- NET_LINK_LABEL
- NET_MAX_BPS
- NET_SAMPLE_INTERVAL
- STYLE_EMPTY
- STYLE_FILL
- STYLE_LABEL
- SYSTEM_INFO_FIELDS
- SYSTEM_INFO_FIELD_STYLES
- SYSTEM_INFO_LABEL_WIDTH
Public Class Methods
Source
# File plugins/btop.rb, line 396 def self.build_report(stats, system_info: nil) lines = [] lines << format_system_info(system_info) if system_info lines << format_metrics_lines(stats) lines.join("\n") end
Source
# File plugins/btop.rb, line 311 def self.cpu_model(cpuinfo) cpuinfo.to_s.each_line do |line| next unless line.start_with?("model name") return line.split(":", 2).last.to_s.strip end "" end
Source
# File plugins/btop.rb, line 182 def self.cpu_percent_between(first, second) total_delta = second[:total] - first[:total] idle_delta = second[:idle] - first[:idle] return 0 unless total_delta.positive? ((total_delta - idle_delta) * 100.0 / total_delta).round end
Source
# File plugins/btop.rb, line 464 def self.default_readers { meminfo: -> { File.read("/proc/meminfo") }, proc_stat: -> { File.read("/proc/stat").lines.first }, df: method(:read_df), loadavg: -> { File.read("/proc/loadavg") }, cpu_count: -> { File.read("/proc/cpuinfo").lines.count { |line| line.start_with?("processor") } }, sleep: method(:kernel_sleep), hostname: -> { File.read("/etc/hostname") }, os_release: -> { File.read("/etc/os-release") }, uname: -> { `uname -r`.to_s }, uptime: -> { File.read("/proc/uptime") }, packages: method(:read_package_count), cpuinfo: -> { File.read("/proc/cpuinfo") }, proc_count: method(:read_proc_count), thread_count: method(:read_thread_count), net_dev: -> { File.read("/proc/net/dev") } } end
Source
# File plugins/btop.rb, line 38 def self.disk_metric_key(mount) mount == "/" ? :dsk_root : :"dsk#{mount.tr('/', '_')}" end
Source
# File plugins/btop.rb, line 134 def self.format_line(percent:, value:, max_value:, label:, fill_style:, width: BAR_WIDTH, value_width: nil, max_value_width: nil) val_w = value_width || value.to_s.length max_w = max_value_width || value_width || max_value.to_s.length data = format_metric_data( percent: percent, value: value, max_value: max_value, fill_style: fill_style, width: width, value_width: val_w, max_value_width: max_w ) "#{data} #{STYLE_LABEL}#{label}#{IRC_RESET}" end
Source
# File plugins/btop.rb, line 126 def self.format_metric_data(percent:, value:, max_value:, fill_style:, width: BAR_WIDTH, value_width:, max_value_width:) bar = progress_bar(percent, width: width, fill_style: fill_style, empty_style: STYLE_EMPTY) pct = format("%03d%%", percent.round) val = value.to_s.rjust(value_width) max_s = max_value.to_s.rjust(max_value_width) "#{bar} #{pct} #{val} / #{max_s}" end
Source
# File plugins/btop.rb, line 374 def self.format_metrics_lines(stats) value_width, max_value_width = metric_column_widths(stats) data_lines = METRIC_ORDER.map do |key| metric = stats.fetch(key) format_metric_data( percent: metric[:percent], value: metric[:value], max_value: metric[:max_value], fill_style: metric[:fill_style], value_width: value_width, max_value_width: max_value_width ) end data_width = data_lines.map { |line| plain_irc_length(line) }.max METRIC_ORDER.zip(data_lines).map do |key, data| metric = stats.fetch(key) padded = pad_plain_irc(data, data_width) "#{padded} #{STYLE_LABEL}#{metric[:label]}#{IRC_RESET}" end.join("\n") end
Source
# File plugins/btop.rb, line 278 def self.format_network_summary(iface:, rx_bytes:, tx_bytes:) "#{iface} #{human_bytes(rx_bytes)}↓ #{human_bytes(tx_bytes)}↑" end
Source
# File plugins/btop.rb, line 349 def self.format_system_info(info) values = { host: info[:host], os: info[:os], kernel: info[:kernel], uptime: info[:uptime], packages: "#{info[:packages]} (pacman)", cpu: info[:cpu], procs: info[:procs], threads: info[:threads], net: info[:net] } [ system_info_art, SYSTEM_INFO_FIELDS.map { |key, label| format_system_info_field(key, label, values.fetch(key)) }.join(FIELD_SEPARATOR) ].join(" ") end
Source
# File plugins/btop.rb, line 344 def self.format_system_info_field(key, label, value) style = SYSTEM_INFO_FIELD_STYLES.fetch(key) "#{style}#{label.ljust(SYSTEM_INFO_LABEL_WIDTH)}#{IRC_RESET}: #{value}" end
Source
# File plugins/btop.rb, line 298 def self.format_uptime(seconds) total = seconds.to_f.round days = total / 86_400 hours = (total % 86_400) / 3600 minutes = (total % 3600) / 60 parts = [] parts << "#{days}d" if days.positive? parts << "#{hours}h" if days.positive? || hours.positive? parts << "#{minutes}m" parts.join(" ") end
Source
# File plugins/btop.rb, line 199 def self.gather_disk_stats(readers:) DISK_MOUNTS.to_h do |mount| disk = parse_df_output(readers.fetch(:df).call(mount)) [ disk_metric_key(mount), { percent: disk[:percent], value: human_bytes(disk[:used_bytes]), max_value: human_bytes(disk[:total_bytes]), label: disk_label(mount), fill_style: STYLE_FILL } ] end end
Source
# File plugins/btop.rb, line 403 def self.gather_stats(readers:) info = parse_meminfo(readers.fetch(:meminfo).call) mem = memory_stats(info) swp = swap_stats(info) first_cpu = parse_proc_stat_line(readers.fetch(:proc_stat).call) first_net = parse_net_dev(readers.fetch(:net_dev).call) readers.fetch(:sleep).call(NET_SAMPLE_INTERVAL) second_cpu = parse_proc_stat_line(readers.fetch(:proc_stat).call) second_net = parse_net_dev(readers.fetch(:net_dev).call) cpu_percent = cpu_percent_between(first_cpu, second_cpu) net = network_stats(first_net, second_net, interval_sec: NET_SAMPLE_INTERVAL) load = load_stats(readers.fetch(:loadavg).call, cpu_count: readers.fetch(:cpu_count).call) { cpu: { percent: cpu_percent, value: format("%.1f", cpu_percent), max_value: "100.0", label: "CPU", fill_style: STYLE_FILL }, mem: { percent: mem[:percent], value: human_size(mem[:used_kb]), max_value: human_size(mem[:total_kb]), label: "Mem", fill_style: STYLE_FILL }, swp: { percent: swp[:percent], value: human_size(swp[:used_kb]), max_value: human_size(swp[:total_kb]), label: "Swp", fill_style: STYLE_FILL }, load: { percent: load[:percent], value: load[:current], max_value: load[:max], label: "Load", fill_style: STYLE_FILL }, net: { percent: net[:percent], value: net[:value], max_value: net[:max_value], label: "Net", fill_style: STYLE_FILL } }.merge(gather_disk_stats(readers: readers)) end
Source
# File plugins/btop.rb, line 321 def self.gather_system_info(readers:) net = parse_net_dev(readers.fetch(:net_dev).call) { host: readers.fetch(:hostname).call.to_s.strip, os: parse_os_release(readers.fetch(:os_release).call)[:pretty_name], kernel: readers.fetch(:uname).call.to_s.strip, uptime: format_uptime(parse_uptime(readers.fetch(:uptime).call)), packages: readers.fetch(:packages).call.to_s.strip, cpu: cpu_model(readers.fetch(:cpuinfo).call), procs: readers.fetch(:proc_count).call.to_s.strip, threads: readers.fetch(:thread_count).call.to_s.strip, net: format_network_summary( iface: net[:iface], rx_bytes: net[:rx_bytes], tx_bytes: net[:tx_bytes] ) } end
Source
# File plugins/btop.rb, line 83 def self.human_bytes(bytes) if bytes.to_i >= 1_073_741_824 format("%.1fG", bytes / 1_073_741_824.0) elsif bytes.to_i >= 1_048_576 format("%.1fM", bytes / 1_048_576.0) else format("%.1fK", bytes / 1024.0) end end
Source
# File plugins/btop.rb, line 93 def self.human_rate(bytes_per_sec) bps = bytes_per_sec.to_f if bps >= 1_000_000_000 format("%.1fG", bps / 1_000_000_000.0) elsif bps >= 1_000_000 format("%.1fM", bps / 1_000_000.0) else format("%.1fK", bps / 1000.0) end end
Source
# File plugins/btop.rb, line 74 def self.human_size(kilobytes) bytes = kilobytes.to_i * 1024 if bytes >= 1_073_741_824 format("%.1fG", bytes / 1_073_741_824.0) else format("%.1fM", bytes / 1_048_576.0) end end
Source
# File plugins/btop.rb, line 29 def self.irc_color(fg, bg = nil) IrcFormat.color(fg, bg) end
Source
# File plugins/btop.rb, line 517 def self.kernel_sleep(seconds) Kernel.sleep(seconds) end
Source
# File plugins/btop.rb, line 215 def self.load_stats(loadavg, cpu_count:) load1 = loadavg.to_s.split[0].to_f cores = [cpu_count.to_i, 1].max percent = [(load1 / cores * 100).round, 100].min { percent: percent, current: format("%.1f", load1), max: format("%.1f", cores) } end
Source
# File plugins/btop.rb, line 157 def self.memory_stats(info) total = info["MemTotal"].to_i available = info["MemAvailable"] || info["MemFree"] available = available.to_i used = [total - available, 0].max percent = total.positive? ? (used * 100.0 / total).round : 0 { percent: percent, used_kb: used, total_kb: total } end
Source
# File plugins/btop.rb, line 368 def self.metric_column_widths(stats) value_width = METRIC_ORDER.map { |key| stats.fetch(key)[:value].to_s.length }.max max_value_width = METRIC_ORDER.map { |key| stats.fetch(key)[:max_value].to_s.length }.max [value_width, max_value_width] end
Source
# File plugins/btop.rb, line 256 def self.network_speed_between(first, second, interval_sec) interval = interval_sec.to_f return { rx_bps: 0, tx_bps: 0 } unless interval.positive? { rx_bps: [(second[:rx_bytes].to_i - first[:rx_bytes].to_i) / interval, 0].max, tx_bps: [(second[:tx_bytes].to_i - first[:tx_bytes].to_i) / interval, 0].max } end
Source
# File plugins/btop.rb, line 266 def self.network_stats(first, second, interval_sec: NET_SAMPLE_INTERVAL) speed = network_speed_between(first, second, interval_sec) throughput = speed[:rx_bps] + speed[:tx_bps] percent = NET_MAX_BPS.positive? ? [(throughput * 100.0 / NET_MAX_BPS).round, 100].min : 0 { percent: percent, value: human_rate(speed[:rx_bps]), max_value: NET_LINK_LABEL } end
Source
# File plugins/btop.rb, line 119 def self.pad_plain_irc(text, target_width) pad = target_width - plain_irc_length(text) return text if pad <= 0 "#{text}#{" " * pad}" end
Source
# File plugins/btop.rb, line 190 def self.parse_df_output(output) fields = output.to_s.strip.split used = fields[1].to_i total = fields[2].to_i percent = total.positive? ? (used * 100.0 / total).round : 0 { percent: percent, used_bytes: used, total_bytes: total } end
Source
# File plugins/btop.rb, line 149 def self.parse_meminfo(text) text.to_s.each_line.with_object({}) do |line, info| next unless line =~ /^(\w+):\s+(\d+)/ info[Regexp.last_match(1)] = Regexp.last_match(2).to_i end end
Source
# File plugins/btop.rb, line 227 def self.parse_net_dev(text) primary = { iface: "", rx_bytes: 0, tx_bytes: 0 } total_rx = 0 total_tx = 0 text.to_s.each_line do |line| next unless (match = line.match(/^\s*([^:\s]+):\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/)) iface = match[1] next if iface == "lo" rx_bytes = match[2].to_i tx_bytes = match[3].to_i total_rx += rx_bytes total_tx += tx_bytes traffic = rx_bytes + tx_bytes if traffic >= primary[:rx_bytes] + primary[:tx_bytes] primary = { iface: iface, rx_bytes: rx_bytes, tx_bytes: tx_bytes } end end { iface: primary[:iface], rx_bytes: total_rx, tx_bytes: total_tx } end
Source
# File plugins/btop.rb, line 282 def self.parse_os_release(text) pretty_name = nil text.to_s.each_line do |line| next unless line.start_with?("PRETTY_NAME=") pretty_name = line.split("=", 2).last.to_s.strip.delete('"') break end { pretty_name: pretty_name.to_s } end
Source
# File plugins/btop.rb, line 176 def self.parse_proc_stat_line(line) parts = line.to_s.split[1..].map(&:to_i) idle = parts[3] + parts.fetch(4, 0) { total: parts.sum, idle: idle } end
Source
# File plugins/btop.rb, line 294 def self.parse_uptime(text) text.to_s.split.first.to_f end
Source
# File plugins/btop.rb, line 115 def self.plain_irc_length(text) text.to_s.gsub(/\x03(?:\d{1,2}(?:,\d{1,2})?)?/, "").length end
Source
# File plugins/btop.rb, line 104 def self.progress_bar(percent, width: BAR_WIDTH, fill_style:, empty_style: STYLE_EMPTY) percent = [[percent.to_f, 0].max, 100].min filled = [(percent * width / 100.0).round, width].min empty = width - filled bar = +"" bar << "#{fill_style}#{"|" * filled}#{IRC_RESET}" if filled.positive? bar << "#{empty_style}#{"|" * empty}#{IRC_RESET}" if empty.positive? bar end
Source
# File plugins/btop.rb, line 484 def self.read_df(path) stdout, status = Open3.capture2("df", "-B1", "--output=used,size,target", path) raise StandardError, "df failed" unless status.success? line = stdout.lines.last.to_s.strip fields = line.split "Used #{fields[0]} #{fields[1]} #{fields[2]}" end
Source
# File plugins/btop.rb, line 493 def self.read_package_count stdout, status = Open3.capture2("pacman", "-Qq") raise StandardError, "pacman failed" unless status.success? stdout.lines.count.to_s end
Source
# File plugins/btop.rb, line 500 def self.read_proc_count Dir.children("/proc").count { |entry| entry.match?(/\A\d+\z/) }.to_s end
Source
# File plugins/btop.rb, line 504 def self.read_thread_count total = 0 Dir.glob("/proc/[0-9]*/status").each do |path| File.foreach(path) do |line| next unless line.start_with?("Threads:") total += line.split[1].to_i break end end total.to_s end
Source
# File plugins/btop.rb, line 457 def self.report(readers: default_readers) system_info = gather_system_info(readers: readers) build_report(gather_stats(readers: readers), system_info: system_info) rescue StandardError "Unable to read system stats." end
Source
# File plugins/btop.rb, line 167 def self.swap_stats(info) total = info["SwapTotal"].to_i free = info["SwapFree"].to_i used = [total - free, 0].max percent = total.positive? ? (used * 100.0 / total).round : 0 { percent: percent, used_kb: used, total_kb: total } end
Source
# File plugins/btop.rb, line 340 def self.system_info_art "#{irc_color(10)}[■]#{IRC_RESET}" end
Public Instance Methods
Source
# File plugins/btop.rb, line 525 def execute(m) themed_flood_safe_reply(m, report) end
Source
# File plugins/btop.rb, line 521 def report(readers: self.class.default_readers) self.class.report(readers: readers) end