Reading nagios status information in Tcl
Written by Wojciech Kocjan   
Since my new book on Nagios is starting to take up more and more of my time I decided to put some effort on writing about Nagios and Tcl/Tk. All articles will talk about Nagios version 3. We'll start with trying to read output from Nagios files - starting with status.

For those of you not yet familiar with Nagios, it is an opensource software for system monitoring - which means it makes sure all of your software is running as it should be. Nagios plays very nicely with extending and combining it with other applications is very easy.

Current article and probably 1-2 more will deal with reading Nagios status. All results are stored across several files, but most of them are in the same format - a file contains one or more sections and each section contains one or more attributes. The syntax seems quite easy to read, below is an example from status.dat file:

Parts of status.dat file
info {
        created=1203230967
        version=3.0rc2
        }

programstatus {
        modified_host_attributes=0
        modified_service_attributes=0
        (...)
        }

Sections with same name can be repeated - for example for hoststatus / servicestatus containing status of each host and service. Each attribute needs to fit in a single line - for example attributes with multiple lines use \n to indicate new lines.

This is what we'll start with today - we want to have a simple parser that reads files in such format and returns a list of all sections and all attributes they have. We want 2 procedures - one that accepts a filename and another one that actually accepts a Tcl channel to read. We'll also store every command in nagios namespace.

Let's start with a small wrapper that opens a file, passes the channel to actual reading procedure, closes the channel and returns the result. We'll also define the namespace itself.

Wrapper for reading file
namespace eval nagios {}
 
proc nagios::readFile {filename} {
    set fh [open $filename r]
    set rc [readFileChannel $fh]
    close $fh
 
    return $rc
}
 

That was the easier part. For actual parsing we'll read the file line by line, skip the ones we don't understand and focus on two types of lines - ones in [sectionname] { format and ones in [name]=[value] style. Seems easy? Here it is:
Reading Nagios information from a channel
proc nagios::readFileChannel {fh} {
    set rc [list]
    fconfigure $fh -encoding iso8859-1 -translation auto
    seek $fh 0
    set isobject 0
 
    while {![eof $fh]} {
        gets $fh line
 
        if {[regexp "^\\s*#" $line]} {
            # comment - skip it
        }  elseif  {[regexp "^\\s*(.*?)\\s*\{\\s*\$" $line - objecttype]} {
            # beginning of an object
            set isobject 1
            lappend rc $objecttype [list]
        }  elseif  {$isobject && [regexp "^\\s*(.*?)=(.*)\$" $line - name value]} {
            # name=value line
            set value [string map [list \\n \n \\t \t \\\\ \\] $value]
            set vl [lindex $rc end]
            lappend vl $name $value
            lset rc end $vl
        }
    }
    return $rc
}
 

First we initialize the result list. Next comes channel configuration to make sure we're reading it correctly. Then each line tries to get mached against one of known line types. In the beginning all comments are skipped.

Next we try to match lines defining a new section - in such cases we add section name and an empty list to the final result list. That empty list will later on be used to add all attributes to it. We also store that we currently have an object in progress of being defined.

In case the line has an equal (=) sign, we strip all spaces from beginning and append these values to the last element in the final result.

Finally after we get an EOF from the config file the list is returned.

In next article we'll cover how to use these functions to create a more useful data structure using dict command from Tcl 8.5 (and backported to Tcl 8.4).

We'll also try to cover how to compare two sets of results to see if it's easy to get to know what has changed.

Based on that we can build a simple GUI that will monitor Nagios status file and report any changes to it.