{} as record separators?

(Bob Norfolk) #1

I'm working on a ruby script to read Nagios's status.dat and output it's
data.

The data format looks like this:

service {
        host_name=www.bob.com
        service_description=bob-website
        modified_attributes=0
        check_command=check-bob-website
        event_handler=
        has_been_checked=1
        should_be_scheduled=1
        check_execution_time=0.155
        check_latency=0.250
        check_type=0
        current_state=0
        last_hard_state=0
        current_attempt=1
        max_attempts=3
        state_type=1
        last_state_change=1155243779
        last_hard_state_change=1155243779
        last_time_ok=1155507506
        last_time_warning=1154915720
        last_time_unknown=0
        last_time_critical=1155243478
        plugin_output=HTTP OK HTTP/1.1 200 OK - 0.117 second response
time
        performance_data=time=0.116677s;;;0.000000 size=3998B;;;0
        last_check=1155507506
        next_check=1155507806
        current_notification_number=0
        last_notification=0
        next_notification=0
        no_more_notifications=0
        notifications_enabled=1
        active_checks_enabled=1
        passive_checks_enabled=0
        event_handler_enabled=0
        problem_has_been_acknowledged=0
        acknowledgement_type=0
        flap_detection_enabled=0
        failure_prediction_enabled=1
        process_performance_data=1
        obsess_over_service=1
        last_update=1155507620
        is_flapping=0
        percent_state_change=0.00
        scheduled_downtime_depth=0
        }

What I've written works, but it only works for one block. I'm not quite
sure how to make it work for the whole block?

Any advice would be greatly appreciated.

Here's the code I've written:

Class server

def read_nagiosstatus(filename)
  nagios_status = {}
  for line in IO.readlines(filename):
    line.strip! # Remove all extraneous
whitespace
    line.sub!(/#.*$/, "") # Remove comments
    next unless line.length > 0 # check for end of file
    var, value = line.split(/\s*=\s*/, 2)
    nagios_status[var.intern] = value
  end
  return nagios_status
end

nagiosstatus = read_nagiosstatus("status.dat")
puts "Host name is #{nagiosstatus[:host_name]}"
puts "Service description is #{nagiosstatus[:service_description]}"

···

--
Posted via http://www.ruby-forum.com/.

(ctyu) #2

** first try **

data = {}
File.foreach( "nagios.dat" ) do |e|
  next if e =~ /service \{|\}/
  k,v = e.strip.split("=")
  data[k] = v
end

it didnt work because of line below has more than one "="

        performance_data=time=0.116677s;;;0.000000 size=3998B;;;0

** second try **

data = {}
File.foreach( "nagios.dat") do |e|
  next if e =~ /service \{|\}/
  if e.strip =~ /(.*?)=(.*)/
         data[$1]=$2
  end
end

(Scott) #3

Wow, this can't be "The Ruby Way", but it works:

def nagios_data(data)
  blocks = data.strip.split(/.*\}\n(?=\w+\s+\{)/)
  blocks.map do |nagios_data|
    data_points = nagios_data.split("\n")
    # you can possibly use this as a hash key if they will be unique
    # block_name = data_points[0][/\w+/]
    data_points[1..-1].inject({}) do |values, data_point|
      unless data_point =~ /\s+\}/
        key, value = data_point.strip.sub(/#.*$/, "").split(/\s*=\s*/, 2)
        values[key.intern] = value
      end
      values
    end
  end
end

require 'pp'

pp nagios_data(File.read("status.dat"))

(Bob Norfolk) #4

Here's the code I've written:

Class server

def read_nagiosstatus(filename)
  nagios_status = {}
  for line in IO.readlines(filename):
    line.strip! # Remove all extraneous
whitespace
    line.sub!(/#.*$/, "") # Remove comments
    next unless line.length > 0 # check for end of file
    var, value = line.split(/\s*=\s*/, 2)
    nagios_status[var.intern] = value
  end
  return nagios_status
end

nagiosstatus = read_nagiosstatus("status.dat")
puts "Host name is #{nagiosstatus[:host_name]}"
puts "Service description is #{nagiosstatus[:service_description]}"

So this code actually works. It reads through my key=value pairs just
fine. But of course, there's no code here to separate between multiple
instances of {}.

I understand now how to split on {} thanks to the examples you've all
posted.

What I don't understand though, is how would I address or list these?
I've got two problems to solve.

The first is that I'd be happy if I could just get an object that was
unique based on the host_name and I could call
nagios_status[(:host_name,:plugin_output)] for each host in the
status.dat file.

The second is that each host_name has multiple services. My unique key
needs to be based on the host_name variable. And I think I need to end
up so I have an object that's like host_name.service_name.variables.

def read_nagiosstatus(filename)
  nagios_status = {}
  for line in IO.readlines(filename):
    line.strip! # Remove all extraneous
whitespace
    line.sub!(/#.*$/, "") # Remove comments
    next unless line.length > 0 # check for end of file
    var, value = line.split(/\s*=\s*/, 2)
    nagios_status[var.intern] = value
  end
  return nagios_status
end

nagiosstatus = read_nagiosstatus("status.dat")
puts #{nagios_status[:host_name]}

···

--
Posted via http://www.ruby-forum.com/.

(John Johnson) #5

Bob,

See if this works for you.

Regards,
   JJ

···

On Sat, 19 Aug 2006 14:17:11 -0400, Bob Norfolk <punkrockgeekboy@yahoo.com> wrote:

I'm working on a ruby script to read Nagios's status.dat and output it's
data.

#
# Copyright 2006, by John Johnson, All Rights Reserved
# Released under the NewBSD license.
#

require 'ostruct'
require 'pp'

class Services < Hash

   SERVER_RE = /^\s*service\s*\{(.+?)\n\s*\}\s*$/m
   VALUE_RE = /^\s*([^=]+)\s*=\s*([^\n=]*?)$/
   INTEGER_RE = /^([-+]?\d+)$/
   FLOAT_RE = /^([-+]?\d*\.\d*[eE]?\d*)$/
   BOOLEAN_TRUE_RE = /^[1tTyY]+/ # e.g. 1, true, True, yes, Yes

   # Types of declarations possible for field names.
   :FIELD_NAMES
   :FIELD_REGULAR_EXPRESSIONS

   #
   # Conversion for field values.
   #
   NAME_TYPES = 0
   NAMES = 1
   CONVERSION_BLOCK = 2
   CONVERSIONS = [

     # The following fields have time values that are the number
     # of seconds past the epoch.
     [
       # Types of names listed.
       [ :FIELD_NAMES ],
       # Field name declarations.
       ['last_state_change', 'last_hard_state_change',
       'last_time_ok', 'last_time_warning', 'last_time_unknown',
       'last_time_critical', 'last_check', 'next_check',
       'last_notification', 'next_notification', 'last_update'],
       # Code to convert from a String to whatever this is.
       lambda { |seconds_string|
         return Time.at(seconds_string.to_i)
       }
     ],

     # The following appear to be boolean fields.
     [
       [ :FIELD_NAMES, :FIELD_REGULAR_EXPRESSIONS ],
       ['modified_attributes', 'has_been_checked', 'should_be_scheduled',
         'no_more_notifications',
         /\w+_enabled$/, # all field names ending in _enabled
         'problem_has_been_acknowledged', 'process_performance_data',
         'obsess_over_service', 'is_flapping'],
         lambda { |boolean_string|
           if boolean_string =~ BOOLEAN_TRUE_RE
             true
           else
             false
           end
         }
     ]
   ]

   HOST_NAME_PROPERTY = 'host_name'

   def convert_field(name, value)
     CONVERSIONS.each { |conv_set|
       name_types = conv_set[NAME_TYPES]
       names = conv_set[NAMES]
       conversion_block = conv_set[CONVERSION_BLOCK]

       if name_types.include?(:FIELD_REGULAR_EXPRESSIONS)
         names.each { |name_dec|
           case
           when name_dec.class==String
             if name == name_dec
               return conversion_block.call(value)
             end
           when name_dec.class==Regexp
             if name =~ name_dec
               return conversion_block.call(value)
             end
           else
             fail "Don't know what a #{name_dec.class} field name is"
           end
         }
       end
       if name_types.include?(:FIELD_NAMES)
         if names.include?(name)
           return conversion_block.call(value)
         end
       end
     }

     # Try implicit conversions.
     case value
     when INTEGER_RE
       return Integer(value)
     when FLOAT_RE
       return Float(value)
     end

     return String(value)
   end

   def initialize(filename)
     super
     contents = File.open(filename).read
     contents.scan(SERVER_RE) { |value_ary|
       values = value_ary[0]
       host_name = ""
       service = OpenStruct.new
       values.scan(VALUE_RE) { |name, value|
         converted = convert_field(name, value)
         dumped = Marshal.dump(converted)
         service.instance_eval(
           "self.#{name} = Marshal.load('#{dumped}')")
         host_name=value if name == HOST_NAME_PROPERTY
       }
       self[host_name] = service
     }
   end
end

services = Services.new('nagio.txt')
services.each_pair{ |service, properties|
   puts "Host: #{service}"
   puts "\t has_been_checked: #{properties.has_been_checked}"
   puts "\t next_check: #{properties.next_check}"
   puts "\tnotifications_enabled: #{properties.notifications_enabled}"
}

if services[‘www.bob.com’].obsess_over_service
   puts "Someone is obsessing over Bob!"
else
   puts "No one is obsessing over Bob."
end

if services[‘www.test.com’].obsess_over_service
   puts "Someone is obsessing over Test!"
else
   puts "No one is obsessing over Test."
end

--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/

(Bob Norfolk) #6

Scott wrote:

Wow, this can't be "The Ruby Way", but it works:

def nagios_data(data)
  blocks = data.strip.split(/.*\}\n(?=\w+\s+\{)/)
  blocks.map do |nagios_data|
    data_points = nagios_data.split("\n")
    # you can possibly use this as a hash key if they will be unique
    # block_name = data_points[0][/\w+/]
    data_points[1..-1].inject({}) do |values, data_point|
      unless data_point =~ /\s+\}/
        key, value = data_point.strip.sub(/#.*$/, "").split(/\s*=\s*/, 2)
        values[key.intern] = value
      end
      values
    end
  end
end

require 'pp'

pp nagios_data(File.read("status.dat"))

When I try running this code, I get:

nagios_test2.rb:10:in `nagios_data': undefined method `intern' for
nil:NilClass (NoMethodError)
        from nagios_test2.rb:7:in `inject'
        from nagios_test2.rb:7:in `each'
        from nagios_test2.rb:7:in `inject'
        from nagios_test2.rb:7:in `nagios_data'
        from nagios_test2.rb:3:in `map'
        from nagios_test2.rb:3:in `nagios_data'
        from nagios_test2.rb:19

Where does the intern method come from?

···

--
Posted via http://www.ruby-forum.com/.

(John Johnson) #7

So, did it work?

···

On Sat, 19 Aug 2006 20:59:15 -0400, John Johnson <johnatl@mac.com> wrote:

On Sat, 19 Aug 2006 14:17:11 -0400, Bob Norfolk > <punkrockgeekboy@yahoo.com> wrote:

I'm working on a ruby script to read Nagios's status.dat and output it's
data.

Bob,

See if this works for you.

Regards,
   JJ

--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/

(Bob Norfolk) #8

John Johnson wrote:

   JJ

So, did it work?

Actually, I was just working on debugging this error that the script
above gives :

nagiostest3.rb:111:in `initialize': (eval):1:in `initialize': compile
error (SyntaxError)
(eval):1: unterminated string meets end of file
(eval):1: parse error, unexpected $, expecting ')'
íelf.last_time_critical = Marshal.load(u: Time
                                                               ^
from nagiostest3.rb:108:in `initialize'
        from nagiostest3.rb:104:in `initialize'
        from nagiostest3.rb:119

···

On Sat, 19 Aug 2006 20:59:15 -0400, John Johnson <johnatl@mac.com> > wrote:

--
Posted via http://www.ruby-forum.com/.

(John Johnson) #9

Hm, probably a ' in the dumped data.
Try this instead:

   def initialize(filename)
     super
     contents = File.open(filename).read
     contents.scan(SERVER_RE) { |value_ary|
       values = value_ary[0]
       host_name = ""
       service = OpenStruct.new
       values.scan(VALUE_RE) { |name, value|
         converted = convert_field(name, value)
         dumped = Marshal.dump(converted)
         dumped = dumped.unpack("H*")
         service.instance_eval(
                               "self.#{name} = Marshal.load(['#{dumped}'].pack('H*'))")
         host_name=value if name == HOST_NAME_PROPERTY
       }
       self[host_name] = service
     }
   end
end

It converts the dumped data to a hex string, then back, eliminating the need for escaping characters, etc.

Regards,
   JJ

···

On Tue, 22 Aug 2006 16:00:37 -0400, Bob Norfolk <punkrockgeekboy@yahoo.com> wrote:

John Johnson wrote:

On Sat, 19 Aug 2006 20:59:15 -0400, John Johnson <johnatl@mac.com> >> wrote:

   JJ

So, did it work?

Actually, I was just working on debugging this error that the script
above gives :

nagiostest3.rb:111:in `initialize': (eval):1:in `initialize': compile
error (SyntaxError)
(eval):1: unterminated string meets end of file
(eval):1: parse error, unexpected $, expecting ')'
íelf.last_time_critical = Marshal.load(u: Time
                                                               ^
from nagiostest3.rb:108:in `initialize'
        from nagiostest3.rb:104:in `initialize'
        from nagiostest3.rb:119

--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/