#!/usr/bin/ruby

require 'yaml'


#
#  This class either a) returns the output of running commands, or
# b) returns the output from a faked command
#
class CommandWrapper

  def CommandWrapper.run_command( str )

    if ( ENV['TEST'] && ENV['TEST_PREFIX'] )
      #
      #  Count the number of commands we've executed so far.
      #
      count = ENV['TEST_COUNT'] || '0'
      count = count.to_i
      ENV['TEST_COUNT'] = (count + 1).to_s

      #
      #  Read the output from the faked file.
      #
      #    $prefix/$count.cmd
      #
      file = "#{ENV['TEST_PREFIX']}/#{count}.cmd"
      File.open( file, "r" ).readlines().join()
    else
      `#{str}`
    end
  end
end


#
# Class to look for errors in the output of `dmesg`.
#
class SystemMessages

  #
  #  Return an array of errors found in `dmesg` output.
  #
  def self.errors

    #
    # Alerts we've found
    #
    found = {}

    #
    #  This is a list of error-patterns, and human-readable
    # descriptions.
    #
    #  We've implemented this check this way to ensure that
    # we only alert once even if we see the same error potentially
    # hundreds of times.
    #
    tests = {
      "Memory Error"  => { :pattern => /MCE MEMORY ERROR/i, :minimum => 5 },

      "Machine Check Exception"  =>{ :pattern => /Machine Check Exception/i, :minimum => 1 },
      "Kernel Bug" => { :pattern => /BUG:/i, :minimum => 1},
      "Memory Error - scrubbing" => { :pattern => /memory scrubbing error/i, :minimum => 5 },

      "DIMM Error" => { :pattern => /EDAC.*DIMM.*error/i, :minimum => 5 }
    }

    CommandWrapper.run_command( "dmesg" ).split("\n").each do |line|

      #
      #  For each line test each pattern - store the human-version
      # of the error-message in the hash to collapse uniques.
      #
      tests.each do |name, data|

        #
        # The regexp.
        #
        regexp  = data[:pattern]
        minimum = data[:minimum]

        #
        # Save the matching line in the output, if we've not already
        # done so.
        #
        if ( regexp.match( line ) )

          if ( found[name].nil? )
            found[name] = {}
          end

          found[name]['minimum'] = minimum

          found[name]['count'] ||= 0
          found[name]['count'] += 1

          found[name]['text'] ||= ""
          found[name]['text']  += "#{line}\n"
        end
      end
    end

    #
    #  Now we might have some alerts, but we'll ensure that we've
    # always exceeded our minimum threshold before returning them.
    #
    ret = Hash.new

    found.each do |name,data|

      f = found[name]['count'] || 0
      m = found[name]['minimum']

      if ( f >= m ) then
        ret[name] = found[name]['text']
      else
        STDERR.puts( "Throwing away alert - #{name} - found #{f} times, minimum to raise is #{m}" )
      end
    end
    ret
  end
end

if __FILE__ ==  $PROGRAM_NAME

  to_raise = []

  #
  #  Look for errors.
  #
  errors = SystemMessages.errors

  #
  #  If we found any errors then ensure we'll output them.
  #
  #  The return value will be a hash:
  #
  #     key => Human readable description for the error-type
  #
  #     val => No more than ten lines from dmesg which triggered the match.
  #
  errors.each do |err_type,err_detail|
    h = {}
    h[:id] = err_type
    h[:summary] = err_type + ":" + err_detail
    h[:detail] = "<p>The following lines were found in the output of 'dmesg':<pre> " + err_detail + "</pre>. <p>These match the error condition " + err_type + "</p>"

    to_raise.push(h)

  end

  #
  #  Show the output.
  #
  puts YAML.dump(to_raise)
  exit(0)
end
