Tutorial for Application Monitoring SNMP Agent
This document explains the architecture of SNMP agents that implement ND-APPLICATION-MIB mib generated using NuDesign Visual xAgentBuilder 9 for C++. If you are not familiar with xAgentBuilder please take a look at the xAgentBuilder9 help topics titled “Create Standalone SNMP Agent” and “Create SNMP Extension Dll”. Please note that Application Monitoring Sample Project needs to be downloaded in addition to the xAgentBuilder9 Eval.
This series of agents demonstrate three different ways of architecting an SNMP agent. All projects are built using the evaluation version of NuDesign Visual SNMP xAgentBuilder9. You may need to update your Include and Lib directories in your project settings to reflect the installation directory of the NuDesign Visual SNMP xAgentBuilder 9 on your system.
Each agent can be built as a standalone agent or an extension DLL. The project for extension DLL has the name as a corresponding standalone agent with ‘Dll’ added to it. For example “AppMon.dsp” is the project that creates the standalone agent, and “AppMonDll.dsp” creates the same agent as an extension DLL to be used with SNMP agent service. Later versions of Microsoft’s Visual Studio used the file extensions “.VCPROJ” or “.VCXPROJ” instead of “.DSP”. Note that the only difference between a standalone agent and an extension DLL version is in one file: the standalone agent includes “main.cpp” in the project, while the extension DLL needs “DllMain.cpp”. For convenience prebuild agent / dlls are also included in the Sample download. Instrumentation code is identical in each case (except for handling of notifications!).
Application Monitoring MIB
The application monitoring MIB, ND-APPLICATION-MIB, is a very simple MIB. It consists of two scalar objects:
appName OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-write
STATUS current
DESCRIPTION “Name of the application to monitor.”
::= { appObjects 1 }
appState OBJECT-TYPE
SYNTAX INTEGER {
unknown (0),
started (1),
stopped (2)
}
MAX-ACCESS read-only
STATUS current
DESCRIPTION “Current state of the application.”
::= {appObjects 2 }
The second object appState represents the current state of the application appName. Possible states of the application are:
- unknown (0) – state of the application could not be determined.
- started (1) – application is running
- stopped (2) – application is not running.
MIB also defines one notification object appStartedEvent
appStartedEvent NOTIFICATION-TYPE
OBJECTS {appName,}
STATUS current
DESCRIPTION "The SNMP trap that is generated when the application is started."
::= { appEvents 1 }
SNMP agent should send notification (trap) when application appName is started. Included in the trap’s varbind list is name of the application that was started.
Here is tree structure of the ND-APPLICATION-MIB
[1.3.6.1.4.1] iso.org.dod.internet.private.enterprises
|
+-[4761] nuDesign
|
+-[99] ndtExperimental
|
+-[12] app
|
+-[ 1] appObjects
| |
| +-[ 1] -RW- DisplayString appName
| +-[ 2] -RO- appState
|
+-[ 2] appEvents
|
+-[ 1] appStartedEvent
Please note that the sample projects assume the directories for includes and libraries as the default install directories. If you have installed the
xAgentBuilder product in a directory other than the default directory then you should
- go to the C/C++ tab in the Project Settings. Choose Preprocessor in the Category and update the ‘Additional Include Directories’ field.
- go to the Link tab in the Project Settings. Choose Input in the Category and update the ‘Additional Library Path’ field.
AppMon1 – Agent #1
This is the simplest of the three agents. Source code for this agent is in the AppMon1 directory. Both MIB objects are implemented in DAppObjects class.
appName is instrumented as class member variable m_strAppName.
When a Get SNMP request for appState variable arrives, OnGet_AppState method of DAppObjects class gets called. State
of the application is simply retrieved by calling GetProcessStatus method.
The possible states of the application reported by the agent are:
- unknown (0) – state of the application could not be determined. Also reported when appName is zero length string.
- started (1) – application is running
- stopped (2) – application is not running. Note that this does not mean that the application with name appName exists; i.e. appName is simply compared with the list of names of processes currently running.
Note that this agent does not implement the appStarted event.
To test this agent, please start Visual MIBrowser and load the ND-APPLICATION-MIB. Open the iso.org.dod.internet.private.enterprises.NuDesign nodes. Right-click on the NuDesign node and click on ‘Walk’ to walk the agent. You should see a response coming back from the agent. Now right click on the appName object and choose set. Double click on the entry in the Set window. You can set the name of the process whose status you would like to check. For example, ‘winlogon.exe’. You set this value and then do a GET on ‘appState’. You should see the response as ‘started (1)’.
AppMon2 – Agent #2
Source code for this agent is in the AppMon2 directory. In this version a thread (instance of class ProcMonThread) will be created to monitor the state of the application. This thread will periodically (once every second) check and save the state of the application appName. If the current state is ‘started’ and the previous is either ‘unknown’ or ‘stopped’ a notification will be sent.
Both objects in the MIB are instrumented in the object of class ProcessData. Since access to the data can occur from (at least) two different threads (SNMP request handler and Application Monitor), SingleWriter-MultipleReader synchronization objects is also part of the ProcessData. Before methods of ProcessData are called an appropriate lock is acquired: for Get methods ‘read’ lock, for Set methods ‘write’ lock.
This architecture decouples the actual task of application monitoring from the SNMP agent. SNMP requests are simply fulfilled on variables that represent MIB objects. Note that the state retrieved through an SNMP request is actually the ‘cashed’ value and does not reflect the app state at the time of the request, but at the last time when the monitor thread updated it. In other words, it could be off by one second (i.e. the interval used for periodical app state checking).
Please use Visual MIBrowser or another management application to try out this agent.
AppMon3 – Agent #3
This version of the agent relies on the AppMonServer application. AppMonServer is a console application that monitors the state of the application. Source code for this agent is in the AppMon3 directory and source code for the helper app is in the AppMonServer directory.
Communication between Agent #3 and AppMonServer is established through the named pipes. Two pipes will be opened. One, \\.\pipe\appmon, for fulfilling requests from the agent (initiated by SNMP requests), the other, \\.\pipe\notif, for sending notification(s) from AppMonServer to the agent.
The architecture of the AppMonServer is similar to the architecture of the Agent #2. On start, it creates an application monitor thread, ProcessMonitorThreadProc, then creates \\.\pipe\appmon pipe and eneters the endless loop. Inside the loop it waits for the client (see PipeRequest function in Agent #3) to connect to the pipe. When the connection occurs, a thread RqHndlrThreadProc is created to handle one request, and new wait for the client to connect to the pipe is started.
AppMonServer instruments ‘appName’ and ‘appState’ in the object of class ProcessData. Since access to the data can occur from two different
threads ( RqHndlrThreadProc and ProcessMonitorThreadProc), mutex synchronization objects are also part of the ProcessData. Before methods of
ProcessData are called a lock is acquired.
Thread RqHndlrThreadProc reads from the \\.\pipe\appmon pipe, decodes the request, performs the requested operation, writes the result to the same pipe and finally exits. The format of the request is following:
<g|s>:<varname>:<value>
<g|s> |
g = get, s = set |
<varname> |
variable name (“appState” or “appName”) |
<value> |
variable value (for set request) |
Note that the request handler can accept only one variable at a time. This is chosen for the simplicity and to show one way of interprocess communication. Depending on your application you may want to consider tcp/ip or some other way of communicating with an application.
The request is resolved by calling access methods of ProcessData. Note that calls are serialized by acquiring a mutex lock.
Thread ProcessMonitorThreadProc simply checks and saves the state (in the instance of the ProcessData class) of the application every second. If the current state is ‘started’ and the previous is either ‘unknown’ or ‘stopped’ the Notify function will be executed. It tries to connect to the \\.\pipe\notif pipe and if it succeeds it simply writes appName to it and disconnects.
Agent #3 creates the thread NotifThread. This thread creates \\.\pipe\notif pipe, and then enters the endless loop, Inside the loop it waits for the client (ProcessMonitorThreadProc in AppMonServer) to connect to it, and when connection occurs, reads the ‘appName’ from the pipe and send SNMP notification.
This agent also instantiates an object of class ProcessData, and uses its access methods to retrieve/modify MIB variables. But this ProcessData class is different than the one in AppMonServer. If you take a look at the implementation of the access methods you will see that all of them call the PipeRequest function. The PipeRequest function tries to connect to the existing \\.\pipe\appmon pipe (that is the one created by AppMonServer) and if it succeeds writes a request to the pipe (format of the request is described above), then it reads the result from the same pipe, and finally disconnects.
Alternate Project Targets
The project files generated by our xAgentBuilder9 code generator are intended to be usable across a wide variety of Visual Studio versions. The specific version of these generated project files are for Visual Studio 2008. These project files can be imported into all versions of Visual Studio since then.
The files also default to a win32 project, but you can easily add a new platform, e.g. x64, by adding it in the Visual Studio configuration manager, once the project has been loaded. There is a section in the NuDesign xAgentBuilder9 help pertaining to this.
The layout of the projects provided by the code generator places all executables in the main directory of their respective sub-project.
E.g.
EXE, DLL & REM
The created project files also place in these same directories, the configuration files for the standalone agent and the remote agent.
E.g.
If the project name is ‘Garage’, then ‘Garage.xnv’ is place under EXE and ‘Garage.txt’ is placed under REM.
When a ‘Debug’ target is selected, the projects append a ‘D’ to the end of the name of each of the executables, so that they can reside in the same directory without colliding with the release version. This makes it convenient to test ‘Debug’ and ‘Release’ versions of the code against a single configuration file.
When you add the ‘x64’ platform to the project that same still applies. The final executables are placed in the same directory. It might be advisable to rename the output executable files of each of the sub-projects to avoid ‘colliding’ with the x86 versions of the executables.
E.g.
If the project name is ‘Garage’, rename the EXE output file to be ‘Garage64.exe’
If you are importing to a more recent version of Visual Studio, then due to the placement of executables, you will see a number of warnings from the conversion process. These are mostly related to all sub-project output files going to the one sub-project directory. Typically there will be multiple associated with each but relate to the same problem.
This placement of executables is not necessarily ‘standard’ so when building the project on a newer Visual Studio, you will see related
warnings during the build. Again this is due to the layout of the projects. These too can generally be ignored, except if you have chosen not to rename the output files of your x64 builds, in which case it will act as a reminder to you that you may be overwriting an existing file.