A performance data collector gathers the current statistics of items such as the amount of space used in a file system. The data can be collected using either a script or an SNMP command. For our Filesystem
data, we must create a new data collector called Filesystem
(this is a special name) that will return a property called usedBlocks (another special name).
If your operating system's MIB provides a usedBlocks (or something named like that) value, then we can make use of existing Zenoss infrastructure and just collect that data using SNMP. Otherwise, you need to create a script to take the total size of the filesystem (totalBlocks) and subtract the freeBlocks value. Unfortunately, AIX only provides freeBlocks, so we need to create a command.
With the Zenoss 2.4 release, we can also create a command parser to gather our performance information. This new functionality allows you to write simple code to gather performance data and graph the results.
Multiple collectors for different components of a system can be created, or one huge collectors for everything can be created. Smaller collectors are preferred for maintenance reasons. The following collector is for calculating file system free space, and would live in the libexec/
directory of your ZenPack.
#!/usr/bin/env python """Gather used disk space statistics for AIX""" import sys import re from subprocess import * base_fs_table_oid= "1.3.6.1.4.1.2.6.191.6.2.1" def process_disk_stats( device, community, totalBlocks_oid, freeBlocks_oid ): """Gather OID info and sanitize it""" cmd= "snmpwalk -v1 -c %s -On %s %s %s" % ( community, device, \ totalBlocks_oid, freeBlocks_oid ) proc= Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE ) # # Check to make sure that we don't have any hangups in # executing our smidump # if not proc.stdout: print "Couldn't open pipe to stdout for %s" % cmd return if not proc.stderr: print "Couldn't open pipe to stderr for %s" % cmd return ( line1, line2 )= proc.stdout.readlines() totalBlocks= line1.split()[-1] freeBlocks= line2.split()[-1] usedBlocks= totalBlocks - freeBlocks return totalBlocks, freeBlocks, usedBlocks if __name__ == "__main__": if len(sys.argv) < 4: print "Need device, community and fs_index arguments!" sys.exit(1) (device, community, fs_index )= sys.argv[1:] totalBlocks_oid= ".".join( base_fs_table_oid, 5, fs_index ) freeBlocks_oid= ".".join( base_fs_table_oid, 6, fs_index ) totalBlocks, freeBlocks, usedBlocks= process_disk_stats( device, \ community, totalBlocks_oid, freeBlocks_oid ) print "totalBlocks:%s freeBlocks:%s usedBlocks:%s" % ( totalBlocks, \ freeBlocks, usedBlocks ) sys.exit(0)
Zencommand may be used to execute commands on remote hosts using the SSH protocol. This provides secure and flexible performance monitoring for Unix-style systems such as AIX, Solaris, OS X (Darwin) and Linux servers.
When the remote host has commands that show data in a format already understood by Zencommand (such as Nagios or Cacti plugins), Zencommand can process the results and update the ZODB. However, if you are monitoring servers that have not had these commands installed, you need to extend Zencommand with new parsers to understand the results.
The basic data flow for Zencommand is this:
A collector starts Zencommand with a collector name, like localhost
or collector2
.
Zencommand contacts zenhub and loads the commands to be run against the devices for that configuration. The command configuration includes details such as "use SSH" to run the command on the remote box and credentials to allow access to the remote host. The command configuration also includes a specification for the parser to use on the data that is returned by the command.
Zencommand runs the command on the remote host, and when the command finishes, a parser is created and the results are passed to the processResults()
method of the parser. The processResults()
method is passed the command configuration fetched from ZenHub, and an object into which parsed results will be placed. The parser is also used to copy any data needed by Zencommand during the parsing.
Zencommand takes the returned Python dictionary from the parser and updates the ZODB.
Consider the Unix df command. It can be used to determine free disk space on a device's file systems. Here's a typical output format from Linux:
Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda6 57669700 34162636 20577616 63% / /dev/sda7 71133144 28824804 38694924 43% /home /dev/mmcblk1 3924476 536 3923940 1% /media/disk
The Zenoss data modeler (zenmodeler) will have created components under this device for the file systems. The mapping of data to the component must use the mount point information. ZenHub must copy mount point information from the model data stored in the ZODB into the configuration for this command. To know what data may be needed for parsing, ZenHub creates the parser that will be used by Zencommand, and calls the dataForParser()
method. Remember, this happens in ZenHub, and not Zencommand, and it happens before any command is run.
The result of dataForParser()
is a Python dictionary that is stored as data
in the command configuration passed to Zencommand. When the parser is invoked in Zencommand, it will have access to this information.
After the parser digests the results of running the command, it can produce performance information and events.
The result object is a simple Python class that contains two lists, one called values
and the other called events
.
The events
item contains a dictionary of string to value mappings which are turned into events. Zencommand will update the event with the device name, but the rest of the fields (such as component, severity, etc) are up to the parser to fill in.
The values
item is a list of two-element tuples. The first element is the data point, and the second is a value, which is a Python number or None
. None
is always ignored.
Every command run by Zencommand comes with a list of data points that correspond to that command. In our df example, the datapoints may include percentUsed
and blocksFree
, along with any thresholds or parser-specific data, such as mount point.
Thresholds will be tested by Zencommand, and threshold events automatically generated.
The command's exit code is available at parse time, too.
Parsers will be available to Zenoss when they are placed in the $ZENHOME/Products/ZenRRD/parsers
directory or in a ZenPack's $ZENHOME/ZenPacks.
directory. Each parser should be a sub-class of the pkg
.zpid
-version_id
-py2.4.egg/Zenpacks/pkg
/zpid
/parsersProducts.ZenRRD.CommandParser.CommandParser
class.
A command like df is a very common case. Unix commands will often emit easily-parsed, line-oriented records. There are some useful subclasses of CommandParser
that perform much of the parsing if you provide these parsers with the right details, such as regular expressions. They are:
Table 12.2. CommandParser
Helper Parsers
Name | Description |
---|---|
componentScanner | A regular expression that finds details about a component that can be used to map back to the component known to the Zenoss model. It must return a match named |
scanners | An iterable list of regular expressions that will pull out numerical values from the output of the command. |
componentScanValue | The data to be copied to the data point needed to match the component to the output results. |
Let's examine what these values might be for our df command, and its example output.
For the componentScanner
, we want to find the mount-point data and extract it, so that we can match the Unix file separator ('/') to the component file system that has the id "_". We can use something like:
% (?P<component>/.*)$
For the scanners
, we'll use a tuple of regular expressions to pull out the numerical values we want:
( r' (?P<availableBlocks>\d+) +(?P<percentUsed>\d+)%' )
For the componentScanValue
, we'll specify mount
so that the mount point information is copied to the command configuration by ZenHub and matched against the component
value parsed by the componentScanner
regular expression.