Skip to content

Commit

Permalink
Refactor API and file stats parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
j8r committed Nov 15, 2019
1 parent 8570b47 commit 6436128
Show file tree
Hide file tree
Showing 21 changed files with 612 additions and 427 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ memory.used #=> 2731404
memory.percent.to_i #=> 32
cpu = Hardware::CPU.new
pid = Hardware::PID.new # Default is Process.pid
app = Hardware::PID.new "firefox" # Take the first matching PID
pid_stat = Hardware::PID.new.stat # Default is Process.pid
app_stat = Hardware::PID.new("firefox").stat # Take the first matching PID
loop do
sleep 1
cpu.usage.to_i #=> 17
pid.cpu_usage #=> 1.5
app.cpu_usage.to_i #=> 4
p cpu.usage!.to_i #=> 17
p pid_stat.cpu_usage! #=> 1.5
p app_stat.cpu_usage!.to_i #=> 4
end
```
## Development
Expand Down
4 changes: 2 additions & 2 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: hardware
version: 0.4.0
version: 0.5.0

authors:
- bararchy <bar.hofesh@safe-t.com>
- Julien Reichardt <mi@jrei.ch>

crystal: 0.25.1
crystal: 0.31.1

license: MIT
28 changes: 16 additions & 12 deletions spec/cpu_spec.cr
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
require "./spec_helper"
require "spec"
require "../src/cpu"

describe Hardware::CPU do
cpu = Hardware::CPU.new
it "parses '/proc/stat'" do
cpu.stat.should be_a Array(Int32)
it "returns the usage compared to a previous CPU'" do
cpu = Hardware::CPU.new
sleep 1
cpu_usage = cpu.usage Hardware::CPU.new
cpu_usage.should be > 0
cpu_usage.should be <= 100
end

it "checks the percentage used" do
cpu.previous_used.should be_a Int32
cpu.previous_idle_wait.should be_a Int32
it "parses the last field (guest_nice)" do
Hardware::CPU.new.guest_nice.should be_a Int32
end

it "checks the percentage used" do
sleep 0.1
usage = cpu.usage
usage.should be >= 0
usage.should be <= 100
it "returns the usage by mutating self" do
cpu = Hardware::CPU.new
sleep 1
cpu_usage = cpu.usage!
cpu_usage.should be > 0
cpu_usage.should be <= 100
end
end
2 changes: 1 addition & 1 deletion spec/hardware_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "./spec_helper"
require "spec"

describe Hardware do
end
3 changes: 2 additions & 1 deletion spec/memory_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec_helper"
require "spec"
require "../src/memory"

describe Hardware::Memory do
memory = Hardware::Memory.new
Expand Down
3 changes: 2 additions & 1 deletion spec/net_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec_helper"
require "spec"
require "../src/net"

describe Hardware::Net do
net = Hardware::Net.new
Expand Down
38 changes: 38 additions & 0 deletions spec/pid/stat_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require "spec"
require "../../src/pid"

describe Hardware::PID::Stat do
describe "cpu_time" do
it "returns without children" do
stat = Hardware::PID::Stat.new pid: 1
stat.cpu_time.should be > 0
end

it "returns with children" do
stat = Hardware::PID::Stat.new pid: 1
stat.cpu_time(children: true).should be > 0
end
end

it "checks cpu_usage percentage of all processes" do
channel = Channel(Float64).new
pids_count = 0
Hardware::PID.each do |pid|
spawn do
stat = pid.stat
sleep 9
channel.send stat.cpu_usage!
end
pids_count += 1
end
max_cpu_usage = 0
pids_count.times do
cpu_usage = channel.receive
cpu_usage.should be >= 0
cpu_usage.should be <= 100
max_cpu_usage = cpu_usage if max_cpu_usage < cpu_usage
end
# At least one process in the system should have a cpu_usage superior to 0
max_cpu_usage.should be > 0
end
end
10 changes: 10 additions & 0 deletions spec/pid/status_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require "spec"
require "../../src/pid"

describe Hardware::PID::Status do
status = Hardware::PID.new

it "parses status name" do
status.name.should eq "crystal-run-spec.tmp"
end
end
77 changes: 5 additions & 72 deletions spec/pid_spec.cr
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
require "./spec_helper"
require "spec"
require "../src/pid"

describe Hardware::PID do
describe "class methods" do
it "tests .all " do
Hardware::PID.all &.should be_a Hardware::PID
end

it "tests .get_pids of the current process" do
Hardware::PID.get_pids("crystal-run-spec.tmp").should eq [Process.pid]
end

it "cpu_total_current equal to cpu_total_previous" do
Hardware::PID.cpu_total_current.should eq 0
Hardware::PID.get_pids "crystal-run-spec.tmp", &.should eq Process.pid
end
end

describe "instance methods" do
it "creates a Hardware::PID based on a name" do
Hardware::PID.new("crystal-run-spec.tmp", cpu_total: false).pid.should eq Process.pid
Hardware::PID.new("crystal-run-spec.tmp").number.should eq Process.pid
end

pid = Hardware::PID.new

it "creates a non existant PID" do
expect_raises Exception do
Hardware::PID.new(pid: 0)
Hardware::PID.new(number: 0)
end
end

Expand All @@ -36,66 +29,6 @@ describe Hardware::PID do
File.basename(pid.command).should eq "crystal-run-spec.tmp "
end

describe "tests CPU related methods for" do
describe "cpu_time" do
pid1 = Hardware::PID.new(pid: 1)
it "without children" do
pid1.cpu_time.should be > 1
end

it "with children" do
pid1.cpu_time(children: true).should be > 1
end
end

describe "cpu_usage" do
pid1 = Hardware::PID.new(pid: 1)

it "percentage" do
# Simulate CPU use if no activity
sleep 0.1
usage = pid1.cpu_usage
usage.should be > 0_f32
usage.should be <= 100_f32
end

it "cpu_total_previous equal to cpu_total_current" do
pid1.cpu_total_previous.should eq Hardware::PID.cpu_total_current
end
end
describe "cpu_usage with no updates" do
Hardware::PID.cpu_total_current = -1
pid1 = Hardware::PID.new(pid: 1, cpu_total: false)
pid1.cpu_time_previous = -1

it "type" do
pid1.cpu_usage.should be_a Float32
end

it "cpu_time_previous" do
pid1.cpu_time_previous.should be > 1
end

it "cpu_total_current not updated" do
Hardware::PID.cpu_total_current.should eq -1
end

it "cpu_total_previous not updated" do
pid1.cpu_total_previous.should eq -1
end
end
end

it "returns memory usage" { pid.memory.should be > 1 }

it "parses name" { pid.name.should eq "crystal-run-spec.tmp" }

it "parses name" { pid.net.should be_a Hardware::Net }

it "parses stat" { pid.stat.should be_a Hardware::PID::Stat }

it "parses statm" { pid.statm.should be_a Array(Int32) }

it "parses status" { pid.status.should be_a Hash(String, String) }
end
end
2 changes: 0 additions & 2 deletions spec/spec_helper.cr

This file was deleted.

103 changes: 103 additions & 0 deletions src/cpu.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# CPU related informations of your system.
#
# ```
# cpu = Hardware::CPU.new
# loop do
# sleep 1
# p cpu.usage!.to_i # => 17
# end
# ```
struct Hardware::CPU
# CPU number. `nil` means the whole cores in total.
getter number : Int32?

# Creates a new CPU stat to monitor the given core.
#
# Must be lower than `System.cpu_count`, or `nil` for the whole cores in total.
def initialize(number : Int32? = nil, parse_stats : Bool = true)
if @number = number
raise "CPU number must be superior or equal to 0, and inferior to #{System.cpu_count}" unless 0 <= number < System.cpu_count
end
parse_stat_file if parse_stats
end

{% begin %}
{% stats = %w(user nice system idle iowait irq softirq steal guest guest_nice) %}
{% for stat in stats %}
# Returns the {{stat}} stat field.
getter {{stat.id}} : Int32 { parse_stat_file; @{{stat.id}} || raise "Field not parsed: '{{stat.id}}'" }
{% end %}

private def parse_stat_line(column_num : Int32, buffer : IO)
case column_num
{% i = 1 %}
{% for stat in stats %}
when {{i}} then @{{stat.id}} = buffer.to_s.to_i
{% i = i + 1 %}
{% end %}
end
buffer.clear
end
{% end %}

private def parse_stat_file
# /proc/stat content:
# cpu
# cpu0
# cpu1
# ...
line_num = -1
column_num = 0
buffer = IO::Memory.new
File.open "/proc/stat", &.each_char do |char|
if !@number && line_num == -1 || line_num == @number
if (char == ' ' || char == '\n') && !buffer.empty?
parse_stat_line column_num, buffer

buffer.clear
column_num += 1
else
buffer << char
end
end
if char == '\n'
line_num += 1
break if line_num < System.cpu_count
column_num = 0
end
end
end

# Sum of `user`, `nice`, `system`, `irq`, `softirq` and `steal`.
getter used : Int32 { user + nice + system + irq + softirq + steal }

# Sum of `idle` and `iowait`.
getter idle_total : Int32 { idle + iowait }

# Sum of `used` and `idle_total`
getter total : Int32 { used + idle_total }

# Returns each CPU usage in percentage based on the previous `CPU`.
def usage(previous_cpu : CPU = self) : Float64
# Usage Time / Total Time * 100
(used - previous_cpu.used) / (total - previous_cpu.total) * 100
end

# Like `#usage`, but mutates the instance.
#
# ```
# cpu = Hardware::CPU.new
# loop do
# sleep 1
# p cpu.usage!.to_i # => 17
# end
# ```
def usage! : Float64
# 100 * Usage Time / Total Time
@user || raise "Stat file not previously parsed"
previous_cpu = self
@used = @idle_total = @total = nil
parse_stat_file
usage previous_cpu
end
end
3 changes: 1 addition & 2 deletions src/hardware.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require "./hardware/**"
require "./*"

module Hardware
VERSION = "0.4.0"
end

0 comments on commit 6436128

Please sign in to comment.