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

Add a new riemann-hwmon tool for harware monitors #297

Merged
merged 2 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bin/riemann-hwmon
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

Process.setproctitle($PROGRAM_NAME)

require 'riemann/tools/hwmon'

Riemann::Tools::Hwmon.run
131 changes: 131 additions & 0 deletions lib/riemann/tools/hwmon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# frozen_string_literal: true

require 'riemann/tools'

# See https://www.kernel.org/doc/html/latest/hwmon/index.html
module Riemann
module Tools
class Hwmon
include Riemann::Tools

class Device
attr_reader :hwmon, :type, :number, :crit, :lcrit, :label, :name

def initialize(hwmon, type, number)
@hwmon = hwmon
@type = type
@number = number

@crit = scale(read_hwmon_i('crit'))
@lcrit = scale(read_hwmon_i('lcrit'))
@label = read_hwmon_s('label')
@name = read_hwmon_file('name')
end

def input
read_hwmon_i('input')
end

def report
value = scale(input)

state = :ok
state = :critical if crit && value >= crit
state = :critical if lcrit && value <= lcrit
{
service: "hwmon #{name} #{label}",
state: state,
metric: value,
description: fromat_input(value),
}
end

private

def scale(value)
return nil if value.nil?

case type
when :fan then value.to_i # rpm
when :in, :temp, :curr then value.to_f / 1000 # mV, m°C, mA
when :humidity then value.to_f / 100 # %H
when :power, :energy then value.to_f / 1_000_000 # uW, uJ
end
end

def fromat_input(value)
case type
when :in then format('%<value>.3f V', { value: value })
when :fan then "#{value} RPM"
when :temp then format('%<value>.3f °C', { value: value })
when :curr then format('%<value>.3f A', { value: value })
when :power then format('%<value>.3f W', { value: value })
when :energy then format('%<value>.3f J', { value: value })
when :humidity then format('%<value>d %H', { value: (value * 100).to_i })
end
end

def read_hwmon_i(file)
s = read_hwmon_s(file)
return nil if s.nil?

s.to_i
end

def read_hwmon_s(file)
read_hwmon_file("#{type}#{number}_#{file}")
end

def read_hwmon_file(file)
File.read("/sys/class/hwmon/hwmon#{hwmon}/#{file}").chomp
rescue Errno::ENOENT
nil
end
end

FIRST_NUMBER = {
in: 0,
fan: 1,
temp: 1,
curr: 1,
power: 1,
energy: 1,
humidity: 1,
}.freeze

attr_reader :devices

def initialize
super

@devices = poll_devices
end

def poll_devices
res = []

hwmon = 0
while File.exist?("/sys/class/hwmon/hwmon#{hwmon}")
%i[in fan temp curr power energy humidity].each do |type|
number = FIRST_NUMBER[type]
while File.exist?("/sys/class/hwmon/hwmon#{hwmon}/#{type}#{number}_input")
res << Device.new(hwmon, type, number)

number += 1
end
end

hwmon += 1
end

res
end

def tick
devices.each do |device|
report(device.report)
end
end
end
end
end
6 changes: 4 additions & 2 deletions spec/riemann/tools/http_check_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
require 'openssl'
begin
require 'rackup/handler/webrick'
RACK_HANDLER = Rackup::Handler::WEBrick
rescue LoadError
# XXX: Needed for Ruby 2.6 compatibility
# Moved to the rackup gem in recent versions
require 'rack/handler/webrick'
RACK_HANDLER = Rack::Handler::WEBrick
end
require 'sinatra/base'
require 'webrick'
Expand Down Expand Up @@ -108,7 +110,7 @@ def protected!
Logger: WEBrick::Log.new(File.open(File::NULL, 'w')),
}
@server = WEBrick::HTTPServer.new(server_options)
@server.mount('/', Rack::Handler::WEBrick, TestWebserver)
@server.mount('/', RACK_HANDLER, TestWebserver)
@started = false
Thread.new { @server.start }
Timeout.timeout(1) { sleep(0.1) until @started }
Expand Down Expand Up @@ -260,7 +262,7 @@ def protected!
SSLCertName: '/CN=example.com',
}
@server = WEBrick::HTTPServer.new(server_options)
@server.mount('/', Rack::Handler::WEBrick, TestWebserver)
@server.mount('/', RACK_HANDLER, TestWebserver)
@started = false
Thread.new { @server.start }
Timeout.timeout(1) { sleep(0.1) until @started }
Expand Down
29 changes: 29 additions & 0 deletions spec/riemann/tools/hwmon/device_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'riemann/tools/hwmon'

RSpec.describe Riemann::Tools::Hwmon::Device do
subject { described_class.new(1, :temp, 2) }

before do
allow(File).to receive(:read).with('/sys/class/hwmon/hwmon1/name').and_return("i350bb\n")
allow(File).to receive(:read).with('/sys/class/hwmon/hwmon1/temp2_input').and_return("#{input}\n")
allow(File).to receive(:read).with('/sys/class/hwmon/hwmon1/temp2_crit').and_return("96000\n")
allow(File).to receive(:read).with('/sys/class/hwmon/hwmon1/temp2_lcrit').and_raise(Errno::ENOENT)
allow(File).to receive(:read).with('/sys/class/hwmon/hwmon1/temp2_label').and_return("loc1\n")
end

describe '#report' do
context 'when temperature is ok' do
let(:input) { 31_000 }

it { expect(subject.report).to eq({ service: 'hwmon i350bb loc1', state: :ok, metric: 31.0, description: '31.000 °C' }) }
end

context 'when temperature is critical' do
let(:input) { 96_000 }

it { expect(subject.report).to eq({ service: 'hwmon i350bb loc1', state: :critical, metric: 96.0, description: '96.000 °C' }) }
end
end
end