Newsletter:3/Writing a Command Data Source

From Zenoss Wiki
Jump to: navigation, search

Writing a Command Data Source

At some point, you will most likely need to extend the abilities of Zenoss for some application or device. This guide will go over writing your own command data sources to extract additional information.


At some point, you will most likely need to extend the built-in and ZenPack abilities of Zenoss for some application or device. Most likely you will want to create a Monitoring Template and apply that to the device or device class where extra monitoring is needed. Three of the most popular methods are through SNMP, SSH, or WMI. This guide will go over writing your own command data sources to extract additional information.

Create a Data Source

With your Monitoring Template selected, click on the + (plus) icon in the top center of your interface. The Add Data Source window will appear. Enter a name for your data source, I suggest it be lowercase and one word. Choose "COMMAND" as the Type and click Submit. Double click on your new data source and make sure "Use SSH" is checked. If you do not choose this, the commands will be run locally on the collector assigned to the device that has the monitoring template bound to it.

The Command Template field is where you may enter commands to run. Unfortunately, you can't just make a script, paste it in the Command Template field, and call it a day. There are occasional issues you will run into based upon how Zenoss interprets and runs the commands. The commands entered into the Command Template are run through [TALES] which requires certain characters to be escaped.

See below for details on different types of Command Templates.


SSH is often used for Linux-based devices, either standard servers or embedded. You will need to be familiar with creating Bash command and scripts.


When creating a script, it is recommended to think about performance, especially if it will be bound to many devices and polled often. Try to avoid using pipes and SubShells.

For example, the following code uses two pipes and two subshells to extract data out of an output:

month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(fgrep "pf::person" /usr/local/pf/logs/packetfence.log | grep "$month $day" | wc -l); echo "OK|count=$count)";

The same output can be achieved with the following code, utilizing no pipes and subshells:

month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(grep -c "$month $day .*INFO.*pf::person" /usr/local/pf/logs/packetfence.log); printf "OK|count=%d" "$count";

In a similar example the following code uses a pipe and subshell to not pass data into a command:

echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type= 1" | /usr/local/bin/radclient localhost:18120 status adminsecret

Here is the same command embedded into some additional code to extract data from the output. Note the use of <<< to pass a string into a command instead of the "echo |" and < <() to pass a set of commands into another:

i=1; while [[ $i -le 2 ]]; do while IFS= read -r; do [[ $REPLY = [[:space:]]* ]] && _temp=${REPLY#*-*-*} name=${_temp%[[:space:]]=*} name=${name//-} values+="$(tr '[A-Z]' '[a-z]' <<< "$name")=${_temp#*=[[:space:]]} "; done < <(radclient localhost:18120 status adminsecret <<< "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = $i"); let ++i; done; printf "OK|$values";


You must also consider portability between different *nix systems. One of those ways is to use generic tools available to you instead of specific applications that may not be on all devices. An example of this is the use of "printf" over "echo". For former is considered more portable, and preferred by bash scripters. See the following examples again and note the change.

From echo:

month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(fgrep "pf::person" /usr/local/pf/logs/packetfence.log | grep "$month $day" | wc -l); echo "OK|count=$count)";

To printf:

month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(grep -c "$month $day .*INFO.*pf::person" /usr/local/pf/logs/packetfence.log); printf "OK|count=%d" "$count";

Additionally, you may have issues with commands executing properly, giving you events in /Cmd/Fail, when they execute fine manually. Take note not just of the TALES issues below, but that commands seem to be running in an environment that does not have all the proper environment (PATH) variables set. It is prudent to execute commands using "bash -c" to get around this issue.

For example, the following:

month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(grep -c "$month $day .*INFO.*pf::person" /usr/local/pf/logs/packetfence.log); printf "OK|count=%d" "$count";

Would become:

bash -c month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(grep -c "$month $day .*INFO.*pf::person" /usr/local/pf/logs/packetfence.log); printf "OK|count=%d" "$count";

Escaping TALES

One of the caveats to look out for when entering bash commands into the Command Template is its use of TALES. TALES will occasionally get in the way when trying to interpret your commands, generating events for failed TALES expressions. One of the most common issues is variables. TALES strips the first $ from each use in your code it sees, so to get around that you can escape it, by doubling the $.

The following:

month=$(date -d "$D" '+%b'); day=$(date -d "$D" '+%d'); count=$(grep -c "$month $day .*INFO.*pf::person" /usr/local/pf/logs/packetfence.log); printf "OK|count=%d" "$count";


month=$$(date -d "$$D" '+%b'); day=$$(date -d "$$D" '+%d'); count=$$(grep -c "$$month $$day .*INFO.*pf::person" /usr/local/pf/logs/packetfence.log); printf "OK|count=%d" "$$count";

Proper Output

When creating output for data points, use the following method as noted in the prior examples: "OK|dp1=12345 dp2=abcde dp3=12ab34cd" and so on. The "OK" tells Zenoss the command executed successfully, and the pipe tells it that data points are coming, defined as "datapointname=". Each data point should then be created under your data source.