Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor API and file stats parsers #15

Merged
merged 3 commits into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 6
veelenga marked this conversation as resolved.
Show resolved Hide resolved
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 6
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 4
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
veelenga marked this conversation as resolved.
Show resolved Hide resolved
# 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