To understand the rationale of this sample, view Creating Resource Monitor Agent extensions.
The DLL for a monitor library has predefined entry points that the monitor agent uses to communicate with the monitor library. The DLL entry points are:
The implementations of these exported DLL functions are designed to be generalized in this sample and should cover most monitor library implementations. The process of writing a monitor library using this framework is divided into two steps.
The three main classes used in writing a monitor library are:
The building blocks of this framework are the KeyInfoBlock class. This class encapsulates the characteristics of the nodes displayed in the monitor console. A KeyInfoBlock object can represent either a folder or a leaf node. Leaf nodes require additional attributes because they are actually monitored. Leaf nodes need to specify data type information, format, and pulse information.
Pulse information is used to determine when updates should be requested, how many data points should be saved, and which pulse values to save. The format of the data is a string that describes how the data should display in the monitor console. The format string is the same format used by a printf statement, for example, %s or %6.2f. KeyInfoBlock objects are constructed using a constructor that requires two string parameters, a displayable name, and a programmatic name. The displayable name is the string that is displayed in the monitor console. The programmatic name is the name given to an object that can be used for identification.
The KeyInfoBlock class has two main purposes: encapsulate the information needed to describe the characteristics of a monitored item, and provide the functionality to create the renderings of the object that are required by the monitor agent. The information necessary to create these renderings is contained in the KeyInfoBlock object. The class provides member functions to allow setting these characteristics and member functions to orphan the data structures used by the monitor agent.
Because of the large size of the data structure and the number of strings the object contains, KeyInfoBlock objects are reference counted to help improve performance. The reference counting semantics for this framework does not make a new copy of the object when it is modified. All copies of the object see all modifications made to any of the reference counted copies.
The MonitorLibrary class is the repository for KeyInfoBlock objects and maintains the parent-child relationships. KeyInfoBlock objects are added to the monitor library using the addKeyInfoBlock member function. There are two overrides of this member function, one for adding the KeyInfoBlock object as the root node, and the second for adding a KeyInfoBlock object as a child of another KeyInfoBlock object. The MonitorLibrary object is created in monsamp.cpp during function InitNodes and is maintained for the lifetime of the interaction between the monitor agent and the monitor library. The MonitorLibrary object is used to access KeyInfoBlock objects.
The parent-child relationship between KeyInfoBlock objects is used to orphan an AttributeNodeList data structure. AttributeNodeList is used by the monitor agent to display the child nodes of a folder in the monitor console. The monitor agent asks for the children of a folder by specifying the AttributePath of the folder. The AttributePath is used to find the parent KeyInfoBlock object, and then the children of that node are cursored to build up the AttributeNodeList. The monitor agent then queries additional information for each of the leaf nodes. A mapping is then established for the KeyInfoBlock objects so that the monitor agent can specify a particular KeyInfoBlock object by a handle instead of the more cumbersome AttributePath string. The handle is automatically assigned to a KeyInfoBlock when it is constructed. A KeyInfoBlock object can be obtained by lookup using its AttributePath and the handle can be obtained through a member function. The handle can then be used to later retrieve the same KeyInfoBlock object from the monitor library's collection.
The ValueGenerator class is an abstract base class that the monitor library author must override in a subclass. In this ValueGenerator subclass, the monitor library author can provide a hierarchy of the nodes, represented by the KeyInfoBlock objects. There are two abstract member functions in the ValueGenerator subclass that the monitor library author needs to implement. The first, ValueGenerator::addNodesTo, is where KeyInfoBlock objects are added to the MonitorLibrary object using an interface that adds the object with a parent KeyInfoBlock object or adds the object as the root node. The KeyInfoBlock objects, which are reference counted, are copied when added to the MonitorLibrary object. The KeyInfoBlock object, therefore, does not require a lifetime past the scope of the ValueGenerator::addNodesTo member function. It might be convenient to keep a copy of these objects for comparison purposes. You can specify nodes for the monitor library statically or dynamically. If you specify nodes statically, all of the nodes of the tree are known at initialization. If you specify nodes dynamically, new nodes can be added later. The dynamic scenario is described in detail later in this document. The second member function that needs to be overridden is ValueGenerator::setValueFor. The KeyInfoBlock object that requires a value is the passed parameter. To easily determine which attribute needs to be updated, use the comparison operator against the saved KeyInfoBlock object. You can also compare the programmatic name of the KeyInfoBlock objects, but this technique is only viable if the leaf nodes are uniquely named.
To write a monitor library, first decide on the hierarchy of nodes that are to be displayed in the monitor console. The nodes are then mapped to KeyInfoBlock objects. The KeyInfoBlock objects are created in the ValueGenerator subclass. In this sample, that would be the MyValueGenerator class. The ValueGenerator derived class is instantiated in the monsamp.cpp file and adopted by the MonitorLibrary object. To use a different subclass name, you must change the monsamp.cpp file in the function InitNode. The subclass must override the two abstract member functions addNodesTo and setValueOf.
The constructor for the KeyInfoBlock class requires two parameters, the displayable name and the programmatic name. All KeyInfoBlock objects require this information. A special case for the programmatic name needs to be explained here. The root node of an authored monitor library is a child of a folder that is already present in the monitor console. An entry in the monitor.ini file controls the displayable name of the DLL as displayed by the monitor console. This root folder is used to request children of the monitor library when the folder is expanded. The AttributePath requested by the monitor agent must match the programmatic name of the KeyInfoBlock objects. This means that the root KeyInfoBlock object must have the same programmatic name as the name of the DLL. For example, the sample monsamp.dll would need a programmatic name of MONSAMP for the root KeyInfoBlock object. The member functions that look for a specific KeyInfoBlock object in the class MonitorLibrary check the entire path of KeyInfoBlock objects, all the way up to its root object. If the root object is not named correctly then none of the objects will be found.
Here is the construction of the nodes from myvalugen.cpp. The objects are all of type KeyInfoBlock, but at this point you can not determine which are leaf nodes and which are folders. Note the programmatic name of the fSampleMonitor KeyInfoBlock object; as stated before, this must be the name of the DLL.
MyValueGenerator::MyValueGenerator() : fSampleMonitor( "Sample", "MONSAMP") , fFolder2( "Folder", "FOLDER2") , fFolder3( "Folder", "FOLDER3") , fPI( "PI", "PI") , fTime( "Time", "SECONDS") , fPrinter1( "Printer 1", "PRINTER1") , fPrinter2( "Printer 2", "PRINTER2") { }
The MyValueGenerator class creates all of its KeyInfoBlock objects in its constructor. This is a convenience so the objects can be saved for comparison purposes when values for the leaf objects are requested. The KeyInfoBlock objects that are folders only need to be constructed with the displayable name and the programmatic name. The Java Language String is set using the default value of "en_US". If all of the KeyInfoBlock objects use the same Java Language String, the value can be set once using the static member function KeyInfoBlock::setDefaultJavaLangString, but this must be set before the KeyInfoBlock objects are constructed. The leaf KeyInfoBlock objects need additional information, including setting the data type, format string, and pulse information. There are two types of data, a double or a string. If the data type is double, KeyInfoBlock::setDoubleValue is used to set its value, otherwise KeyInfoBlock::setStringValue is used. The pulse information has three components. The first is the base pulse that specifies how often the data is to be collected. The value is specified in milliseconds. The second piece of information is how many recordings should be saved. The last piece is which pulses should be saved, for example, every third pulse.
The child-parent relationship is specified when the nodes are added to the MonitorLibrary object. The attributes of the leaf nodes are set up here before the objects are added to the monitor library. The monitor agent will ask for these characteristics during the call to GetNodeParms in monsamp.cpp.
KeyInfoBlock objects are added to the MonitorLibrary object in response to the monitor agent calling the DLL entry point, InitNodes. The ValueGenerator object is adopted at this point, and then used to add the KeyInfoBlock objects. The adding of the KeyInfoBlock is initiated by the InitNodes indirect calling of ValueGenerator::addNodesTo through the MontorLibrary object. The nodes are added to the MontorLibrary object using MonitorLibrary::addKeyInfoBlock. There are two overrides of the MonitorLibrary::addKeyInfoBlock member function. The first override just takes on KeyInfoBlock object, and adds this object as the root node. This node, as described previously, must have the programmatic name set to the name of the monitor library DLL. The second override allows a KeyInfoBlock object to be added as the child of a previously added parent KeyInfoBlock object. The KeyInfoBlock objects added to the MonitorLibrary object are copied so the KeyInfoBlock objects can be deleted if they are no longer needed. This example is static because all of the nodes are known up front and are added during the addNodesTo override. In the dynamic case, however, new nodes can be added at a later time.
void MyValueGenerator::addNodesTo( MonitorLibrary& fMonitor ) { // Set up the tree that represents the monitors defined in this DLL. This // directed graph will be used to supply the information required in GetNode // list. We will then assign a key to look up the key information blocks // during NodeConnect. This key will be used to pulse for values and then // supply those values when needed. // The first entry is a dummy entry for the root node. The programmatic name of // this node needs be the name of the dll. fMonitor.addKeyInfoBlock( fSampleMonitor ); // next level of nodes, two are folders and 1 is a leaf node. fSampleMonitor // is the parent of the following nodes. fMonitor.addKeyInfoBlock( fSampleMonitor, fFolder2 ); fMonitor.addKeyInfoBlock( fSampleMonitor, fFolder3 ); // next are all of our leaf nodes. // Set up information to be returned with getNodeParms fPI.setBasePulse( 30 * 1000 ); // once every 30 secs fPI.setPulsesPerData( 1 ); // get data on every pulse. fPI.setNumberRecorded( 10 ); // keep around 5 minutes worth. fPI.setFormat( "%6.2f"); fPI.setDataType( KeyInfoBlock::kDouble ); fMonitor.addKeyInfoBlock( fSampleMonitor, fPI ); // add pulse information to the leaf nodes. fTime.setBasePulse( 30 * 1000 ); // once every 30 secs fTime.setPulsesPerData( 1 ); // get data on every pulse. fTime.setNumberRecorded( 10 ); // keep around 5 minutes worth. fTime.setFormat( "%0.0f"); fTime.setDataType( KeyInfoBlock::kDouble ); fMonitor.addKeyInfoBlock( fFolder2, fTime ); // add pulse information to the leaf nodes. fPrinter1.setBasePulse( 30 * 1000 ); // once every 30 secs fPrinter1.setPulsesPerData( 1 ); // get data on every pulse. fPrinter1.setNumberRecorded( 10 ); // keep around 5 minutes worth. fPrinter1.setFormat( "%s"); fPrinter1.setDataType( KeyInfoBlock::kString ); fMonitor.addKeyInfoBlock( fFolder2, fPrinter1 ); // add pulse information to the leaf nodes. fPrinter2.setBasePulse( 20 * 1000 ); // once every 20 secs fPrinter2.setPulsesPerData( 1 ); // get data on every pulse. fPrinter2.setNumberRecorded( 1 ); // keep around 20 seconds worth. fPrinter2.setFormat( "%s"); fPrinter2.setDataType( KeyInfoBlock::kString ); fMonitor.addKeyInfoBlock( fFolder3, fPrinter2 ); }
The member function MonitorLibrary::addKeyInfoBlock adds the nodes in an ordered fashion to the end of a link list. This order is irrelevant to the monitor agent. The monitor agent displays the nodes in alphabetical order in the monitor console and this order cannot be controlled. Note that once a node has been reported to the monitor agent it is always be displayed in the monitor console. There is no way to remove a node once it has been supplied to the monitor agent, which is why there is no interface in MonitorLibrary to remove a KeyInfoBlock. The information is not persistent, so once the monitor agent has been stopped on the monitored machine, the nodes do not show up unless they are reported again.
Nodes can be added dynamically to a monitor Library also by overriding ValueGenerator::updateNodes. The root node of the monitor library must still be added in the member function ValueGenerator::addNodes. Any other node can be dynamically added later. When a folder is expanded, the monitor agent requests the AttributeNodeList. This request occurs every time a folder is opened, so nodes can be added at this time. The monitor agent calls the DLL entry point GetNodeList, which is implemented in monsamp.cpp. Before the AttributeNodeList data structure is orphaned to the monitor agent, the monitor library author is given the opportunity to add nodes. You can only add nodes to the monitor console, you can not remove nodes. Nodes can be added in the override of the ValueGenerator::updateNodes member function. The folder opened is the KeyInfoBlock object passed as a parameter. If a new node is to be added, construct one and use the add interface on the MonitorLibrary object, which is also a parameter. Most likely the passed KeyInfoBlock object should be the parent object.
The DLL entry point, NodePulse, has the code to decide if data is needed at this time for the requested KeyInfoBlock object. Data values are set through a call to the MonitorLibrary object, which in turn calls the virtual member function ValueGenerator::setValueFor. This is where the monitor library author implements the code to query the requested data. The query can be on a hardware device, the operating system, or whatever is being monitored for its current status. The returned value is then set into the KeyInfoBlock object parameter and the data is set to be valid. Valid data is then displayed in the monitor console.
The following example demonstrates the providing of data for a monitored item. The fBlock is compared against copies of the leaf nodes to see which item requires data. The monitor author supplies the code for setting the value of the item. The correct KeyInfoBlock::set member function should be called based on the item's data type. The framework does not throw exceptions at this time for these types of mistakes.
void MyValueGenerator::setValueFor( KeyInfoBlock& fBlock ) { // Use the KeyInfoBlock created in the ctr to compare with fblock. // Then supply the data for the KeyInfoBlock requested. if ( fBlock == fPrinter1 ) { // code that actually determines value would be placed here. fBlock.setStringValue( "offline" ); fBlock.setDataValidity( true ); } else if ( fBlock == fPrinter2 ) { fBlock.setStringValue( "online" ); fBlock.setDataValidity( true ); } else if ( fBlock == fPI) { fBlock.setDoubleValue( 3.14 ); fBlock.setDataValidity( true ); } else if ( fBlock == fTime ) { fBlock.setDoubleValue( (double) time(NULL) ); fBlock.setDataValidity( true ); } }
The monitor agent loads monitor libraries by searching the bin directory of Director for DLLs that follow the monitor library naming convention. The monitor library has the naming convention of monxxxxx.dll, where xxxxx is user-defined. The monitor libraries are then invoked by calling the predefined entry points in the monitor library DLL. The sample monitor, for example, is named monsamp.dll. The DLL appears in the monitor console under the root folder, Director Agent. By default, the name of the folder for a monitor library is the name of the DLL. This folder can be mapped to a more meaningful string by placing an entry in the file monitors.ini, which is under the Director bin directory in a locale specific directory. An example is the mapping of monsamp to Customer Sample.
Assume that the monitor library DLL is executing on the monitored machine. The easiest way to debug a monitor library DLL is to use tracing messages in the DLL, for example:
#ifdef _DEBUG ofstream to("C:\\temp\\test.out", ios::out | ios::app ); to << "GetNodeList " << endl; #endif
Remember that the monitor library DLL is executing on the monitored machine, which is probably not the machine that is displaying the monitor console. The trace file is on the monitored machine. The file test.out can then be used to view tracing information from the monitor library DLL.
A monitor agent might not be able to find the monitor library DLL on a monitored machine for several reasons. If the monitor library is not set up correctly then there will not be an entry for the DLL in the monitor console. The first problem might be that the Director agent is already running when the monitor library DLL was installed on the target machine. For this monitor library to be discovered, the IPC mechanism must be stopped and restarted so TWGMONIT will search for and find the new monitor library DLL. Another possibility is that the monitor library DLL was not named correctly. The last possibility is that the monitor library DLL was not built with the required DLL exports. This could occur if a DEF file is not used when linking the DLL.
This sample uses char * for its interface for setting and retrieving strings, and therefore only supports single byte character strings. The monitor agent expects all of the strings passed to it to be in a form called Compressed Unicode. This sample takes care of converting all of the SBCS strings to compressed Unicode strings that the monitor agent expects. If DBCS NLS support is required, then the KeyInfoBlockData class interface needs to be enhanced to allow passing Unicode strings.
A project for the sample can be created in Microsoft Visual C++ and the project built as a console DLL. The external requirements of the sample are header files shipped with the SDK and the twgucd32.dll file. The sample needs to be linked against twgucd32.lib, which is also shipped with the SDK.