24.6.5. Extending Net-SNMP The Net-SNMP Agent can be extended to provide application metrics in addition to raw system metrics. This allows for capacity planning as well as performance issue troubleshooting. For example, it may be helpful to know that an email system had a 5-minute load average of 15 while being tested, but it is more helpful to know that the email system has a load average of 15 while processing 80,000 messages a second. When application metrics are available via the same interface as the system metrics, this also allows for the visualization of the impact of different load scenarios on system performance (for example, each additional 10,000 messages increases the load average linearly until 100,000). A number of the applications that ship with Red Hat Enterprise Linux extend the Net-SNMP Agent to provide application metrics over SNMP. There are several ways to extend the agent for custom applications as well. This section describes extending the agent with shell scripts and Perl plug-ins. It assumes that the net-snmp-utils and net-snmp-perl packages are installed, and that the user is granted access to the SNMP tree as described in Section 24.6.3.2, "Configuring Authentication" . 24.6.5.1. Extending Net-SNMP with Shell Scripts The Net-SNMP Agent provides an extension MIB ( NET-SNMP-EXTEND-MIB ) that can be used to query arbitrary shell scripts. To specify the shell script to run, use the extend directive in the /etc/snmp/snmpd.conf file. Once defined, the Agent will provide the exit code and any output of the command over SNMP. The example below demonstrates this mechanism with a script which determines the number of httpd processes in the process table. Note The Net-SNMP Agent also provides a built-in mechanism for checking the process table via the proc directive. See the snmpd.conf (5) manual page for more information. The exit code of the following shell script is the number of httpd processes running on the system at a given point in time: #!/bin/sh NUMPIDS=`pgrep httpd | wc -l` exit USDNUMPIDS To make this script available over SNMP, copy the script to a location on the system path, set the executable bit, and add an extend directive to the /etc/snmp/snmpd.conf file. The format of the extend directive is the following: extend name prog args where name is an identifying string for the extension, prog is the program to run, and args are the arguments to give the program. For instance, if the above shell script is copied to /usr/local/bin/check_apache.sh , the following directive will add the script to the SNMP tree: The script can then be queried at NET-SNMP-EXTEND-MIB::nsExtendObjects : Note that the exit code ( " 8 " in this example) is provided as an INTEGER type and any output is provided as a STRING type. To expose multiple metrics as integers, supply different arguments to the script using the extend directive. For example, the following shell script can be used to determine the number of processes matching an arbitrary string, and will also output a text string giving the number of processes: #!/bin/sh PATTERN=USD1 NUMPIDS=`pgrep USDPATTERN | wc -l` echo "There are USDNUMPIDS USDPATTERN processes." exit USDNUMPIDS The following /etc/snmp/snmpd.conf directives will give both the number of httpd PIDs as well as the number of snmpd PIDs when the above script is copied to /usr/local/bin/check_proc.sh : The following example shows the output of an snmpwalk of the nsExtendObjects OID: Warning Integer exit codes are limited to a range of 0-255. For values that are likely to exceed 256, either use the standard output of the script (which will be typed as a string) or a different method of extending the agent. This last example shows a query for the free memory of the system and the number of httpd processes. This query could be used during a performance test to determine the impact of the number of processes on memory pressure: 24.6.5.2. Extending Net-SNMP with Perl Executing shell scripts using the extend directive is a fairly limited method for exposing custom application metrics over SNMP. The Net-SNMP Agent also provides an embedded Perl interface for exposing custom objects. The net-snmp-perl package provides the NetSNMP::agent Perl module that is used to write embedded Perl plug-ins on Red Hat Enterprise Linux. The NetSNMP::agent Perl module provides an agent object which is used to handle requests for a part of the agent's OID tree. The agent object's constructor has options for running the agent as a sub-agent of snmpd or a standalone agent. No arguments are necessary to create an embedded agent: use NetSNMP::agent (':all'); my USDagent = new NetSNMP::agent(); The agent object has a register method which is used to register a callback function with a particular OID. The register function takes a name, OID, and pointer to the callback function. The following example will register a callback function named hello_handler with the SNMP Agent which will handle requests under the OID .1.3.6.1.4.1.8072.9999.9999 : USDagent->register("hello_world", ".1.3.6.1.4.1.8072.9999.9999", \&hello_handler); Note The OID .1.3.6.1.4.1.8072.9999.9999 ( NET-SNMP-MIB::netSnmpPlaypen ) is typically used for demonstration purposes only. If your organization does not already have a root OID, you can obtain one by contacting an ISO Name Registration Authority (ANSI in the United States). The handler function will be called with four parameters, HANDLER , REGISTRATION_INFO , REQUEST_INFO , and REQUESTS . The REQUESTS parameter contains a list of requests in the current call and should be iterated over and populated with data. The request objects in the list have get and set methods which allow for manipulating the OID and value of the request. For example, the following call will set the value of a request object to the string " hello world " : USDrequest->setValue(ASN_OCTET_STR, "hello world"); The handler function should respond to two types of SNMP requests: the GET request and the GETNEXT request. The type of request is determined by calling the getMode method on the request_info object passed as the third parameter to the handler function. If the request is a GET request, the caller will expect the handler to set the value of the request object, depending on the OID of the request. If the request is a GETNEXT request, the caller will also expect the handler to set the OID of the request to the available OID in the tree. This is illustrated in the following code example: my USDrequest; my USDstring_value = "hello world"; my USDinteger_value = "8675309"; for(USDrequest = USDrequests; USDrequest; USDrequest = USDrequest->()) { my USDoid = USDrequest->getOID(); if (USDrequest_info->getMode() == MODE_GET) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } elsif (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.1")) { USDrequest->setValue(ASN_INTEGER, USDinteger_value); } } elsif (USDrequest_info->getMode() == MODE_GETNEXT) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.1"); USDrequest->setValue(ASN_INTEGER, USDinteger_value); } elsif (USDoid < new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.0"); USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } } } When getMode returns MODE_GET , the handler analyzes the value of the getOID call on the request object. The value of the request is set to either string_value if the OID ends in " .1.0 " , or set to integer_value if the OID ends in " .1.1 " . If the getMode returns MODE_GETNEXT , the handler determines whether the OID of the request is " .1.0 " , and then sets the OID and value for " .1.1 " . If the request is higher on the tree than " .1.0 " , the OID and value for " .1.0 " is set. This in effect returns the " " value in the tree so that a program like snmpwalk can traverse the tree without prior knowledge of the structure. The type of the variable is set using constants from NetSNMP::ASN . See the perldoc for NetSNMP::ASN for a full list of available constants. The entire code listing for this example Perl plug-in is as follows: #!/usr/bin/perl use NetSNMP::agent (':all'); use NetSNMP::ASN qw(ASN_OCTET_STR ASN_INTEGER); sub hello_handler { my (USDhandler, USDregistration_info, USDrequest_info, USDrequests) = @_; my USDrequest; my USDstring_value = "hello world"; my USDinteger_value = "8675309"; for(USDrequest = USDrequests; USDrequest; USDrequest = USDrequest->()) { my USDoid = USDrequest->getOID(); if (USDrequest_info->getMode() == MODE_GET) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } elsif (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.1")) { USDrequest->setValue(ASN_INTEGER, USDinteger_value); } } elsif (USDrequest_info->getMode() == MODE_GETNEXT) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.1"); USDrequest->setValue(ASN_INTEGER, USDinteger_value); } elsif (USDoid < new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.0"); USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } } } } my USDagent = new NetSNMP::agent(); USDagent->register("hello_world", ".1.3.6.1.4.1.8072.9999.9999", \&hello_handler); To test the plug-in, copy the above program to /usr/share/snmp/hello_world.pl and add the following line to the /etc/snmp/snmpd.conf configuration file: The SNMP Agent Daemon will need to be restarted to load the new Perl plug-in. Once it has been restarted, an snmpwalk should return the new data: The snmpget should also be used to exercise the other mode of the handler:

Commands:
#!/bin/sh NUMPIDS=`pgrep httpd | wc -l` exit USDNUMPIDS
extend httpd_pids /bin/sh /usr/local/bin/check_apache.sh
~]USD snmpwalk localhost NET-SNMP-EXTEND-MIB::nsExtendObjects NET-SNMP-EXTEND-MIB::nsExtendNumEntries.0 = INTEGER: 1 NET-SNMP-EXTEND-MIB::nsExtendCommand."httpd_pids" = STRING: /bin/sh NET-SNMP-EXTEND-MIB::nsExtendArgs."httpd_pids" = STRING: /usr/local/bin/check_apache.sh NET-SNMP-EXTEND-MIB::nsExtendInput."httpd_pids" = STRING: NET-SNMP-EXTEND-MIB::nsExtendCacheTime."httpd_pids" = INTEGER: 5 NET-SNMP-EXTEND-MIB::nsExtendExecType."httpd_pids" = INTEGER: exec(1) NET-SNMP-EXTEND-MIB::nsExtendRunType."httpd_pids" = INTEGER: run-on-read(1) NET-SNMP-EXTEND-MIB::nsExtendStorage."httpd_pids" = INTEGER: permanent(4) NET-SNMP-EXTEND-MIB::nsExtendStatus."httpd_pids" = INTEGER: active(1) NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."httpd_pids" = STRING: NET-SNMP-EXTEND-MIB::nsExtendOutputFull."httpd_pids" = STRING: NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."httpd_pids" = INTEGER: 1 NET-SNMP-EXTEND-MIB::nsExtendResult."httpd_pids" = INTEGER: 8 NET-SNMP-EXTEND-MIB::nsExtendOutLine."httpd_pids".1 = STRING:
#!/bin/sh PATTERN=USD1 NUMPIDS=`pgrep USDPATTERN | wc -l` echo "There are USDNUMPIDS USDPATTERN processes." exit USDNUMPIDS
extend httpd_pids /bin/sh /usr/local/bin/check_proc.sh httpd extend snmpd_pids /bin/sh /usr/local/bin/check_proc.sh snmpd
~]USD snmpwalk localhost NET-SNMP-EXTEND-MIB::nsExtendObjects NET-SNMP-EXTEND-MIB::nsExtendNumEntries.0 = INTEGER: 2 NET-SNMP-EXTEND-MIB::nsExtendCommand."httpd_pids" = STRING: /bin/sh NET-SNMP-EXTEND-MIB::nsExtendCommand."snmpd_pids" = STRING: /bin/sh NET-SNMP-EXTEND-MIB::nsExtendArgs."httpd_pids" = STRING: /usr/local/bin/check_proc.sh httpd NET-SNMP-EXTEND-MIB::nsExtendArgs."snmpd_pids" = STRING: /usr/local/bin/check_proc.sh snmpd NET-SNMP-EXTEND-MIB::nsExtendInput."httpd_pids" = STRING: NET-SNMP-EXTEND-MIB::nsExtendInput."snmpd_pids" = STRING: NET-SNMP-EXTEND-MIB::nsExtendResult."httpd_pids" = INTEGER: 8 NET-SNMP-EXTEND-MIB::nsExtendResult."snmpd_pids" = INTEGER: 1 NET-SNMP-EXTEND-MIB::nsExtendOutLine."httpd_pids".1 = STRING: There are 8 httpd processes. NET-SNMP-EXTEND-MIB::nsExtendOutLine."snmpd_pids".1 = STRING: There are 1 snmpd processes.
~]USD snmpget localhost 'NET-SNMP-EXTEND-MIB::nsExtendResult."httpd_pids"' UCD-SNMP-MIB::memAvailReal.0 NET-SNMP-EXTEND-MIB::nsExtendResult."httpd_pids" = INTEGER: 8 UCD-SNMP-MIB::memAvailReal.0 = INTEGER: 799664 kB
use NetSNMP::agent (':all'); my USDagent = new NetSNMP::agent();
USDagent->register("hello_world", ".1.3.6.1.4.1.8072.9999.9999", \&hello_handler);
USDrequest->setValue(ASN_OCTET_STR, "hello world");
my USDrequest; my USDstring_value = "hello world"; my USDinteger_value = "8675309"; for(USDrequest = USDrequests; USDrequest; USDrequest = USDrequest->next()) { my USDoid = USDrequest->getOID(); if (USDrequest_info->getMode() == MODE_GET) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } elsif (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.1")) { USDrequest->setValue(ASN_INTEGER, USDinteger_value); } } elsif (USDrequest_info->getMode() == MODE_GETNEXT) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.1"); USDrequest->setValue(ASN_INTEGER, USDinteger_value); } elsif (USDoid < new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.0"); USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } } }
#!/usr/bin/perl use NetSNMP::agent (':all'); use NetSNMP::ASN qw(ASN_OCTET_STR ASN_INTEGER); sub hello_handler { my (USDhandler, USDregistration_info, USDrequest_info, USDrequests) = @_; my USDrequest; my USDstring_value = "hello world"; my USDinteger_value = "8675309"; for(USDrequest = USDrequests; USDrequest; USDrequest = USDrequest->next()) { my USDoid = USDrequest->getOID(); if (USDrequest_info->getMode() == MODE_GET) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } elsif (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.1")) { USDrequest->setValue(ASN_INTEGER, USDinteger_value); } } elsif (USDrequest_info->getMode() == MODE_GETNEXT) { if (USDoid == new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.1"); USDrequest->setValue(ASN_INTEGER, USDinteger_value); } elsif (USDoid < new NetSNMP::OID(".1.3.6.1.4.1.8072.9999.9999.1.0")) { USDrequest->setOID(".1.3.6.1.4.1.8072.9999.9999.1.0"); USDrequest->setValue(ASN_OCTET_STR, USDstring_value); } } } } my USDagent = new NetSNMP::agent(); USDagent->register("hello_world", ".1.3.6.1.4.1.8072.9999.9999", \&hello_handler);
perl do "/usr/share/snmp/hello_world.pl"
~]USD snmpwalk localhost NET-SNMP-MIB::netSnmpPlaypen NET-SNMP-MIB::netSnmpPlaypen.1.0 = STRING: "hello world" NET-SNMP-MIB::netSnmpPlaypen.1.1 = INTEGER: 8675309
~]USD snmpget localhost NET-SNMP-MIB::netSnmpPlaypen.1.0 NET-SNMP-MIB::netSnmpPlaypen.1.1 NET-SNMP-MIB::netSnmpPlaypen.1.0 = STRING: "hello world" NET-SNMP-MIB::netSnmpPlaypen.1.1 = INTEGER: 8675309