Skip to content

Commit

Permalink
Add a bunch more graphs
Browse files Browse the repository at this point in the history
  • Loading branch information
hschne committed Sep 30, 2024
1 parent 0c14e9c commit 79a7bab
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 22 deletions.
4 changes: 4 additions & 0 deletions app/models/bandwidth_usage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class BandwidthUsage < ActiveRecord::Base
end
4 changes: 4 additions & 0 deletions app/models/disk_io.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# # frozen_string_literal: true

class DiskIO < ActiveRecord::Base
end
38 changes: 37 additions & 1 deletion app/puny_monitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
require_relative "models/cpu_load"
require_relative "models/memory_usage"
require_relative "models/filesystem_usage"
require_relative "models/disk_io"
require_relative "models/bandwidth_usage"
require_relative "../lib/system_utils"

require_relative "../config/application"
Expand Down Expand Up @@ -63,10 +65,44 @@ class App < Sinatra::Base
filesystem_usage.to_json
end

get "/data/disk_io" do
content_type :json
end_time = Time.now
start_time = end_time - 1.hour
[
{ name: "Read MB/s", data: DiskIO.where(created_at: start_time..end_time)
.group_by_minute(:created_at, n: 1, series: true)
.average(:read_mb_per_sec) },
{ name: "Write MB/s", data: DiskIO.where(created_at: start_time..end_time)
.group_by_minute(:created_at, n: 1, series: true)
.average(:write_mb_per_sec) }
].to_json
end

get "/data/bandwidth" do
content_type :json
end_time = Time.now
start_time = end_time - 1.hour
[
{ name: "Incoming Mbps", data: BandwidthUsage.where(created_at: start_time..end_time)
.group_by_minute(:created_at, n: 1, series: true)
.average(:incoming_mbps) },
{ name: "Outgoing Mbps", data: BandwidthUsage.where(created_at: start_time..end_time)
.group_by_minute(:created_at, n: 1, series: true)
.average(:outgoing_mbps) }
].to_json
end

@scheduler.every "5s" do
CpuLoad.create(load_average: SystemUtils.cpu_load_average)
CpuLoad.create(load_average: SystemUtils.cpu_usage_percent)
MemoryUsage.create(used_percent: SystemUtils.memory_usage_percent)
FilesystemUsage.create(used_percent: SystemUtils.filesystem_usage_percent)

disk_io = SystemUtils.disk_io_stats
DiskIO.create(read_mb_per_sec: disk_io[:read_mb_per_sec], write_mb_per_sec: disk_io[:write_mb_per_sec])

bandwidth = SystemUtils.bandwidth_usage
BandwidthUsage.create(incoming_mbps: bandwidth[:incoming_mbps], outgoing_mbps: bandwidth[:outgoing_mbps])
end
end
end
30 changes: 26 additions & 4 deletions app/views/index.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
<h1>System Information</h1>

<h2>Recent CPU Loads</h2>
<h2>CPU Usage</h2>
<%= area_chart "/data/cpu",
ytitle: "Load Average",
ytitle: "CPU Usage (%)",
min: 0,
max: 100,
library: {
title: {
text: "CPU Usage",
},
},
refresh: 5 %>

<h2>Bandwidth Usage</h2>
<%= line_chart "/data/bandwidth",
ytitle: "Bandwidth (Mbps)",
library: {
title: {
text: "CPU Load Over Time",
text: "Bandwidth Usage",
},
},
refresh: 5 %>

<h2>Memory Usage</h2>
<%= line_chart "/data/memory",
ytitle: "Used Memory (%)",
ytitle: "Memory Usage (%)",
min: 0,
max: 100,
library: {
Expand All @@ -33,3 +45,13 @@ library: {
},
},
refresh: 5 %>

<h2>Disk I/O</h2>
<%= area_chart "/data/disk_io",
ytitle: "MB/s",
library: {
title: {
text: "Disk I/O",
},
},
refresh: 5 %>
8 changes: 1 addition & 7 deletions config/initializers/chartkick.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,5 @@
height: "400px",
xtitle: "Time",
points: false,
curve: false,
library: {
yAxis: {
min: 0,
max: 100
}
}
curve: false
}
9 changes: 9 additions & 0 deletions db/migrate/20230516000000_create_disk_ios.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class CreateDiskIos < ActiveRecord::Migration[7.0]
def change
create_table :disk_ios do |t|
t.float :read_mb_per_sec
t.float :write_mb_per_sec
t.timestamps
end
end
end
9 changes: 9 additions & 0 deletions db/migrate/20230517000000_create_bandwidth_usages.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class CreateBandwidthUsages < ActiveRecord::Migration[7.0]
def change
create_table :bandwidth_usages do |t|
t.float :incoming_mbps
t.float :outgoing_mbps
t.timestamps null: false
end
end
end
16 changes: 15 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,27 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2023_05_15_123458) do
ActiveRecord::Schema[7.2].define(version: 2024_09_30_155845) do
create_table "bandwidth_usages", force: :cascade do |t|
t.float "incoming_mbps"
t.float "outgoing_mbps"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "cpu_loads", force: :cascade do |t|
t.float "load_average"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "disk_ios", force: :cascade do |t|
t.float "read_mb_per_sec"
t.float "write_mb_per_sec"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "filesystem_usages", force: :cascade do |t|
t.float "used_percent"
t.datetime "created_at", null: false
Expand Down
Empty file added db/seeds.rb
Empty file.
111 changes: 102 additions & 9 deletions lib/system_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@

module SystemUtils
class << self
def cpu_load_average
load_avg_file = "#{proc_path}/loadavg"
File.readlines(load_avg_file).first.to_f
def cpu_usage_percent
prev_cpu = read_cpu_stat
sleep(1)
current_cpu = read_cpu_stat

prev_idle = prev_cpu[:idle] + prev_cpu[:iowait]
idle = current_cpu[:idle] + current_cpu[:iowait]

prev_non_idle = prev_cpu[:user] + prev_cpu[:nice] + prev_cpu[:system] + prev_cpu[:irq] + prev_cpu[:softirq] + prev_cpu[:steal]
non_idle = current_cpu[:user] + current_cpu[:nice] + current_cpu[:system] + current_cpu[:irq] + current_cpu[:softirq] + current_cpu[:steal]

prev_total = prev_idle + prev_non_idle
total = idle + non_idle

total_diff = total - prev_total
idle_diff = idle - prev_idle

cpu_percentage = ((total_diff - idle_diff).to_f / total_diff * 100).round(2)
[cpu_percentage, 100.0].min
end

def memory_usage_percent
Expand All @@ -15,9 +31,6 @@ def memory_usage_percent
cached = mem_info.match(/Cached:\s+(\d+)/)[1].to_f
used = total - free - buffers - cached
(used / total * 100).round(2)
rescue StandardError => e
puts "Error reading memory usage: #{e.message}"
0.0
end

def filesystem_usage_percent(mount_point = "/")
Expand All @@ -27,15 +40,95 @@ def filesystem_usage_percent(mount_point = "/")
used_blocks = total_blocks - available_blocks
used_percent = (used_blocks.to_f / total_blocks * 100).round(2)
[used_percent, 100.0].min
rescue StandardError => e
puts "Error reading filesystem usage: #{e.message}"
0.0
end

def disk_io_stats
prev_stats = read_disk_stats
sleep(1)
curr_stats = read_disk_stats

read_sectors = curr_stats[:read_sectors] - prev_stats[:read_sectors]
write_sectors = curr_stats[:write_sectors] - prev_stats[:write_sectors]

sector_size = 512
read_mb_per_sec = (read_sectors * sector_size / 1_048_576.0).round(2)
write_mb_per_sec = (write_sectors * sector_size / 1_048_576.0).round(2)

{
read_mb_per_sec:,
write_mb_per_sec:
}
end

def bandwidth_usage
prev_stats = read_network_stats
sleep(1)
curr_stats = read_network_stats

incoming_bytes = curr_stats[:rx_bytes] - prev_stats[:rx_bytes]
outgoing_bytes = curr_stats[:tx_bytes] - prev_stats[:tx_bytes]

bytes_to_mbits = 8.0 / 1_000_000 # Convert bytes to megabits
{
incoming_mbps: (incoming_bytes * bytes_to_mbits).round(2),
outgoing_mbps: (outgoing_bytes * bytes_to_mbits).round(2)
}
end

private

def proc_path
ENV.fetch("PROC_PATH", "/proc")
end

def read_cpu_stat
cpu_stats = File.read("#{proc_path}/stat").lines.first.split(/\s+/)
{
user: cpu_stats[1].to_i,
nice: cpu_stats[2].to_i,
system: cpu_stats[3].to_i,
idle: cpu_stats[4].to_i,
iowait: cpu_stats[5].to_i,
irq: cpu_stats[6].to_i,
softirq: cpu_stats[7].to_i,
steal: cpu_stats[8].to_i
}
end

def read_disk_stats
primary_disk = File.read("#{proc_path}/partitions")
.lines
.drop(2)
.first
.split
.last

stats = File.read("#{proc_path}/diskstats")
.lines
.map(&:split)
.find { |line| line[2] == primary_disk }

{
read_sectors: stats[5].to_i,
write_sectors: stats[9].to_i
}
end

def read_network_stats
primary_interface = File.read("#{proc_path}/net/route")
.lines
.drop(1)
.find { |line| line.split[1] == "00000000" }
&.split&.first
stats = File.read("#{proc_path}/net/dev")
.lines
.map(&:split)
.find { |line| line[0].chomp(":") == primary_interface }

{
rx_bytes: stats[1].to_i,
tx_bytes: stats[9].to_i
}
end
end
end

0 comments on commit 79a7bab

Please sign in to comment.