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:

  • sys.stats

Attributes

timer R

A timer status subclass.

Public Class Methods

new( * )

Create a new device adapter.

# File lib/ravn/hal/device/system_stats.rb, line 45
def initialize( * )
        @timer = self.create_timer
        super
end

Public Instance Methods

parse_all()

Massage gathered data into a suitable structure for a Ravn::Message event.

# File lib/ravn/hal/device/system_stats.rb, line 78
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
parse_diskstats()

Return a hash of the most useful information from /proc/diskstats. www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats

# File lib/ravn/hal/device/system_stats.rb, line 104
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 # filter out loopbacks, ram disks
                acc[ devname ] = {
                        read_count:       reads,
                        write_count:      writes,
                        io_current_count: io_in_progress,
                        io_time_spent:    time_in_io # ms
                }.transform_values( &:to_i )
        end

rescue => err
        self.log.error "Unable to parse diskstats: %s" % [ err.message ]
        return {}
end
parse_loadavg()

Return a hash of the most useful information from /proc/loadavg.

# File lib/ravn/hal/device/system_stats.rb, line 179
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
parse_meminfo()

Return a hash of the most useful information from /proc/meminfo. All values are in kB.

# File lib/ravn/hal/device/system_stats.rb, line 126
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
parse_netdev()

Return a hash of the most useful information from /proc/net/dev.

# File lib/ravn/hal/device/system_stats.rb, line 193
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
parse_stat()

Return a hash of the most useful information from /proc/stat.

# File lib/ravn/hal/device/system_stats.rb, line 150
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
parse_thermals()

Return a hash of thermal zone temperatures in Celsius.

# File lib/ravn/hal/device/system_stats.rb, line 215
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
run_gather( * )

Begin parsing various sources and send the resulting event.

# File lib/ravn/hal/device/system_stats.rb, line 70
def run_gather( * )
        data = self.parse_all
        self.send_event( data )
end
send_event( data )

Emit the sys.stats event.

# File lib/ravn/hal/device/system_stats.rb, line 96
def send_event( data )
        msg = Ravn::HAL::Message.new( 'sys.stats', data: data )
        self.filter_up( msg )
end
start()

Fire up an interval timer for polling various system attributes.

# File lib/ravn/hal/device/system_stats.rb, line 55
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_timer()

Create a Concurrent::TimerTask that gathers statistics on an interval.

# File lib/ravn/hal/device/system_stats.rb, line 234
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