{} as record separators?

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]}”

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”))

** 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

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]}

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 methodintern’ for
nil:NilClass (NoMethodError)
from nagios_test2.rb:7:in inject' from nagios_test2.rb:7:ineach’
from nagios_test2.rb:7:in inject' from nagios_test2.rb:7:innagios_data’
from nagios_test2.rb:3:in map' from nagios_test2.rb:3:innagios_data’
from nagios_test2.rb:19

Where does the intern method come from?

On Sat, 19 Aug 2006 14:17:11 -0400, Bob Norfolk
[email protected] 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

Copyright 2006, by John J., All Rights Reserved

Released under the NewBSD license.

require ‘ostruct’
require ‘pp’

class Services < Hash

SERVER_RE = /^\sservice\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

John J. wrote:

On Sat, 19 Aug 2006 20:59:15 -0400, John J. [email protected]
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 J. [email protected]
wrote:

JJ

So, did it work?

On Tue, 22 Aug 2006 16:00:37 -0400, Bob Norfolk
[email protected] wrote:

above gives :
from nagiostest3.rb:119

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