Ravn::HAL::Device::
SystemStats
class
A simple gatherer/reporter for the state of various operating system resources. Currently assumes we’re running on a Linux OS for most stats.
-
System wide memory consumption
-
Process memory consumption
-
System wide CPU usage and load
-
Process CPU usage
-
Disk I/O
-
Network I/O
Responsible for generating the following event:
- timer R
A timer status subclass.
Create a new device adapter.
def initialize( * )
@timer = self.create_timer
super
end
Massage gathered data into a suitable structure for a Ravn::Message event.
def parse_all
data = Hash.new{|k,v| k[v] = {} }
data[ :usage ] = Process.rusage.to_h
data.merge!( self.parse_stat )
data[ :cpu ].merge!( self.parse_loadavg )
data[ :processes ][ :total ] = data[ :cpu ].delete( :proc_total )
data[ :disk ] = self.parse_diskstats
data[ :memory ] = self.parse_meminfo
data[ :network ] = self.parse_netdev
data[ :thermals ] = self.parse_thermals
return data
end
Return a hash of the most useful information from /proc/diskstats. www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
def parse_diskstats
return Pathname.new( '/proc/diskstats' ).each_line.with_object({}) do |line, acc|
major, _, devname, reads, _, _, _,
writes, _, _, _, io_in_progress, time_in_io = line.split
next if major.to_i <= 7
acc[ devname ] = {
read_count: reads,
write_count: writes,
io_current_count: io_in_progress,
io_time_spent: time_in_io
}.transform_values( &:to_i )
end
rescue => err
self.log.error "Unable to parse diskstats: %s" % [ err.message ]
return {}
end
Return a hash of the most useful information from /proc/loadavg.
def parse_loadavg
fields = Pathname.new( '/proc/loadavg' ).read.split
return {
load: fields[ 0 ].to_f,
proc_total: fields[ 3 ].sub( %r|\d+/|, '' ).to_i
}
rescue => err
self.log.error "Unable to parse loadavg: %s" % [ err.message ]
return {}
end
Return a hash of the most useful information from /proc/meminfo. All values are in kB.
def parse_meminfo
stats = Pathname.new( '/proc/meminfo' ).each_line.with_object({}) do |line, acc|
if match = line.match( /(?<label>\w+):\s+(?<val>\d+)/ )
acc[ match[:label].downcase.to_sym ] = match[ :val ].to_i
end
end
return {
total: stats[ :memtotal ],
free: stats[ :memfree ],
available: stats[ :memavailable ],
active: stats[ :active ],
inactive: stats[ :inactive ],
swaptotal: stats[ :swaptotal ],
swapfree: stats[ :swapfree ]
}
rescue => err
self.log.error "Unable to parse meminfo: %s" % [ err.message ]
return {}
end
Return a hash of the most useful information from /proc/net/dev.
def parse_netdev
return Pathname.new( '/proc/net/dev' ).each_line.with_object( {} ) do |line, acc|
fields = line.split
next unless fields.first.index( ':' )
acc[ fields.first[0..-2] ] = {
bytes_rx: fields[ 1 ],
packets_rx: fields[ 2 ],
errors_rx: fields[ 3 ],
bytes_tx: fields[ 9 ],
packets_tx: fields[ 10 ],
errors_tx: fields[ 11 ]
}.transform_values( &:to_i )
end
rescue => err
self.log.error "Unable to parse network info: %s" % [ err.message ]
return {}
end
Return a hash of the most useful information from /proc/stat.
def parse_stat
hsh = Hash.new{|k,v| k[v] = {} }
return Pathname.new( '/proc/stat' ).each_line.with_object( hsh ) do |line, acc|
fields = line.split
case fields.first
when 'cpu'
acc[ :cpu ] = {
user: fields[ 1 ],
system: fields[ 3 ],
idle: fields[ 4 ],
iowait: fields[ 5 ]
}.transform_values( &:to_i )
when 'procs_running'
acc[ :processes ][ :running ] = fields[1].to_i
when 'procs_blocked'
acc[ :processes ][ :blocked ] = fields[1].to_i
end
end
rescue => err
self.log.error "Unable to parse stat: %s" % [ err.message ]
return {}
end
Return a hash of thermal zone temperatures in Celsius.
def parse_thermals
return Pathname.new( '/sys/class/thermal' ).
children.select{|c| c.basename.to_s.start_with?("thermal") }.
each_with_object( {} ) do |thermal, acc|
acc[ thermal.basename.to_s.sub('thermal_', '' ) ] = (thermal + 'temp').read.to_f / 1000
end
rescue => err
self.log.error "Unable to parse thermals: %s" % [ err.message ]
return {}
end
Begin parsing various sources and send the resulting event.
def run_gather( * )
data = self.parse_all
self.send_event( data )
end
Emit the sys.stats event.
def send_event( data )
msg = Ravn::HAL::Message.new( 'sys.stats', data: data )
self.filter_up( msg )
end
Fire up an interval timer for polling various system attributes.
def start
super
if RbConfig::CONFIG[ 'host_os' ] =~ /linux/i
self.timer.execute
else
self.log.warn "Not gathering system statistics, " +
"expected a Linux host (got %p instead)" % [
RbConfig::CONFIG[ 'host_os' ]
]
end
end
Protected Instance Methods
Create a Concurrent::TimerTask that gathers statistics on an interval.
def create_timer
timer = Concurrent::TimerTask.new( &self.method(:run_gather) )
timer.execution_interval = self.class.status_frequency
timer.add_observer( Ravn::LoggingTaskObserver.new(:systemstats_timer) )
return timer
end