WMI Providers

Overview

Microsoft favors provider-based architectures. There are OLE DB providers, cryptographic providers that are designed to work together with Microsoft Crypto API, and finally, there are WMI providers. The reasons for Microsoft's affection are more than just sentimental. Today's competition-driven world of software development is ridiculously diverse when it comes to technology, which often renders conventional architectures useless.

Take relational databases, for instance. Over the past few years, fierce competition has successfully annihilated many aspiring database vendors; however, there are still enough database products around to turn the lives of IT strategists into a never-ending nightmare. While this market segment is certainly dominated by such giants as Microsoft SQL Server and Oracle, die-hard Sybase is still out there, holding many Wall Street firms hostage, and even DB2, despite its Jurassic mainframe past, is making headway in the modern computing world. Moreover, regardless of the market dynamics, databases tend to have a much longer life span when compared to other software products. Once committed to a particular database infrastructure, you are basically trapped, since replacing such an infrastructure is generally a very time-consuming and extremely expensive exercise.

For in-house software developers, database independence may remain a subject for a theoretical discussion. However, for numerous commercial software vendors, it is a constant source of grief. Database affinity severely limits the product's market penetration abilities and may easily amount to an extensive loss of revenue.

This is where provider-based architectures come to the rescue. By building a layer of abstraction between the underlying database API and the application, you can afford the luxury of coding to a standard set of interfaces and then rely on plug-in components or providers to translate the application's requests into the database API calls. The advantages are apparent—an application, which utilizes such a provider architecture, can be ported from one database to another in seconds, without changing a single line of code. All you need to do is replace the database provider component—this can usually be achieved simply by changing a configuration file.

When it comes to database access, provider-based frameworks do not surprise anyone. In fact, many such frameworks have existed for years, especially on Windows platforms. OLE DB and its predecessor, ODBC, have always been, and remain, the primary vehicles for dealing with relational and non-relational data sources for just about any Windows-based software system. These technologies have proven themselves to be so successful that many software vendors, in addition to Microsoft, have made numerous attempts to utilize the same architectural principles. Thus, Rogue Wave Software offers the SourcePro DB (and now-defunct DBTools++) library, which is designed to solve the database independence problem through the use of plug-in database drivers. The Perl DBI database interface is built upon the same idea of providing a generalized interface that internally communicates with database-specific replaceable components. Even Sun Microsystems ported ODBC to their premier Solaris operating environment.

Today the apparent success of provider-based architectures has attracted the close attention of many software engineers who are looking to solve problems other than those of database access. Although this architectural principle may easily and beneficially be applied to many aspects of modern software development, the area of systems management certainly deserves special attention. By definition, a good management system must be able to deal with the wide variety of hardware and software technologies that make up today's complex computing universe. Unfortunately, inventing a generalized approach to managing thousands of dissimilar entities is extremely difficult; that is why most management systems tend to concentrate on certain aspects of the managed universe. For example, the Simple Network Management Protocol (SNMP) is almost solely dedicated to network management and most of the attempts to adopt it to address other areas of systems management never gained much popularity.

WMI is unique in that its goal is to cover all aspects of the management domain. The industry experience accumulated over the last couple of years indicates that WMI is fairly successful at achieving this goal. You should understand that one of the critical factors that determines WMI's success is its provider-based architecture. WMI itself is just a framework for collecting, disseminating, and managing the data, and its value is directly related to the availability of providers that are capable of dealing with various facets of the management domain.

Fortunately, there are plenty of providers. First, the WMI SDK includes a number of providers that allow you to manage the most common aspects of computer systems, such as disks, networks, processors and so on. To address more esoteric management needs, there are providers that collect various systems statistics; providers that allow you to integrate legacy systems, such as SNMP into WMI; and providers that deal with specific Windows technologies, such as Active Directory. Finally, there are tools and wizards for building custom providers that can do just about anything imaginable. The fact that WMI makes such diverse and versatile providers available is the main reason for its growing popularity.

As you may have guessed by now, this chapter is all about WMI providers. However, it does not recap the material presented in Chapter 5. This time, rather than focusing on the subject of custom provider development, I will attempt to provide an overview of some of the existing providers distributed with WMI. Covering every single existing provider is well outside the scope of this book, so I will only address a few—primarily those that do not seem to receive the coverage they deserve elsewhere.


Performance Data Providers

Monitoring system and application performance is one of the most critical tasks with which many system managers concern themselves. Although there are many performance monitoring tools available, you will find that being able to review the performance data via WMI is very attractive. First, you can effectively use the existing WMI-based management tools, such as WMI Studio, to monitor the performance. Second, once you become familiar with various WMI client APIs, you will learn to appreciate the unlimited flexibility of programmatic access to the performance data that they afford.

The system and application performance data is delivered to WMI by several providers. In fact, there are three available providers that can collect the performance data from local or remote computers:

  • Performance Monitoring provider: This provider supplies performance information in the form of instances of CIM classes that represent the performance counters of interest.
  • Performance Counter provider: This provider supplies raw performance counter data so that the management applications often have to apply special calculation algorithms to derive meaningful performance data.
  • Cooked Counter provider: This provider supplies calculated or "cooked" performance data, similar to the data that appears in the System Performance Monitor.

Although all of these providers can be used to retrieve just about any kind of system or application performance information, there are certain advantages and disadvantages in using a particular provider to satisfy certain monitoring requirements. The remainder of this section is dedicated to providing more details on the operations and the usage patterns of each of these providers.

The Performance Monitoring Provider

The Performance Monitoring provider is a conventional WMI provider that is available under Windows NT 4.0 and Windows 2000 platforms. It is not available on Windows 95, Windows 98, or Windows Me platforms. The Performance Monitoring provider is comprised of two WMI providers: instance and property providers. The instance provider supplies the performance data in the form of instances of CIM classes. The property provider updates the properties of the specific instances of these classes.

Installing the Performance Monitoring Provider

This provider is not registered by default when the WMI SDK is installed. In order to request its services, you have to carry out all registration-related tasks by hand. Since there are really two providers—instance and property—you can perform the registration in two ways. To register the provider as instance provider, you must create instances of two WMI classes, __Win32Provider and __InstanceProviderRegistration, and save them to the CIM Repository. The following MOF code can be used to register the Performance Monitoring provider as an instance provider:

instance of __Win32Provider as $ProvInstance {
 Name = "PerformanceMonitorProvider";
 ClsId = "{f00b4404-f8f1-11ce-a5b6-00aa00680c3f}";
};
instance of __InstanceProviderRegistration {
 Provider = $ProvInstance;
 SupportsPut = FALSE;
 SupportsGet = TRUE;
 SupportsDelete = FALSE;
 SupportsEnumeration = TRUE;
};

Note that the ClsId property of the __Win32Provider instance refers to the provider's COM object, which is implemented within stdprov.dll. This DLL, which can be found in the %SystemRoot%System32WBEM directory, has to be correctly registered on your system for the provider to function.

Registering the provider as a property provider is very similar to registering the instance provider. In fact, the only difference is that you need to use an instance of the __PropertyProviderRegistration system class instead of the __InstanceProviderRegistration instance:

instance of __Win32Provider as $ProvInstance {
 Name = "PerformanceMonitorPropProvider";
 Clsid = "{72967903-68EC-11d0-B729-00AA0062CBB7}";
};
instance of __PropertyProviderRegistration {
 Provider = $ProvInstance;
 SupportsGet = TRUE;
 SupportsPut = FALSE;
};

Here, the ClsId property of the __Win32Provider instance also refers to a COM object implemented within the same stdprov.dll.

Once registration is complete, the provider is fully operational. However, you will find that it is not obvious how to access the performance data since the CIM Repository does not seem to contain any classes that are backed by the Performance Monitoring provider. It turns out that these classes also have to be created manually. Depending on whether the instance or property provider is used, the definition for a class, which represents a performance counter, may be different.

Using the Performance Monitoring Provider as an Instance Provider

When the Performance Monitoring provider is being used as an instance provider, the WMI class that is to house the performance data should roughly correspond to a given Performance Monitor object. Therefore, if your objective is to monitor the CPU performance, a WMI class should look like the Processor object in Performance Monitor. Then each property of the WMI class will relate to the respective counter name within the Processor object. For instance, in order to be able to retrieve the value of the "% User Time" counter, the WMI class definition should contain a property that corresponds to this counter.

Although you can certainly look up the counter names and their associated descriptions using the Performance Monitor GUI, doing this programmatically is a lot more fun. Coincidentally, the System.Diagnostics namespace contains a few types that come in very handy when you are working with performance counters. For example, the following code snippet will print out the names and descriptions of all performance counters that make up the Processor object:

using System;
using System.Diagnostics;
class CounterHelper {
 public static void Main(string[] args) {
 PerformanceCounterCategory cat =
 new PerformanceCounterCategory("Processor");
 foreach(PerformanceCounter cntr in cat.GetCounters("0")) {
 Console.WriteLine("{0}: {1}", cntr.CounterName, cntr.CounterHelp);
 }
 }
}

This code is extremely simple. First, it creates an instance of PerformanceCounterCategory type, which refers to the Processor object of Performance Monitor. It then enumerates all counters within the Processor object that are returned by the GetCounters method of the PerformanceCounterCategory object. This method takes a single string parameter, which represents the name of the performance object instance. It is always safe to use "0" when dealing with Processor objects because any machine will always have at least one CPU. Finally, for each counter object returned, the code prints out the values of two properties: CounterName, which contains the display name of the counter, and CounterHelp, which is essentially a counter description. The output produced by this code resembles the following:

% Processor Time: Processor Time is expressed as a percentage of the elapsed
time that a processor is busy executing a non-Idle thread. It can be viewed as
the fraction of the time spent doing useful work. Each processor is assigned an
Idle thread in the Idle process, which consumes those unproductive processor
cycles not used by any other threads.
% User Time: User Time is the percentage of processor time spent in User Mode in
non-Idle threads. All application code and subsystem code executes in User
Mode. The graphics engine, graphics device drivers, printer device drivers,
and the window manager also execute in User Mode....

Once all required properties of a WMI class are identified, the class can be defined as follows:

[Dynamic, Provider("PerformanceMonitorProvider"),
 ClassContext("local|Processor")]
class PerfMon_Processor {
 [key]
 string Processor;
 [PropertyContext("% Processor Time")]
 real32 ProcessorTime;
 [PropertyContext("% User Time")]
 real32 UserTime;
 [PropertyContext("% Privileged Time")]
 real32 PrivilegedTime;
 [PropertyContext("Interrupts/sec")]
 real32 Interrupts;
 [PropertyContext("% DPC Time")]
 real32 DPCTime;
 [PropertyContext("% Interrupt Time")]
 real32 InterruptTime;
 [PropertyContext("DPCs Queued/sec")]
 real32 DPCsQueued;
 [PropertyContext("DPC Rate")]
 real32 DPCRate;
 [PropertyContext("DPC Bypasses/sec")]
 real32 DPCBypasses;
 [PropertyContext("APC Bypasses/sec")]
 real32 APCBypasses;
};

Note, it is not really necessary for a WMI class definition to include the properties that refer to each and every counter within a given performance object. You can simply pick just those properties in which you are interested.

You may have noticed a couple of class and property qualifiers here that may seem unfamiliar. The first one is the Dynamic class qualifier; it simply indicates that a provider backs instances of a particular class. The Provider qualifier establishes the binding between the class and its provider; its string parameter refers to the name of __Win32Provider instance, which represents the provider registration. The ClassContext qualifier establishes the mapping between the WMI class and a particular performance object. Its parameter is a string that may contain a number of tokens separated by a vertical bar (|). The first token represents the name of the computer that houses the performance object of interest. The second token is the name of the performance object to bind to. Finally, there is the PropertyContext qualifier, which establishes the binding between a particular property of the WMI class and its respective performance counter. This qualifier's parameter is a display name of the associated performance counter.

Yet another thing to notice is the Processor property, marked with the Key qualifier. Since performance objects are represented by instances of WMI classes, every such instance must have a unique identity. Thus, the Performance Monitoring provider will automatically set the Processor property to the instance ID of the respective CPU. For instance, on a dual-CPU machine, the provider will create two instances of the PerfMon_Processor class, with their Processor properties set to 0 and 1 respectively.

Once compiled and saved into the CIM Repository, the PerfMon_Processor class is ready to be used. Accessing the performance data is no different from accessing instances of any other WMI classes. Thus, the following snippet of code continuously prints out some statistics for CPU 0:

using System;
using System.Management;

class CPUMonitor {
 public static void Main(string[] args) {
 while(true) {
 ManagementObject mo = new ManagementObject("PerfMon_Processor='0'");
 Console.WriteLine("% Processor Time: {0}", mo["ProcessorTime"]);
 Console.WriteLine("% User Time: {0}", mo["UserTime"]);
 Console.WriteLine("% Privileged Time: {0}", mo["PrivilegedTime"]);
 System.Threading.Thread.Sleep(5000);
 }
 }
}

Note that every iteration of the while loop recreates the instance of ManagementObject type. Although seemingly inefficient at first, this is necessary in order to refresh the performance data. As you may remember, a property access operation invokes the IWbemClassObject::Get method, which always fetches the locally cached property value. Therefore, unless the instance of the ManagementObject type is rebound to its underlying WMI object, its properties will not be refreshed.

Generally, mapping performance objects to WMI classes is straightforward—a particular performance object category corresponds to a WMI class, counters correspond to the class properties, and performance objects map to instances of WMI classes. However, there are certain performance objects that do not easily yield themselves to this kind of modeling. The TCP object, for instance, which contains a slew of TCP/IP statistical counters, does not have any instances. Consider the following WMI class, which may be used to retrieve TCP/IP performance data:

[Dynamic, Provider("PerformanceMonitorProvider"), ClassContext("local|TCP")]
class PerfMon_TCP {
 [Key]
 string KeyProp;
 [PropertyContext("Segments/sec")]
 real32 Segments;
 [PropertyContext("Connections Established")]
 real32 ConnectionsEstablished;
 [PropertyContext("Connections Active")]
 real32 ConnectionsActive;
 [PropertyContext("Connections Passive")]
 real32 ConnectionsPassive;
 [PropertyContext("Connection Failures")]
 real32 ConnectionFailures;
 [PropertyContext("Connections Reset")]
 real32 ConnectionsReset;
 [PropertyContext("Segments Received/sec")]
 real32 SegmentsReceived;
 [PropertyContext("Segments Sent/sec")]
 real32 SegmentsSent;
 [PropertyContext("Segments Retransmitted/sec")]
 real32 SegmentsRetransmitted;
};

The class definitions in this code look very similar to those for the performance Processor object; however, it is unclear how you can access the individual instances of this class since the TCP performance object is global. One way to do this is to bind to a class and then enumerate its instances:

using System;
using System.Management;

class TCPMonitor {
 public static void Main(string[] args) {
 ManagementClass mc = new ManagementClass("PerfMon_TCP");
 foreach(ManagementObject mo in mc.GetInstances()) {
 Console.WriteLine("Key: {0}", mo["KeyProp"]);
 }
 }
}

Running the preceding code reveals a single instance with its KeyProp key property set to @. This means that the TCP performance object essentially maps to a singleton WMI class. Therefore, the WMI class definition above should be changed as follows:

[Singleton, Dynamic, Provider("PerformanceMonitorProvider"), ClassContext("local|TCP")]
class PerfMon_TCP {
 [PropertyContext("Segments/sec")]
 real32 Segments;
 [PropertyContext("Connections Established")]
 real32 ConnectionsEstablished;
 [PropertyContext("Connections Active")]
 real32 ConnectionsActive;
 [PropertyContext("Connections Passive")]
 real32 ConnectionsPassive;
 [PropertyContext("Connection Failures")]
 real32 ConnectionFailures;
 [PropertyContext("Connections Reset")]
 real32 ConnectionsReset;
 [PropertyContext("Segments Received/sec")]
 real32 SegmentsReceived;
 [PropertyContext("Segments Sent/sec")]
 real32 SegmentsSent;
 [PropertyContext("Segments Retransmitted/sec")]
 real32 SegmentsRetransmitted;
};

There are two things to notice here: first, the class is marked with the Singleton qualifier; second, there is no key property because singletons are not required to have a unique identity. Therefore, the code to retrieve the TCP performance data can be simplified as follows:

using System;
using System.Management;

class TCPMonitor {
 public static void Main(string[] args) {
 while(true) {
 ManagementObject mo = new ManagementObject("PerfMon_TCP=@");
 Console.WriteLine("Connections Established: {0}",
 mo["ConnectionsEstablished"]);
 Console.WriteLine("Connections Active: {0}", mo["ConnectionsActive"]);
 Console.WriteLine("Connections Passive: {0}", mo["ConnectionsPassive"]);
 System.Threading.Thread.Sleep(5000);
 }
 }
}

Using the Performance Monitoring Provider as a Property Provider

An interesting alternative to the instance-centric approach, outlined above, is using the Performance Monitoring provider as a property provider. Just as it is the case with the instance provider, to make the property provider work, you have to define the WMI classes to represent the performance data. Additionally, instances of these classes also have to be defined manually, since the property provider is not capable of returning the instance-level data to its clients. Consider the following sample MOF code:

class PerfMonProp_TCP {
 [Key]
 string KeyProp;
 real32 ConnectionsEstablished;
 real32 ConnectionsActive;
 real32 ConnectionsPassive;
};
[DYNPROPS]
instance of PerfMonProp_TCP {
 KeyProp = "tcp1";
 [PropertyContext("local|TCP|Connections Established"),
 Dynamic, Provider("PerformanceMonitorPropProvider")]
 ConnectionsEstablished;
 [PropertyContext("local|TCP|Connections Active"),
 Dynamic, Provider("PerformanceMonitorPropProvider")]
 ConnectionsActive;
 [PropertyContext("local|TCP|Connections Passive"),
 Dynamic, Provider("PerformanceMonitorPropProvider")]
 ConnectionsPassive;
};

The class definition for PerfMonProp_TCP is fairly straightforward and does not include any special qualifiers. The class has a key property KeyProp, although it is not, strictly speaking, necessary. Since properties of this class refer to global performance counters, the class may as well be marked as a singleton.

The instance definition is a bit more interesting. First, the instance is decorated with the DYNPROPS qualifier, which simply indicates that the instance is to house the values, backed by a property provider. The KeyProp property is initialized to an arbitrary value to guarantee the uniqueness of the instance identity. Finally, each of the instance's properties is marked with the already familiar PropertyContext qualifier. However, rather then simply referring to the display names of the associated performance counters, these PropertyContext qualifiers include the machine and performance object names as part of their initialization strings. This information is necessary in order for the property provider to bind to a correct performance object for each property. Additionally, each property is decorated with the Dynamic qualifier to indicate that a property provider backs it, and with the Provider qualifier, which refers to the proper provider registration entry.

Using the PerfMonProp_TCP class is no different than using the previously defined PerfMon_TCP class. There is, however, an interesting twist. Since the performance data is provided at the property level rather than the instance level, the WMI class definition no longer has to correspond to a single performance object. In fact, it is perfectly legitimate to define a class that combines the performance counters from multiple categories into a single package:

class PerfMonProp_Combo {
 [Key]
 string KeyProp;
 real32 ConnectionsEstablished;
 real32 AvailableBytes;
};
[DYNPROPS]
instance of PerfMonProp_Combo {
 KeyProp = "combo1";
 [PropertyContext("local|TCP|Connections Established"),
 Dynamic, Provider("PerformanceMonitorPropProvider")]
 ConnectionsEstablished;
 [PropertyContext("local|Memory|Available Bytes"),
 Dynamic, Provider("PerformanceMonitorPropProvider")]
 AvailableBytes;
};

This is certainly convenient because the management application is afforded the luxury of defining its single performance monitoring class, which includes all counters of interest. Unfortunately, there does not appear to be a way to retrieve nonglobal performance counters with the property provider. For instance, it is not possible to alter the definition of the WMI class above to refer to, say, the "% Processor Time" counter for CPU 0.

Usage Considerations

The Performance Monitoring provider often comes in very handy when you are reading the performance data. There are, however, a few drawbacks. First, WMI class definitions for performance objects have to be created by hand, which is not only tedious but also error-prone. The second and the most disappointing issue is the suboptimal performance of this provider, especially when you are retrieving large amounts of data on a regular basis. That is why Microsoft offers a couple of alternatives for retrieving the performance counter values.

The Performance Counter Provider

Perhaps, the most significant performance implication of using the Performance Monitoring provider is the necessity to marshal fairly large amounts of data. The Performance Monitoring provider is a conventional, old-fashioned WMI provider that is always loaded out-of-process respective to the client application, hence the need for marshalling the performance data across the process boundaries.

High-Performance Providers

To alleviate the performance problem often associated with conventional providers, Microsoft introduced a so-called high-performance provider API— a set of interfaces designed to increase the efficiency of communication between a client WMI application and a data provider. The basic idea behind the high-performance provider API is simple. As opposed to conventional providers, high-performance providers, if located on the same machine as the client application, are loaded in-process to the client, thus eliminating the need for cross-process data marshalling. Even when a client and a provider are located on different computers, the high-performance API is still a superior communication vehicle because it allows for caching the data on the remote machine and transmits only the minimal required data back to the client.

Because performance data providers need to handle vast amounts of frequently changing statistical data, choosing them to be implemented as high-performance providers is a good idea. Thus, starting with Windows 2000, the WMI distribution includes a high-performance alternative to the Performance Monitoring provider: the Performance Counter provider, also referred to as the Raw Performance Counter provider.

The Performance Counter Provider and the ADAP Process

Besides addressing performance issues, the Performance Counter provider eliminates the need to manually define the WMI classes that represent various performance objects. More precisely, it is not the provider itself, but rather the WMI AutoDiscovery/AutoPurge (ADAP) process that allows you to automatically import the definitions for Windows performance objects into the CIM Repository. The sole purpose of ADAP is to monitor the state of performance objects on a given computer and ensure that the definitions for these objects remain synchronized with their respective WMI performance class definitions.

On Windows 2000 and XP platforms, the ADAP process is launched automatically as soon as the WinMgmt service starts; unfortunately, neither the ADAP functionality nor the Performance Counter provider are available on any other Windows platforms. Upon startup, ADAP examines all currently installed performance DLLs and generates a list of all currently available performance objects. This list is then compared to a list of WMI performance classes and necessary adjustments are made.

Under Windows 2000 and XP, you can add custom performance objects and counters by implementing a performance DLL and registering it using lodctr.exe utility. Obviously, installing a new performance library is likely to render the WMI performance class definitions outdated or incomplete. That is why lodctr.exe and its counterpart unlodctr.exe, which is used to de-register the performance libraries, both invoke the ADAP process, thus making sure that WMI's CIM Repository remains synchronized with the actual performance objects.

ADAP can also be invoked manually via the /resyncperf command-line switch of winmgmt.exe:

winmgmt.exe /resyncperf 

On Windows 2000, ADAP is implemented as part of winmgmt.exe; however, under Windows XP, it is a separate executable called wmiadap.exe. Table 7-1 lists all command-line parameters taken by wmiadap.exe:

Table 7-1: wmiadap.exe Command-Line Options

COMMAND-LINE SWITCH

DESCRIPTION

/f

Instructs wmiadap.exe to parse all performance DLLs on a given system and synchronize the WMI performance class definitions

/c

Causes wmiadap.exe to clear the status of all currently available performance libraries

/r

Instructs wmiadap.exe to parse all the Windows Driver Model (WDM) drives on a given system and synchronize the WMI performance class definitions

/t

Throttles the ADAP process as soon as the user operates a keyboard or mouse

It is, therefore, possible to invoke the ADAP process manually as follows:

wmiadap.exe /f

The ADAP process maintains an error log, which can be found in %SystemRoot%System32WbemLogsWmiadap.log. The log is used to record various events triggered during the execution of the ADAP process, such as indications of the process's startup and shutdown as well as certain error conditions. In addition to the log file, all errors related to loading and parsing the performance DLLs are recorded in the Windows event log and can be viewed with the Event Viewer (eventvwr.exe). ADAP error messages can be found in the application log—you can filter errors based on the value of the Source column, which should be set to WinMgmt for ADAP messages. The application log messages are worth looking at because, on occasion, they may explain why certain performance counters are not being correctly transferred to the CIM Repository.

Finally, WMI system class, __ADAPStatus, located in the rootDEFAULT namespace, offers a way to programmatically determine the status of the ADAP process. This class has the following definition:

class __ADAPStatus : __SystemClass {
 uint32 Status;
 datetime LastStartTime;
 datetime LastStopTime;
};

Its LastStartTime and LastStopTime properties are self-explanatory and reflect the last time the ADAP process was started and shut down respectively. The Status property is a bit more interesting—it is an integer value that defines the current state of the ADAP process. The allowable values for this property are shown in Table 7-2:

Table 7-2: __ADAPStatus.Status Constants

VALUE

DESCRIPTION

0

The ADAP process has never been run on the computer.

1

The ADAP process is currently running.

2

The ADAP process is currently processing a performance library.

3

The ADAP process is currently updating the CIM Repository.

4

The ADAP process has finished.

To programmatically determine the status of the ADAP process, you can write code similar to the following:

using System;
using System.Management;

class ADAPStatus {
 public static void Main(string[] args) {
 ManagementObject mo =
 new ManagementObject(@"\.
ootDEFAULT:__ADAPStatus=@");
 Console.WriteLine("Status: {0}", mo["Status"]);
 Console.WriteLine("Last started: {0}", mo["LastStartTime"]);
 Console.WriteLine("Last stopped: {0}", mo["LastStopTime"]);
 }
}

The only thing to notice here is that the __ADAPStatus object is singleton and, therefore, has to be accessed using the @ symbol.

When the ADAP process successfully finishes, the WMI CIM Repository will be updated with the definitions of WMI classes that represent the performance objects. All such WMI classes will be derived from either Win32_PerfRawData or Win32_PerfFormattedData—preinstalled abstract classes that are used as superclasses for raw and "cooked" performance objects. The majority of the performance classes will be imported into the rootCIMV2 namespace; however, those classes that represent the performance objects for WDM drivers will be loaded into the rootWMI namespace, instead.

The remainder of this section will concentrate on non-WDM performance objects that are backed by the Raw Performance Counter provider and derived from the Win32_PerfRawData class. The following section will cover the Cooked Counter provider, which supports the classes, derived from Win32_PerfFormattedData.

Understanding the Performance Counter Provider

To better understand how the Performance Counter provider works, take a close look at one of the performance classes imported into the CIM Repository by the ADAP process. The Win32_PerfRawData_PerfOS_Processor class relates to the Processor Performance category and each of its instances corresponds to one of the Processor performance objects:

[perfdefault, dynamic, provider("Nt5_GenericPerfProvider_V1"),
 registrykey("PerfOS"), perfindex(238), helpindex(239),
 perfdetail(100), genericperfctr]
class Win32_PerfRawData_PerfOS_Processor : Win32_PerfRawData {
 [key] string Name = NULL;
 [perfdefault, DisplayName("% Processor Time"),
 countertype(558957824), perfindex(6), helpindex(7),
 defaultscale(0), perfdetail(100)]
 uint64 PercentProcessorTime;
 [DisplayName("% User Time"), countertype(542180608),
 perfindex(142), helpindex(143),
 defaultscale(0), perfdetail(200)]
 uint64 PercentUserTime;
 [DisplayName("% Privileged Time"), countertype(542180608),
 perfindex(144), helpindex(145),
 defaultscale(0), perfdetail(200)]
 uint64 PercentPrivilegedTime;
 [DisplayName("Interrupts/sec"), countertype(272696320),
 perfindex(148), helpindex(149), defaultscale(-2),
 perfdetail(100)]
 uint32 InterruptsPersec;
 [DisplayName("% DPC Time"), countertype(542180608),
 perfindex(696), helpindex(339),
 defaultscale(0), perfdetail(200)]
 uint64 PercentDPCTime;
 [DisplayName("% Interrupt Time"), countertype(542180608),
 perfindex(698), helpindex(397),
 defaultscale(0), perfdetail(200)]
 uint64 PercentInterruptTime;
 [DisplayName("DPCs Queued/sec"), countertype(272696320),
 perfindex(1334), helpindex(1335),
 defaultscale(0), perfdetail(200)]
 uint32 DPCsQueuedPersec;
 [DisplayName("DPC Rate"), countertype(65536),
 perfindex(1336), helpindex(1337),
 defaultscale(0), perfdetail(200)]
 uint32 DPCRate;
 [DisplayName("DPC Bypasses/sec"), countertype(272696320),
 perfindex(1338), helpindex(1339),
 defaultscale(0), perfdetail(200)]
 uint32 DPCBypassesPersec;
 [DisplayName("APC Bypasses/sec"), countertype(272696320),
 perfindex(1340), helpindex(1341),
 defaultscale(0), perfdetail(200)]
 uint32 APCBypassesPersec;
};

At first glance, the definition for this class seems to be very similar to that of the PerfMon_Processor class used earlier in this chapter to illustrate the operations of the Performance Monitoring provider. Indeed, the concept is the same—the class itself corresponds to the Process Performance category, while each of its properties relates to a single performance counter within the category. Yet another similarity is a key property, Name, which is set to the unique ID of the Processor instance by the provider.

However, this class, as well as all of its properties, is decorated with various qualifiers that are different from those of the PerfMon_Processor class. These qualifiers are required by the Performance Counter provider in order to locate the respective performance objects and associate them with the properties of the WMI class. The list of class qualifiers along with their descriptions is presented in Table 7-3.

Table 7-3: Class Qualifiers Used by the Performance Counter Provider

QUALIFIER

DESCRIPTION

Costly

If set, indicates that retrieving instances of this class is an expensive operation in terms of resource consumption. ADAP automatically adds this qualifier to a WMI class definition for a performance object that is marked as costly (see the Platform SDK documentation for more information on creating Performance Extension DLLs). Additionally, ADAP adds the _Costly suffix to the name of the WMI class. Example:

Win32_PerfRawData_PerfProc_ThreadDetails_Costly.

GenericPerfCounter

If set, indicates that the WMI class corresponds to a performance object backed by a legacy performance DLL.

HelpIndex

Specifies the index of the object's help text in the performance names database.

HiPerf

Marks the WMI class as a high-performance class.

PerfDefault

If set, indicates that the WMI class is to be used as a default selection in a list box in a graphical application.

PerfDetail

Indicates the level of knowledge the audience is required to possess. This qualifier can take the following values:

PERF_DETAIL_NOVICE (0x00000064)—For novice users

PERF_DETAIL_ADVANCED (0x000000C8) —For advanced users

PERF_DETAIL_EXPERT (0x0000012C) —For expert users

PERF_DETAIL_WIZARD (0x00000190) —For system designers

PerfIndex

Specifies the index of the object's display name in the performance names database.

RegistryKey

Specifies the name of the registry subkey under HKLMSystemCurrentControlSetServices under which the performance counter definitions are located.

Besides the qualifiers specific to the Performance Counter provider the class is marked with the Dynamic qualifier, indicating that its instances are supplied dynamically by the provider; as well as by the Provider qualifier, which establishes the link to the appropriate provider registration entry.

The actual binding between the WMI class and the respective performance counters is achieved through the property qualifiers that ADAP adds to the class definition. Table 7-4 lists all the property qualifiers specific to the Performance Counter provider.

Table 7-4: Property Qualifiers Used by the Performance Counter Provider

QUALIFIER

DESCRIPTION

CounterType

Indicates the type of performance counter associated with a given property. Since the Performance Counter provider exposes raw, uncalculated performance data, the CounterType qualifier is used by WMI clients to determine the formula for calculating the performance values that are meaningful to the end users. A detailed description of the available counter types is presented later in this section.

DefaultScale

The scale value used by graphical display applications such as System Performance Monitor to scale the performance chart lines.

DisplayName

Although this qualifier is not really specific to the Performance Counter provider, here it plays dual role. In addition to specifying the UI display name for the property, it is used to establish an association between a performance counter object and a property of its respective WMI class.

HelpIndex

Specifies the index of the object's help text in the performance names database.

PerfDefault

If set, indicates that WMI class property is to be used as a default selection in a list box in a graphical application.

PerfDetail

Indicates the level of knowledge the audience is required to possess. See Table 7-3 for the complete list of allowed values.

PerfIndex

Specifies the index of the object's display name in the performance names database.

Most of these qualifiers are fairly self-explanatory. CounterType, however, is a key to deciphering the performance data exposed by the Performance Counter provider, and so it is a bit more complicated. As I already mentioned, this provider supplies uncalculated data, which, in its raw form, does not make much sense to the end users. For instance, the Interrupts/sec performance counter has the type of PERF_COUNTER_COUNTER (with a CounterType of 272696320), which means that at any given time, it will hold a value that reflects the total rate of interrupts for a processor. Thus, to derive the interrupt rate per second, you would have to take snapshots of the counter value over some time interval and perform the calculation based on the following formula:

Interrupts/sec = (V1 - V0)/((T1 - T0)/TB)

where

  • V1: Counter value at the end of the monitoring interval
  • V0: Counter value at the beginning of the monitoring interval
  • T1: Number of processor ticks at the end of the monitoring interval
  • T0: Number of processor ticks at the beginning of the monitoring interval
  • TB: Time base or frequency of the processor ticks

In this case, the expression (V1 – V0) evaluates to the number of interrupts that take place within a certain monitoring interval. The other expression, (T1 – T0), reflects the length of the monitoring interval in units of processor ticks. Considering that tick frequency is expressed in numbers of ticks per second; ((T1 – T0)/TB) evaluates to the number of seconds elapsed during the monitoring interval. Thus, the entire calculation yields a value that reflects an average number of processor interrupts per second.

There are many different counter types that govern the calculations of the performance values. Based on the counter usage pattern and required computation algorithm, all counter types can be divided into the following categories:

  • Noncomputational counters
  • Base counters
  • Basic Algorithm counters
  • Counter Algorithm counters
  • Timer Algorithm counters
  • Precision Timer Algorithm counters
  • Queue-Length Algorithm counters
  • Statistical counters

Each of these categories will now be described in detail.

Noncomputational Counters

The noncomputational counters are the easiest to understand because their values can be used "as is" without applying any computational algorithms. Table 7-5 lists all noncomputational counter types along with their descriptions.

Table 7-5: Noncomputational Performance Counter Types

COUNTER

DESCRIPTION

PERF_COUNTER_TEXT (2816)

Variable-length Unicode text string. This counter does not require any calculations.

PERF_COUNTER_RAWCOUNT (65536)

Raw counter that represents the last observed value. This counter does not require any calculations.

PERF_COUNTER_LARGE_RAWCOUNT (65792)

Similar to PERF_COUNTER_RAWCOUNT, but uses 64-bit representation, which allows very large values to be stored.

PERF_COUNTER_RAWCOUNT_HEX (0)

Raw counter that represents the last observed value in HEX format. This counter does not require any calculations.

PERF_COUNTER_LARGE_RAWCOUNT_HEX (256)

Similar to PERF_COUNTER_RAWCOUNT_HEX, but uses 64-bit representation, which allows very large values to be stored.

Base Counters

The base counter types are not intended to be used by themselves. Instead, such counters supply base values to act as denominators in the formulas used to calculate the performance statistics for other counter types. For instance, the AvgDiskBytesPerRead property of the Win32_PerfRawData_PerfDisk_PhysicalDisk class contains the total number of bytes transferred from the disk during the read operation. Unless there is a basis for determining the number of read operations that take place over a time interval, this property is not very useful. Such a basis is provided by another property of the same class, AvgDiskBytesPerRead_Base, which reflects the total number of read operations so that the following formula can be applied to calculate the meaningful counter value:

Avg. Bytes/read = (V1 - V0)/(B1 - B0)

where

  • V1: Value of AvgDiskBytesPerRead at the end of the monitoring interval
  • V0: Value of AvgDiskBytesPerRead at the beginning of the monitoring interval
  • B1: Value of AvgDiskBytesPerRead_Base at the end of the monitoring interval
  • B0: Value of AvgDiskBytesPerRead_Base at the beginning of the monitoring interval

The base counter types are represented by the WMI class properties with the _Base suffix. Declarations of such properties must immediately follow the corresponding counter properties declarations within the WMI class definition.

Table 7-6 lists all the available base counter types along with their descriptions.

Table 7-6: Base Performance Counter Types

COUNTER

DESCRIPTION

PERF_AVERAGE_BASE (1073939458)

Represents the base value used to calculate cooked values for the PERF_AVERAGE_TIMER and PERF_AVERAGE_BULK counter types

PERF_COUNTER_MUTI_BASE (1107494144)

Represents the base value used to calculate cooked values for PERF_COUNTER_MULTI_TIMER, PERF_COUNTER_MULTI_TIMER_INV, PERF_100NSEC_MULTI_TIMER and PERF_100NSEC_MULTI_TIMER_INV counter types

PERF_LARGE_RAW_BASE (1073939715)

Represents the base value used to calculate cooked values for 64-bit PERF_RAW_FRACTION counter types

PERF_RAW_BASE (1073939459)

Represents the base value used to calculate cooked values for PERF_RAW_FRACTION counter types

PERF_SAMPLE_BASE (1073939457)

Represents the base value used to calculate cooked values for PERF_SAMPLE_COUNTER and PERF_SAMPLE_FRACTION counter types.

Basic Algorithm Counters

The basic algorithm counter types typically represent either absolute or relative (percentage) change in a measured performance value over a time interval. For instance, the ElapsedTime property of the Win32_PerfRawData_PerfProc_Thread class reflects the total number of clock ticks that correspond to the start time of the tread. Thus, to calculate the time elapsed from the moment the thread is started to a given point, you can use the following formula:

Elapsed Time = (T0 - V0)/TB

where

  • V0: Current counter value
  • T0: Current number of clock ticks
  • TB: Time base or number of clock ticks per second

The basic algorithm counter types employ different formulas to produce cooked counter values, and it is uncommon to see the computational algorithms that utilize the base properties. Table 7-7 lists all the available basic algorithm counter types along with their descriptions and associated computational formulas. The following is the legend for the formulas:

  • V0/V1: Counter values at the beginning/end of the monitoring interval
  • T0/T1: Time values (number of clock ticks) at the beginning/end of the monitoring interval
  • B0/B1: Base property values at the beginning/end of the monitoring interval
  • TB: Time base or number of clock ticks per second
Table 7-7: Basic Algorithm Counter Types

COUNTER

DESCRIPTION

FORMULA

PERF_RAW_FRACTION (537003008)

Current percentage value calculated as a ratio of a subset of values to its set. Such a counter usually contains an instantaneous value to be divided by the value of the corresponding base counter (PERF_RAW_BASE).

V0/B0 * 100

PERF_SAMPLE_FRACTION (549585920)

Similar to PERF_RAW_FRACTION, but represents an average percentage value calculated over a period of time. This counter also requires a base counter of type PERF_RAW_BASE.

(V1-V0) / (B1-B0) * 100

PERF_COUNTER_DELTA (4195328)

Represents an absolute change in the value of the underlying performance counter measured over a period of time.

V1 - V0

PERF_COUNTER_LARGE_DELTA (4195584)

Same as PERF_COUNTER_DELTA, but uses 64-bit representation to hold very large values.

V1 - V0

PERF_ELAPSED_TIME (807666944)

Represents the total time elapsed between the start of the process and the current point in time.

(T0 - V0) / TB

Counter Algorithm Counters

The counter algorithm counter types are used to represent rate or average values calculated either for a sample or over a certain time interval. One example of a counter that falls into this category is Interrupt/sec of the Processor performance object discussed earlier in this section. Table 7-8 shows all available counter algorithm counter types along with their descriptions and associated computational formulas. The variables, used in these formulas adhere to the previous legend.

Table 7-8: Counter Algorithm Counter Types

COUNTER

DESCRIPTION

FORMULA

PERF_AVERAGE_BULK (1073874176)

Average number of items processed per operation. This value is calculated as a ratio of items processed to the total number of completed operations. Example: the AvgDiskBytesPerTransfer property of the Win32_PerfRawData_PerfDisk_ PhysicalDisk class. The calculation requires the base property of type PERF_AVERAGE_BASE.

(V1 - V0) / (B1 - B0)

PERF_COUNTER_COUNTER (272696320)

Average number of completed operations per each second in the monitoring interval.

(V1 - V0) / ((T1 - T0) / TB)

PERF_SAMPLE_COUNTER (4260864)

Average number of operations completed in one second. The computation for this counter requires a base property of type PERF_SAMPLE_BASE.

(V1 - V0) / (B1 - B0)

PERF_COUNTER_BULK_COUNT (272696576)

Same as PERF_COUNTER_COUNTER but uses 64-bit representation to hold larger values.

(V1 - V0) / ((T1 - T0) / TB)

Timer Algorithm Counters

Timer algorithm counter types are used to represent time-based performance measures, such as the average time per operation or the ratio of active time vs. total time. An example of such a counter is an object represented by the PercentPrivilegedTime property of the Win32_PerfRawData_PerfOS_Processor class. This property reflects the percentage of time the processor spends in privileged mode, and it can be calculated using the following formula:

PercentagePrivilegedTime = ((V1-V0)/(T1-T0))*100

Interestingly, this counter is of type PERF_100NSEC_TIMER, which means that its value is expressed in units of 100 nanoseconds rather than in clock ticks, and the sampling interval is measured with 100-nanosecond precision.

Table 7-9 lists all the available timer algorithm counter types along with their descriptions and associated computational formulas. The variables used in the formulas adhere to the earlier legend.

Table 7-9: Timer Algorithm Counter Types

COUNTER

DESCRIPTION

FORMULA

PERF_COUNTER_TIMER (541132032)

Percentage of time an object is active.

((V1 – V0) / (T1 – T0)) * 100

PERF_COUNTER_TIMER_INV (557909248)

This counter type is the opposite of PERF_COUNTER_TIMER and expresses the percentage of time an object is inactive.

(1 – (V1 – V0)/(T1 – T0)) * 100

PERF_AVERAGE_TIMER (805438464)

Average time per operation. This counter types requires a base counter of type PERF_AVERAGE_BASE.

((V1 – V0) / TB) / (B1 – B0)

PERF_100NSEC_TIMER (542180608)

This counter is the same as PERF_COUNTER_TIMER, but it expresses the value in units of 100 nanoseconds rather than in clock ticks.

((V1 – V0) / (T1 – T0)) * 100

PERF_100NSEC_TIMER_INV (592512256)

This counter is the same as PERF_COUNTER_TIMER_INV, but it expresses the value in units of 100 nanoseconds rather than in clock ticks.

(1 – (V1 – V0) / (T1 – T0)) * 100

PERF_COUNTER_MULTI_TIMER (574686464)

Percentage of time an object (or multiple objects) is active. This counter requires a base counter of type PERF_COUNTER_MULTI_BASE.

((V1 – V0) / ((T1 – T0) / TB)) / B1 * 100

PERF_COUNTER_MULTI_TIMER_INV 591463680)

This counter type is the opposite of PERF_COUNTER_ MULTI_TIMER and expresses the percentage of time an object (or multiple objects) is inactive.

(((B1 – ((V1 – V0) / ((T1 – T0) / TB))) / B1) * 100

PERF_100NSEC_MULTI_TIMER (575735040)

This counter is the same as PERF_COUNTER_MULTI_TIMER, but it expresses the value in units of 100 nanoseconds rather than in clock ticks.

((V1 – V0) / ((T1 – T0) / TB)) / B1 * 100

PERF_100NSEC_MULTI_TIMER_INV (592512256)

This counter is the same as PERF_COUNTER_MULTI_TIMER_INV, but it expresses the value in units of 100 nanoseconds rather than in clock ticks.

((B1 – ((V1 – V0) / ((T1 – 0) / TB))) / B1) * 100

Precision Algorithm Counters

The purpose of the precision algorithm counter types is to obtain the performance measurements that are more precise than those provided by the regular counter types. When you collect the performance data using regular counter types, there is an inherent error due to a small variable delay between the point when the value of the system clock is retrieved and the point when the performance sample is read. Thus, to minimize or eliminate this delay, the precision algorithm counter types employ a base counter of type PERF_PRECISION_TIMESTAMP, which is essentially a high-precision timestamp that should be used instead of the system clock. Therefore, a property of a WMI class that corresponds to a precision counter type must be immediately followed by a base property that refers to a PERF_PRECISION_TIMESTAMP counter, as shown here:

class Win32_PerfRawData_PerfDisk_PhysicalDisk : Win32_PerfRawData {
 ...
 [countertype(542573824)...] // PERF_PRECISION_100NS_TIMER
 uint64 PercentIdleTime;
 [countertype(1073939712)...] // PERF_PRECISION_TIMESTAMP
 uint64 PercentIdleTime_Base;
 ...
};

Table 7-10 lists all available precision algorithm counter types along with their descriptions and associated computational formulas. The variables used in the formulas adhere to the legend shown earlier.

Table 7-10: Precision Algorithm Counter Types

COUNTER

DESCRIPTION

FORMULA

PERF_PRECISION_SYSTEM_TIMER (541525248)

This counter is the same as PERF_COUNTER_TIMER, but it uses the base counter for computations rather than the system clock.

((V1 – V0) / (T1 – T0)) * 100

PERF_PRECISION_100NS_TIMER (542573824)

This counter is the same as PERF_PRECISION_SYSTEM_TIMER, but it uses the 100-nanosecond precision base counter.

((V1 – V0) / (T1 – T0)) * 100

Queue-Length Algorithm Counters

Queue-length algorithm counter types typically represent an average number of items on a queue that is calculated over a time interval. An example of such a counter is the Average Disk Queue Length of a physical disk object, which is represented by the AvgDiskQueueLength property of Win32_PerfRawData_PerfDisk_PhysicalDisk. At any given time the raw value of this counter reflects the total number of items on the queue; thus, to produce the calculated measure, you can use the following formula:

AvgDiskQueueLength = (V1 - V0)/(T1 - T0)

Note, this counter is of type PERF_COUNTER_100NS_QUEUELEN_TYPE, so the monitoring interval has to be measured with 100-nanosecond precision.

Table 7-11 lists all the available queue-length algorithm counter types along with their descriptions and associated computational formulas. The variables, used in the formulas adhere to the legend shown earlier.

Table 7-11: Queue-Length Algorithms Counter Types

COUNTER

DESCRIPTION

FORMULA

PERF_COUNTER_QUEUELEN_TYPE (4523008)

Average length of a queue for a resource calculated over a certain time interval.

(V1 – V0) / (T1 – T0)

PERF_COUNTER_LARGE_QUEUELEN_TYPE (4523264)

This counter is the same as PERF_COUNTER_QUEUELEN_TYPE, but it uses 64-bit representation to hold larger values.

(V1 – V0) / (T1 – T0)

PERF_COUNTER_100NS_QUEUELEN_TYPE (5571840)

This counter is the same as PERF_COUNTER_QUEUELEN_TYPE, but it requires the monitoring interval to be measured with 100-nanosecond precision.

(V1 – V0) / (T1 – T0)

PERF_COUNTER_OBJECT_TIME_QUEUELEN_TYPE (6620416)

This counter is the same as PERF_COUNTER_QUEUELEN_TYPE, but it uses an object-specific time base rather than the system clock.

(V1 – V0) / (T1 – T0)

Statistical Counters

Finally, the last and perhaps the most complex category of performance counter types are the statistical counter types. These counter types are not available through the WMI classes supported by the Performance Counter provider. Instead, you must use the Cooked Counter provider, described in the next section, in order to obtain the statistical performance measures. In fact, these counters are only accessible through the Cooked Counter provider and do not exist outside of WMI. Thus, it is impossible to retrieve this kind of data using conventional performance interfaces, such as the Performance Data Helper (PDH) library.

Table 7-12 lists all the available statistical counter types along with their descriptions.

Table 7-12: Statistical Counter Types

COUNTER

DESCRIPTION

COOKER_AVERAGE

Provides the statistical average of the data for a particular counter. The cooked value is calculated by repeatedly sampling the underlying raw value, summing up the results, and dividing them by the number of samples taken.

COOKER_MIN

Represents the smallest value from a set of observations of a single raw counter value.

COOKER_MAX

Represents the largest value from a set of observations of a single raw counter value.

COOKER_RANGE

Expresses the difference between the minimum and the maximum values from a set of observations of a single raw counter value.

COOKER_VARIANCE

Expresses the variability that can be indicative of dispersion for a set of observations of a single raw counter value. The cooked value is calculated by averaging the squared deviations from the mean for each sample taken.

Identifying the Counter Type

One pretty annoying thing about working with WMI raw performance classes is that you have to lookup the counter types based on the decimal value of the CounterType property qualifier. In other words, by looking at the MOF definition for a given class, you cannot deduce the types of counters to which the properties refer, unless, of course, you memorize the decimal values that correspond to each counter type. Life can be made a lot easier with help of a simple program that translates the numeric CounterType qualifier values into the human-readable counter type constants:

using System;
using System.Management;

enum PerfCounterType : ulong
{
 PERF_COUNTER_RAWCOUNT_HEX = 0,
 PERF_COUNTER_LARGE_RAWCOUNT_HEX = 256,
 PERF_COUNTER_TEXT = 2816,
 PERF_COUNTER_RAWCOUNT = 65536,
 PERF_COUNTER_LARGE_RAWCOUNT = 65792,
 PERF_DOUBLE_RAW = 73728,
 PERF_COUNTER_DELTA = 4195328,
 PERF_COUNTER_LARGE_DELTA = 4195584,
 PERF_SAMPLE_COUNTER = 4260864,
 PERF_COUNTER_QUEUELEN_TYPE = 4523008,
 PERF_COUNTER_LARGE_QUEUELEN_TYPE = 4523264,
 PERF_COUNTER_100NS_QUEUELEN_TYPE = 5571840,
 PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE = 6620416,
 PERF_COUNTER_COUNTER = 272696320,
 PERF_COUNTER_BULK_COUNT = 272696576,
 PERF_RAW_FRACTION = 537003008,
 PERF_COUNTER_TIMER = 541132032,
 PERF_PRECISION_SYSTEM_TIMER = 541525248,
 PERF_100NSEC_TIMER = 542180608,
 PERF_PRECISION_100NS_TIMER = 542573824,
 PERF_OBJ_TIME_TIMER = 543229184,
 PERF_PRECISION_OBJECT_TIMER = 543622400,
 PERF_SAMPLE_FRACTION = 549585920,
 PERF_COUNTER_TIMER_INV = 557909248,
 PERF_100NSEC_TIMER_INV = 558957824,
 PERF_COUNTER_MULTI_TIMER = 574686464,
 PERF_100NSEC_MULTI_TIMER = 575735040,
 PERF_COUNTER_MULTI_TIMER_INV = 591463680,
 PERF_100NSEC_MULTI_TIMER_INV = 592512256,
 PERF_AVERAGE_TIMER = 805438464,
 PERF_ELAPSED_TIME = 807666944,
 PERF_COUNTER_NODATA = 1073742336,
 PERF_AVERAGE_BULK = 1073874176,
 PERF_SAMPLE_BASE = 1073939457,
 PERF_AVERAGE_BASE = 1073939458,
 PERF_RAW_BASE = 1073939459,
 PERF_PRECISION_TIMESTAMP = 1073939712,
 PERF_LARGE_RAW_BASE = 1073939715,
 PERF_COUNTER_MULTI_BASE = 1107494144,
 PERF_COUNTER_HISTOGRAM_TYPE = 2147483648
};
class PerfCounters {
public static void Main(string[] args) {
 ManagementClass c = new ManagementClass("Win32_PerfRawData");
 foreach(ManagementClass mc in c.GetSubclasses()) {
 Console.WriteLine("Class: {0}", mc["__CLASS"]);
 foreach(PropertyData pd in mc.Properties) {
 try {
 PerfCounterType ct = (PerfCounterType)ulong.Parse(
 mc.GetPropertyQualifierValue(pd.Name, "CounterType").ToString());
 Console.WriteLine(" {0} : {1}", pd.Name, ct);
 } catch (Exception e) {}
 }
 Console.WriteLine();
 }
}
}

The key idea behind this code involves using the enumeration PerfCounterType to translate the decimal counter type values into meaningful identifiers. As you may remember, when printing the members of an enumeration, the WriteLine method outputs a label rather than the underlying numeric value. Thus, by defining the PerfCounterType enumeration so that each of its members has an underlying value that corresponds to a single counter type value, you can simply cast the numeric value of the CounterType qualifier to an enumeration variable and print it out, letting the WriteLine method handle the conversion. Note that counter types are usually large decimal numbers; therefore, the PerfCounterType has to have an underlying type that is large enough to accommodate such values. The default underlying type is int, which is not sufficient to hold most of the counter type values; that is why the enumeration is declared with an underlying type of long.

Yet another trick is enumerating all WMI raw performance classes. Luckily, every class backed by the Performance Counter provider originates from the same ancestor: Win32_PerfRawData. As a result, the task of obtaining the performance classes can be reduced to binding to the Win32_PerfRawData class and enumerating all of its subclasses.

Finally, you may be asking, "What is the purpose of the try/catch block that surrounds the code that retrieves the value of the CounterType qualifier for each property and converts it to the associated member of the PerfCounterType enumeration?" The problem that the try/catch block is designed to solve is that certain properties of a performance class—its key, for instance—may not have the CounterType qualifier, in which case the GetPropertyQualifierValue method will throw an exception. For our purposes, such properties are of no interest and should be simply ignored, hence the empty try/catch construct in the code fragment above.

Curiously, you do not even have to define your own PerfCounterType enumeration. The System.Diagnostics namespace already contains an enumeration type, called PerformanceCounterType, which is equivalent to the PerfCounterType, shown earlier, and, therefore it can be used in the same way. The only difference is that rather than using the standard counter value constants referred to throughout the Platform SDK documentation, this enumeration introduces its own comprehensive labels. Thus, PERF_RAW_FRACTION is referred to as RawFraction, PERF_COUNTER_RAWCOUNT becomes NumberOfItems32, and so on.

Working with Raw Performance Classes

At first, raw performance classes may seem fairly incomprehensible and difficult to work with, simply due to the large amount of background information that you have to study in order to produce even a rudimentary monitoring program. However, once you develop a basic understanding of the principles that govern the collection of raw performance data, coding becomes fairly trivial. In order to put things in context, look at this simple example of monitoring the Interrupts/sec counter of the Processor object:

using System;
using System.Management;

class ProcessorMonitor {
public static void Main(string[] args) {
 DateTime t0 = DateTime.UtcNow;
 DateTime t1;
 ManagementObject mo =
 new ManagementObject("Win32_PerfRawData_PerfOS_Processor='0'");
 float v0 = float.Parse(mo["InterruptsPersec"].ToString());
 float v1 = 0;
 while(true) {
 System.Threading.Thread.Sleep(10000);
 t1 = DateTime.UtcNow;
 mo = new ManagementObject("Win32_PerfRawData_PerfOS_Processor='0'");
 v1 = float.Parse(mo["InterruptsPersec"].ToString());
 Console.WriteLine((v1-v0)/(t1-t0).Seconds);
 v0 = v1;
 t0 = t1;
 }
}
}

This code is really not complex. First, it allocated two sets of variables: two of type DateTime to hold the time measurements for the beginning and end of the monitoring interval; and two floats for recording the values of the InterruptsPersec property of Win32_PerfRawData_PerfOS_Processor also at the beginning and end of the monitoring interval. The variables that correspond to the starting time of the monitoring interval and counter value at that time are initialized appropriately. The time is read from the UtcNow property of the DateTime type, which returns the current time in Universal Time Coordinates (UTC) also known as Greenwich Mean Time (GMT). The value of the performance counter is obtained from the InterruptsPersec property of the Win32_PerfRawData_PerfOS_Processor object, which corresponds to CPU 0. Then an infinite while loop first suspends the execution of the current thread for the duration of the monitoring interval (10000 milliseconds), and when the thread returns from the Thread.Sleep method, it obtains the time and the counter value that correspond to the end of the interval.

The cooked counter value is then calculated as the difference between the counter values at the beginning and end of the interval divided by the interval duration in units of seconds ((V1 – V0)/(T1 – T0)) and printed on the system console. The duration of the interval is calculated using the subtraction operator (–) of DateTime type, which yields an object of type TimeSpan. To convert the TimeSpan object into an integer number of seconds, the code reads the value of its Seconds property. Finally, the code reinitializes the variables that correspond to the beginning of the monitoring interval to prepare for the next iteration of the while loop.

Despite its obvious simplicity, this code may raise a few questions. First, the formula used to calculate the cooked value of the performance counter seems to be a bit different. As you may remember, the original calculation, shown earlier, relies on recording the interval beginning and end time as a number of clock ticks and then dividing the difference between these by either the time base or clock frequency. The reality is that it does not necessarily have to be done this way. The DateTime objects records the time using a100-nanosecond precision clock. As a result, the TimeSpan object, which was obtained by subtracting the beginning time from the end time, houses the interval duration expressed in units of 100-nanosecond ticks. Thus, reading the value of the TimeSpan.Seconds property, which converts ticks to seconds, is equivalent to applying the time base to the calculation.

If the idea is to eventually calculate the duration of the interval in units of seconds, why bother recording the time? After all, the thread is suspended for 10 seconds (10000 milliseconds) using the Thread.Sleep method, so why not just divide the counter value difference by 10 and be done with it? Well, unfortunately, Thread.Sleep is imprecise and may not necessarily suspend the current thread for exactly 10 seconds. This may introduce a significant error into the calculation and skew the results.

The approach taken by the preceding code is fine and will produce fairly accurate results, especially for large monitoring intervals. However, there will always be some error, mainly due to the way the measurements are taken. The process of taking a performance measurement has two parts: recording the time, and sampling the performance counter value that corresponds to that point in time. Because there are two distinct steps involved, taking a performance measurement is not atomic as there is always a slight delay between the two operations. (Actually, this is one of the reasons why Microsoft introduced the precision algorithm counter types that rely on high-precision timestamps rather than the system clock.) Nevertheless, even when you are dealing with simple counters such as Interrupts/sec, there is a way to minimize an error that results from the nonatomic nature of the performance measurement operation.

As I already mentioned, all raw performance counter classes have a common superclass: Win32_PerfRawData. There is a reason for such design, besides just providing a convenient way to enumerate all performance classes. It turns out that Win32_PerfRawData has a few useful properties of its own that often provide a neat solution to some of the problems that may arise when you are taking the performance measurements. Take a look at the class definition:

class Win32_PerfRawData : Win32_Perf {
 string Name;
 string Caption;
 string Description;
 uint64 Timestamp_Object;
 uint32 Frequency_Object;
 uint64 Timestamp_PerfTime;
 uint64 Frequency_PerfTime;
 uint64 Timestamp_Sys100NS;
 uint64 Frequency_Sys100NS;
}

This class has the following properties:

  • Name: The name of the associated performance metric
  • Caption: A short description of the associated performance metric
  • Description: A long description of the associated performance metric
  • Timestamp_Object: An object-defined timestamp that contains a time measure expressed in units specific to a given performance object
  • Frequency_Object: A time base or frequency in ticks per second for the object-defined timestamp, Timestamp_Object
  • Timestamp_PerfTime: A timestamp expressed in units of clock ticks
  • Frequency_PerfTime: A time base or frequency in ticks per second for the timestamp, Timestamp_PerfTime
  • Timestamp_Sys100NS: A timestamp, expressed in units of 100-nanosecond clock ticks
  • Frequency_Sys100NS: A time base or frequency of the 100-nanosecond clock for the timestamp, Timestamp_Sys100NS

Two of the Win32_PerfRawData properties—Timestamp_PerfTime and Frequency_PerfTime—are exactly what you need to calculate the cooked value for the Interrupts/sec counter. Using the values of these properties not only alleviates the need for recording the time manually, but it also ensures the atomic nature of the operation. As you may remember, binding to a management object causes a copy of the object to be created locally, which means that all subsequent property access operations work against this local copy. Thus, once a bound instance of the ManagementObject type is created, it reflects the state of the management object at a time of creation so that the timestamp values correspond exactly to the respective counter values.

With this in mind, the code for monitoring the Interrupts/sec counter can be rewritten as follows:

using System;
using System.Management;

class ProcessorMonitor {
public static void Main(string[] args) {
 ulong t0, t1, tb;
 float v0, v1;
 ManagementObject mo =
 new ManagementObject("Win32_PerfRawData_PerfOS_Processor='0'");
 v0 = float.Parse(mo["InterruptsPersec"].ToString());
 t0 = ulong.Parse(mo["Timestamp_PerfTime"].ToString());
 tb = ulong.Parse(mo["Frequency_PerfTime"].ToString());
 while(true) {
 System.Threading.Thread.Sleep(5000);
 mo = new ManagementObject("Win32_PerfRawData_PerfOS_Processor='0'");
 v1 = float.Parse(mo["InterruptsPersec"].ToString());
 t1 = ulong.Parse(mo["Timestamp_PerfTime"].ToString());
 Console.WriteLine((v1-v0)/((t1-t0)/tb));
 v0 = v1;
 t0 = t1;
 }
}
}

This approach is not only simpler and more accurate, it is also elegant and definitely more self-contained because there is no reliance on external mechanisms for recording the time.

The Cooked Counter Provider

The Performance Counter provider is certainly a very good source of performance metrics; it affords its users a great deal of flexibility while ensuring adequate performance. There is, however, a price to pay. Using this provider is a bit tricky; a developer needs to fully understand the raw performance counter types and be proficient in applying the appropriate computations in order to derive meaningful "cooked" performance statistics. In order to shorten the learning curve and provide a way to quickly build simple performance monitoring tools, Microsoft introduced the Cooked Counter provider, which became the preferred source of performance metrics on Windows XP platforms.

Understanding the Cooked Counter Provider

The Cooked Counter provider is a high-performance provider that exposes the calculated or formatted performance statistics; as a result this provider eliminates the need to understand the differences between various counter types and to apply proper calculations to the raw data. In effect, you may think of this provider as a wrapper on top of the Performance Counter provider that encapsulates the algorithms necessary to produce the cooked performance metrics.

WMI classes, backed by the Cooked Counter provider, are created automatically and maintained by ADAP. These classes reside in the rootCIMV2 namespace and, just like the raw performance classes, have a common ancestor: Win32_PerfFormattedData. The definition for this class is identical to that of Win32_PerfRawData, which was shown earlier in this chapter. As a matter of fact, WMI classes that are exposed by the Cooked Counter provider are also very similar to their respective raw counter classes—typically, each "cooked" class would act as a wrapper for the corresponding "raw" class. For example, Win32_PerfFormattedData_PerfOS_Processor is a cooked alternative to the already familiar Win32_PerfRawData_PerfOS_Processor. What sets the cooked and raw class definitions apart is their qualifiers. Table 7-13 lists all the class qualifiers that are used for WMI classes that are backed by the Cooked Counter provider.

Table 7-13: Class Qualifiers for Cooked Performance Counter Classes

QUALIFIER

DESCRIPTION

AutoCook

This qualifier sets the cooking version of the class and essentially indicates that the provider will cook the performance data automatically based on the raw counter values. The current release of the Cooked Counter provider requires this qualifier be set to 1.

AutoCook_RawClass

Name of the underlying raw performance class used by the provider to derive the values for cooking.

AutoCook_RawDefault

This qualifier indicates that the underlying raw class has the following properties, often used in the performance measures computations: Timestamp_PerfTime, Timestamp_Sys100NS, Timestamp_Object, Frequency_PerfTime, Frequency_Sys100NS, and Frequency_Object. The current release of the Cooked Counter provider requires this qualifier be set to 1.

Base

For counters that require a base counter for computations, this qualifier indicates the name of the property of an underlying raw class that corresponds to the base performance counter.

Cooked

Boolean value that indicates whether the class contains cooked performance data.

Perf100NSTimeFreq

Name of the underlying raw class property to be used as a frequency of a 100-nanosecond clock in cooking computations.

Perf100NSTimeStamp

Name of the underlying raw class property to be used as a 100-nanosecond timestamp in cooking computations.

PerfObjTimeFreq

Name of the underlying raw class property to be used as an object frequency in cooking computations.

PerfObjTimeStamp

Name of the underlying raw class property to be used as an object timestamp in cooking computations.

PerfSysTimeFreq

Name of the underlying raw class property to be used as a frequency of a system clock in cooking computations.

PerfSysTimeStamp

Name of the underlying raw class property to be used as a system timestamp in cooking computations.

Just as is the case with raw performance classes, the properties of the cooked classes are decorated with provider-specific qualifiers as well. These qualifiers provide a "recipe" for "cooking" the raw data and obtaining the meaningful result. Table 7-14 lists all property qualifiers for the cooked performance classes.

Table 7-14: Property Qualifiers for Cooked Performance Classes

QUALIFIER

DESCRIPTION

Base

For counters that require a base counter for computations, this qualifier indicates the name of the property of an underlying raw class, which corresponds to the base performance counter.

CookingType

This qualifier identifies the formula required to produce a cooked result based on the value of the underlying raw counter. This qualifier uses the same set of values as the CounterType qualifier of the Raw Performance Counter provider. For a complete description of counter types, see Tables 7-5 through 7-12.

Counter

Name of the underlying raw class property to be used as a counter value in cooking computations.

PerfTimeFreq

Name of the underlying raw class property to be used as a clock frequency in cooking computations. If this qualifier is not present, the provider will use a default value, set by the appropriate class-level qualifier (see Table 7-13).

PerfTimeStamp

Name of the underlying raw class property to be used as a timestamp in cooking computations. If this qualifier is not present, the provider will use a default value, set by the appropriate class-level qualifier (see Table 7-13).

SampleWindow

Mainly used for statistical counter types to define a number of samples for calculating the final cooked value.

Using the Cooked Counter Provider

The best news, of course, is that an application developer does not have to know anything about these qualifiers. Unlike the Performance Counter provider, which requires the programmers to fully understand its class and property qualifiers in order to apply the proper computation to the raw values, the Cooked Counter provider does all the grunt work behind the scenes. Thus, implementing a monitoring utility based upon the cooked performance classes becomes a rather trivial coding exercise. To prove this, I will rewrite the monitor for the Interrupts/sec counter using the Win32_PerfFormattedData_PerfOS_Processor class:

using System;
using System.Management;

class ProcessorMonitor {
public static void Main(string[] args) {
 while(true) {
 System.Threading.Thread.Sleep(5000);
 mo = new ManagementObject("Win32_PerfFormattedData_PerfOS_Processor='0'");
 Console.WriteLine(mo["InterruptsPersec"]);
 }
}
}

As you can see, retrieving a performance metric basically comes down to reading the respective property of a cooked WMI object. This approach is far more advantageous than using the Performance Counter provider because it results in a more compact and simple code and leaves virtually no room for error. Unfortunately, the Cooked Counter provider is only available on Windows XP platforms, so the unfortunate users of older Windows systems are stuck with using either the slow Performance Monitoring provider or doing the computations using the raw performance data.


The SNMP Provider

The Simple Network Management Protocol (SNMP) was developed in the late 1980s in an attempt to satisfy rapidly growing demands for reliable management of heterogeneous networks. SNMP quickly gained popularity and remains the best explored management standard for TCP/IP-based internets.

The core of SNMP is the database housing the management data upon which the network management system operates. This database is commonly referred to as Management Information Base (MIB). SNMP MIB is a tree-like collection of objects, each representing a managed resource on a network. An SNMP-based network management system can monitor the state of these objects by reading their properties, and it can alter their states by modifying these properties. The organization of a MIB is governed by a standard called the Structure of Management Information (SMI), which outlines the rules for constructing and defining MIB management objects. Over the years, many different MIBs have been developed to address various aspects of network and system management; these include the Relational Database Monitoring MIB, the Mail Management MIB, and many others. However, MIB-II, which defines the second version of the management information base for TCP/IP-based internets, remains the most important and the most commonly used MIB specification. MIB-II defines the following broad groups of management information:

  • System: Includes general information about the networked system such as its identification information, location, and uptime
  • Interfaces: Houses information that describes each of the system's network interfaces
  • AT: Contains information pertinent to the operations of an address translation (AT) protocol—essentially the contents of the address translation table
  • IP: Contains information pertinent to the operations of the Internet Protocol (IP) on a given system
  • ICMP: Houses information pertinent to the operations of the Internet Control Message Protocol (ICMP) on a given system
  • TCP: Contains information pertinent to the operations of the Transmission Control Protocol (TCP) on a given system
  • UDP: Contains information pertinent to the operations of User Datagram Protocol (UDP) on a given system
  • EGP: Houses information pertinent to the operations of Exterior Gateway Protocol (EGP) on a given system
  • DOT3: Contains information pertinent to the transmission schemes and access protocols at each system interface
  • SNMP: Includes information pertinent to the operations of the Simple Network Management Protocol (SNMP) on a given system

Due to its widespread popularity and significant install base, SNMP is still a valuable source of management information, despite its fairly narrow specialization and some inherent inflexibility. As a result, when you are building a distributed enterprise management system, the ability to integrate the existing SNMP installations into the management model may be instrumental in determining your success.

WMI SNMP Provider

In a perfect world, all managed systems talk the same language or, in other words, expose the information for management in a way that complies with a common standard such as WBEM. In the real world, however, the situation is different. Thus, a typical managed environment is likely to include a number of computers that do not understand WBEM and, at best, run some legacy management system such as SNMP. My world, for instance, consists of five Sun Microsystems SPARCs running Solaris 8, two Linux machines, six Compaq servers running Windows 2000, and countless desktops with an odd mix of Windows NT, Windows 2000, and Windows XP.

Managing the Windows boxes in a centralized fashion does not seem to be a problem because they all support WMI. UNIX machines, on the other hand, are more troublesome. This does not mean that the UNIX/Linux community has been ignoring the WBEM standard altogether—in fact, a few UNIX vendors already provide WBEM-compliant management tools, such as Solaris WBEM SDK. Moreover, the latest Common Information Model (CIM) Specification version 2.6 is specifically concerned with the issue of managing UNIX platforms. The new model includes a number of elements that map to the Open Group's Single UNIX Specification and address various management aspects of UNIX systems, such as modeling of processes, threads, and filesystems. Thus, at least in theory, it should be possible to outfit the UNIX machines with WBEM capabilities, which would allow for administering them from a single centralized management console.

Unfortunately, this is where the reality kicks in. First, most UNIX WBEM implementations are nowhere near as sophisticated as WMI and do not seem to support some of its essential functionality (WQL queries, for instance). But most importantly, there does not seem to be a good way of integrating, say, the Solaris WBEM package with WMI, due to radical differences in their APIs and interfaces. To nobody's surprise, Solaris's WBEM is Java-based, and therefore, it does not lend itself easily to being integrated into a COM-centric WMI model. Perhaps in the future when all WBEM implementations are equipped with support for platform-neutral communication protocols such as the Simple Object Access Protocol (SOAP), the integration issue will go away, but the technology is not there yet.

The good news is that all UNIX flavors support SNMP. In fact, there is an SNMP implementation for just about every conceivable computing platform in existence today. Although not a match for WBEM in terms of versatility and flexibility, SNMP has a number of advantages. First, it is an old and well-understood standard, so most implementations are similar when it comes to the structure of management information and supported functionality. Second, it conveys the management information using a set of well-defined UDP-based messages, meaning that cross-platform communications are not a problem. Thus, if integrated with WMI, SNMP may be an acceptable, although not perfect, solution to the problem of managing dissimilar computing nodes in a centralized fashion.

The integration of SNMP with WMI is achieved via the SNMP provider, which essentially acts as a proxy, translating the WMI requests into SNMP messages and then converting the SNMP responses into a form that is consumable by WMI. In fact, there are several SNMP providers:

  • Class provider: Responsible for dynamically providing WMI class definitions that correspond to SNMP MIB objects.
  • Instance provider: Responsible for providing access to SNMP management data.
  • Event providers: Responsible for generating WMI events from SNMP traps and notifications. There are two event providers that generate WMI events in two different formats: encapsulated and referent. These formats are described in detail in the "Receiving SNMP Traps" section later in this chapter.

Mapping SNMP Objects to WMI

The most important task the providers mentioned in the previous section are required to perform is mapping the SNMP data to WMI classes and objects. So that you can understand how this is accomplished, I will first explain how SNMP MIBs are structured.

The notation used to define SNMP objects, is known as Abstract Syntax Notation One (ASN.1). This is a declarative language that was developed and standardized by the Telecommunication Standardization Sector (CCITT) of the International Telecommunications Union (ITU) and the International Standards Organization (ISO). ASN.1 provides a set of predefined primitive types, such as INTEGER or BOOLEAN and compound data types, such as SET or SEQUENCE; and it offers syntax for declaring new types, which are derived from existing types. Additionally, there is a provision for defining macros—templates that can define sets of related types. A macro definition outlines a set of legitimate macro instances and specifies the traits of a set of related types. In essence, a macro definition can be viewed as a meta-type, which defines characteristics of a type. A macro instance, produced by supplying proper arguments for the parameters of the macro, is a specification for a concrete SNMP type. Finally, a macro instance value represents a specific SNMP entity or object.

There are few SNMP macros used as a basis for mapping the SNMP data to WMI:

  • OBJECT-TYPE: Used to describe the traits of an SNMP object
  • TEXTUAL-CONVENTION: Used to enhance the readability of the MIB source and specify additional type semantics
  • TRAP-TYPE: Used to describe the characteristics of an SNMP trap or event in SNMPv1
  • NOTIFICATION-TYPE: Used to describe the characteristics of an SNMP trap or event in SNMPv2

When building SNMP MIBs, the only way to structure the data is by defining simple, two-dimensional tables that contain entries with scalar values. Thus, it is possible to draw a parallel between an SNMP table and a WMI class so that individual table entries correspond to specific WMI objects. For instance, the TCP Connection table, which houses the information about all TCP connections for a particular managed node, contains the following elements or columns:

  • tcpConnState: State of a particular TCP connection
  • tcpConnLocalAddress: The local IP address of a particular TCP connection
  • tcpConnLocalPort: The local port number of a particular TCP connection
  • tcpConnRemAddress: The remote IP address of a particular TCP connection
  • tcpConnRemPort: The remote port number of a particular TCP connection

The entire data structure is defined in SNMP MIB as a sequence of OBJECTTYPE macros where the enclosing macro defines the tcpConnTable object itself, and individual columns are specified by inner OBJECT-TYPE macros—one for each of the elements in the preceding list.

Translated to MOF, such definition will look like the following:

class SNMP_RFC1213_MIB_tcpConnTable : SnmpObjectType {
 string tcpConnState;
 sint32 tcpConnRemPort;
 sint32 tcpConnLocalPort;
 string tcpConnRemAddress;
 string tcpConnLocalAddress;
};

Here the entire TCP Connection Table is represented by a single WMI class and the individual table columns are translated into the WMI class properties. Note that the class is derived from an abstract superclass SnmpObjectType. The superclass does not add any properties; it is only used as a vehicle for grouping the SNMP objects. The naming convention used for the generated WMI class definitions is interesting—the name of the WMI class is constructed as a name of the respective SNMP table, prefixed with "SNMP_RFC1213_MIB" string. The RFC1213_MIB portion of the prefix is actually the name of the MIB module, provided by another macro, MODULE-IDENTITY. Apparently, RFC1213 refers to Request for Comments 1213, "Management Information Base for Network Management of TCP/IP-Based Internets: MIB2," which defines the second version of the MIB.

SNMP events, referred to as traps, are mapped in a similar fashion. If a trap is defined in a MIB using the SNMPv1 TRAP-TYPE macro, it is first mapped to a newer SNMPv2 NOTIFICATION-TYPE macro and then converted to an appropriate WMI class. All WMI classes representing SNMP traps, ultimately derive from the system class, __ExtrinsicEvent, which makes subscribing for traps similar to registering for WMI extrinsic events. However, depending on the event provider, the trap classes may inherit from one of the two subclasses of __ExtrinsicEvent: SnmpNotification or SnmpExtendedNotification. The former is used to model encapsulated events, while the latter represents referent events. The difference between the two is usually in the form of some additional class properties within the referent event class; these properties allow you to establish a link to an underlying object that is responsible for generating a trap.

The MIB definitions are converted to a form consumable by WMI using the SNMP Information Module Compiler (smi2smir.exe) utility, which is distributed as part of the WMI SDK. It is a very flexible program, which can be used to not only translate MIBs, but also update the CIM Repository and produce extensive informational and diagnostic output.

Based on whether you want to use the dynamic class provider, you have two options when converting SNMP MIBs to WMI class definitions. First, you can use the Information Module Compiler to produce regular MOF files, which can then be loaded into the repository manually, using mofcomp.exe. Alternatively, you can instruct smi2smir.exe to load the generated WMI class definitions into a specially designated portion of the CIM Repository, known as SNMP Module Information Repository (SMIR). SMIR is a special namespace, which usually resides under rootSNMPSMIR, and it is used by the dynamic class provider as a template for generating the appropriate class definitions on demand.

The following is the basic syntax for invoking smi2smir.exe:

SMI2SMIR.EXE   

Table 7-15 lists all the SMI2SMIR.EXE command-line options.

Table 7-15: SMI2SMIR.EXE Command-Line Options

OPTION

DESCRIPTION

/m

Designates the level of diagnostics to output:

0—Silent

1—Fatal

2—Fatal and warnings (default)

3—Fatal, warnings, and information messages

/c

Indicates the maximum total number of fatal and warning messages to output. If omitted, there is no limit.

/v1

Enforces strict conformance to SNMPv1 and instructs the compiler to report an error if it encounters non-SNMPv1-compliant statements.

/v2c

Enforces strict conformance to SNMPv2 and instructs the compiler to report an error if it encounters non-SNMPv2-compliant statements.

/d

Deletes the module specified by the MIB file from the SMIR.

/p

Deletes all modules from the SMIR.

/l

Lists all modules in the SMIR.

/lc

Conducts a local syntax check on the module specified by the MIB file.

/ec

Conducts a local and external syntax check on the module specified by the MIB file.

/a

Conducts a local and external syntax check on the module specified by the MIB file and loads the module into the SMIR.

/sa

Same as /a except that it does not produce any diagnostic output.

/g

Generates a MOF file suitable for being loaded into the SMIR.

/gc

Generates a static MOF file suitable for being loaded into the appropriate SNMP namespace manually. This option is very useful in situations when the dynamic class provider is not used.

/h, /?

Prints out help information.

Some command-line options may take command modifiers, as detailed in Table 7-16.

Table 7-16: SMI2SMIR.EXE Command Modifiers

MODIFIER

DESCRIPTION

/i

Specifies a lookup directory to be searched for dependent MIB files. This modifier can be used with options /a, /ec, /g, /gc, and /sa. You can use this option multiple times on a single command-line in order to specify multiple lookup directories. The order of lookup depends on the order of /i options within the command line.

/ch

Adds date, time, host, and user identifiers to the header of the generated MOF file. This modifier can be used with the /g and /gc options.

/t

Instructs the compiler to generate SnmpNotification classes. This modifier can be used with /a, /g, and /sa options.

/ext

Instructs the compiler to generate SnmpExtendedNotification classes. This modifier can be used with /a, /g, and /sa options.

/t /o

Instructs the compiler to generate only SnmpNotification classes. This modifier can be used with /a, /g, and /sa options.

/ext /o

Instructs the compiler to generate only SnmpExtendedNotification classes. This modifier can be used with /a, /g, and /sa options.

/s

Causes the compiler to ignore the text in the DESCRIPTION clause. This modifier can be used to minimize the size of the output WMI class definitions. It can be used with /a, /g, /gc, and /sa options.

/auto

Causes the compiler to rebuild the MIB lookup table before processing the command-line option. This modifier can be used with /a, /ec, /g, and /gc options.

When processing MIB files, smi2smir.exe uses a registry lookup table to search for dependent MIB files. In order to administer and maintain this table, the utility provides a few command-line options, shown in Table 7-17.

Table 7-17: smi2smir.exe Lookup Table Administration Command-Line Options

OPTION

DESCRIPTION

/pa

Adds the directory name supplied on the command line to the lookup table

/pd

Deletes the directory name supplied on the command line from the lookup table

/pl

Lists all directory names in the lookup table

/r

Rebuilds the lookup table

Finally, the smi2smir.exe utility can be used to obtain the module information (ASN.1 module names) from a MIB file. Table 7-18 shows the command-line options that instruct the compiler to extract the names from the SNMP module.

Table 7-18: smi2smir.exe Module Information Command-Line Options

OPTION

DESCRIPTION

/n

Instructs the compiler to extract and output the ASN.1 name of the MIB module pointed to by the MIB file argument.

/ni

Instructs the compiler to extract and output the ASN.1 names of all import modules, which are used by the MIB module, pointed to by the MIB file argument.

Configuring the SNMP Provider

As I already mentioned, the SNMP to WMI bridge can be configured in a couple of different ways (with or without the dynamic class provider). Perhaps the easiest way to set it up is to compile the MIB for a particular SNMP device using the smi2smir.exe with the /gc option and then load the resulting WMI class definitions into the appropriate WMI namespace. Once such static class definitions are installed, the SNMP instance provider takes care of retrieving the appropriate instance-level data.

The apparent downside of this approach is that it involves a fair amount of manual labor and leaves a lot of room for error. The problem is that SNMP devices and SNMP agents differ and they may not always possess certain capabilities. Thus, unless you load a MIB definition that is specific to the device in question, rather than a generic MIB, the repository may be polluted with unsupported SNMP classes.

This problem may easily be solved by utilizing the dynamic class provider. This provider is responsible for generating the WMI class definitions dynamically, based on the template classes, which can be found in the SMIR repository. The repository, located in the rootSNMPSMIR namespace, usually houses the WMI class definitions that correspond to a generic MIB. In fact, if installed with SNMP support enabled, WMI automatically loads such generic classes into the SMIR, thus alleviating the need for manually compiling the MIB modules with smi2smir.exe. Once the SMIR is populated with class templates, the provider can query the SNMP agent and match the returned SNMP object IDs (OIDs) against the objects in the repository, thus determining the capabilities of the agent.

The provider is fairly flexible and can operate in two modes: correlated and noncorrelated. When enumerating classes in correlated mode, the provider traverses the SMIR and returns only the classes that are supported by the SNMP agent. In noncorrelated mode, all classes from SMIR are returned to the client. While the former is default, the mode can be controlled via the Boolean context value Correlate, which can be set through the IWbemContext interface.

Regardless of whether the class provider is utilized, there has to be a way to associate a set of WMI classes that pertain to a particular SNMP device with that device. If you recall the definition for the SNMP_RFC1213_MIB_tcpConnTable class, shown earlier, you may remember that the class does not seem to include any properties, such as host name or address, which would allow it to be bound to a specific SNMP device.

In this case, rather than associating individual WMI classes with corresponding SNMP objects that pertain to a given device, the SNMP provider relies on the association between a device and a WMI namespace, which houses the respective classes. Thus, in order to gain access to SNMP objects for a given computing node, you have to set up a separate WMI proxy namespace that is linked to the node in question. For instance, in order to see the SNMP objects pertaining to one of my UNIX hosts called "sun1", I would have to set up a separate WMI namespace and link it to the host name or address. Linking a namespace and a SNMP device is accomplished with the help of class qualifiers with which the namespace class is decorated. The following MOF code snippet shows the mini-malistic declaration for the namespace class:

#pragma namespace("\\.\rootsnmp")
[AgentAddress("sun1")]
instance of __Namespace {
 Name = "sun1";
};

The result of compiling and loading this code with MOFCOMP.EXE is a new namespace rootSNMPSUN1, attributed with an AgentAddress qualifier that points to the UNIX host "sun1".

The AgentAddress qualifier is not the only qualifier that may appear as part of the SNMP namespace declaration, although it is the only qualifier necessary for the SNMP provider to establish a link between WMI and the SNMP device. Table 7-19 shows all the qualifiers that are permitted to appear as part of the SNMP namespace declaration.

Table 7-19: SNMP Provider Namespace Qualifiers

QUALIFIER

DESCRIPTION

AgentAddress

Transport address (valid unicast host IP address or DNS name) associated with a specific SNMP agent.

AgentTransport

Transport protocol that is used to communicate with the SNMP agent. Currently, the only valid values are IP (Internet Protocol) and IPX (Internet Packet Exchange). The default value is IP.

AgentReadCommunityName

Variable-length octet string used by the SNMP agent to authenticate the requestor during a read operation. The default value is public.

AgentWriteCommunityName

Variable-length octet string used by the SNMP agent to authenticate the requestor during a write operation. The default value is public.

AgentRetryCount

Integer value that specifies the number of times an SNMP request can be retried before indicating failure to the client. The default value is 1.

AgentRetryTimeout

Time (in milliseconds) to wait for the response from an SNMP agent before indicating failure. The default value is 500.

AgentVarBindsPerPdu

Integer value indicating the maximum number of variables that can be included in a single request. The default value is 10.

AgentFlowControlWindowSize

Integer value indicating the maximum number of outstanding requests that can be sent to an SNMP agent while the response has not yet been received. The value of 0 indicates an infinite number of requests. The default value is 10.

AgentSNMPVersion

String specifying the version of SNMP used to communicate with an SNMP agent. The permitted values are 1 (for SNMPv1) and 2C (for SNMPv2C). The default value is 1.

In order to configure the SNMP bindings for a particular device, you have to set up not only the SNMP namespace, but also the appropriate provider registration entries, and if the class provider is not utilized, you have to load the static classes into the namespace. As I already mentioned, the simplest configuration relies on the SMIR repository and therefore, does not include the static class definitions. For instance, Listing 7-1 shows the MOF code that is sufficient to establish the link to the SNMP agent that is running on the host "sun1".

Listing 7-1: SNMP Namespace and Provider Registration

#pragma namespace("\\.\root\snmp")

[AgentAddress ( "sun1" )]
instance of __Namespace {
 Name = "sun1" ;
} ;

#pragma namespace("\\.\root\snmp\sun1")

instance of __Win32Provider as $PClass {
 Name = "MS_SNMP_CLASS_PROVIDER";
 Clsid = "{70426720-F78F-11cf-9151-00AA00A4086C}";
};
instance of __ClassProviderRegistration {
 Provider = $PClass;
 SupportsGet = TRUE;
 SupportsPut = FALSE;
 SupportsDelete = FALSE;
 SupportsEnumeration = TRUE;
 QuerySupportLevels = NULL ;
 ResultSetQueries = { "Select * From meta_class Where __this isa SnmpMacro" } ;
} ;
instance of __Win32Provider as $EventProv {
 Name = "MS_SNMP_REFERENT_EVENT_PROVIDER";
 ClsId = "{9D5BED16-0765-11d1-AB2C-00C04FD9159E}";
};
instance of __EventProviderRegistration {
 Provider = $EventProv;
 EventQueryList = {"select * from SnmpExtendedNotification"} ;
};
instance of __Win32Provider as $EncapEventProv {
 Name = "MS_SNMP_ENCAPSULATED_EVENT_PROVIDER";
 ClsId = "{19C813AC-FEE7-11D0-AB22-00C04FD9159E}";
};
instance of __EventProviderRegistration {
 Provider = $EncapEventProv;
 EventQueryList = {"select * from SnmpNotification"};
};
instance of __Win32Provider as $PInst {
 Name = "MS_SNMP_INSTANCE_PROVIDER";
 Clsid = "{1F517A23-B29C-11cf-8C8D-00AA00A4086C}";
};
instance of __InstanceProviderRegistration {
 Provider = $PInst;
 SupportsGet = TRUE;
 SupportsPut = TRUE;
 SupportsDelete = TRUE;
 SupportsEnumeration = TRUE;
 QuerySupportLevels = { "WQL:UnarySelect" } ;
};

As you may see, in addition to creating a new namespace, rootSNMPSUN1, this code registers the dynamic class provider, two event providers—encapsulated and referent—and the SNMP instance provider. Once compiled and loaded into the CIM Repository, this code will create all the metadata the SNMP provider needs to connect to the SNMP agent on the host "sun1" and gain access to all available SNMP objects. Note that the dynamic class provider will take upon itself the responsibility of generating dynamic WMI class definitions for the SNMP objects based on the templates found in the SMIR repository.

Accessing SNMP Data

Using the SNMP Provider to access the management data is fairly trivial once all the configuration work is completed. In order to make sure you understand how the SNMP data is retrieved through WMI, I will attempt to create a little program that mimics the functionality of the popular UNIX utility arp(1M). The arp(1M) program displays and modifies the contents of the Internet-to-Ethernet address resolution tables used by the Address Resolution Protocol (ARP). For the sake of saving space, the capabilities of our program will be limited to printing the contents of the address translation or Net-to-Media table, which is equivalent to running the arp(1M) utility with the -a command line switch. The complete source code for this program is shown in Listing 7-2.

Listing 7-2: Listing the Contents of the Internet-to-Ethernet Address Resolution Table

using System;
using System.Management;

public class Arp {
public static void Main(string[] args) {
 ManagementClass mc = new ManagementClass(
 @"\.
ootSNMPSUN1:SNMP_RFC1213_MIB_ipNetToMediaTable");
 foreach(ManagementObject mo in mc.GetInstances()) {
 Console.WriteLine("{0} {1} {2} {3}",
 mo["ipNetToMediaIfIndex"], mo["ipNetToMediaNetAddress"],
 mo["ipNetToMediaPhysAddress"], mo["ipNetToMediaType"]);
 }
}
}

This code first constructs a new ManagementClass object that is bound to the definition for the SNMP_RFC1213_MIB_ipNetToMediaTable WMI class, which corresponds to the SNMP Net-to-Media table. Then the program retrieves all instances of this class using the GetInstances method of the ManagementClass type. Each instance corresponds to a single entry in the Net-to-Media table, or, in other words to a single address translation mapping. Then the following properties of each retrieved object are printed out on the system console:

  • ipNetToMediaIfIndex: Index of the network interface for which the entry is effective.
  • ipNetToMediaNetAddress: IP address that corresponds to the physical address for the entry.
  • ipNetToMediaPhysAddress: Physical, media-dependent address for the entry. For example, for ethernets, it will contain the MAC address.
  • ipNetToMediaType: Dynamic or static depending of whether the particular address mapping is learned through the ARP (dynamically).

Once compiled and executed, the program will produce the output, similar to the following:

2 198.22.30.50 00:02:55:66:31:9c dynamic
2 198.22.31.112 08:00:20:90:cf:1c static
2 198.22.31.121 00:02:a5:87:72:72 dynamic
2 198.22.31.122 00:50:04:09:e7:9b dynamic
2 198.22.31.124 00:02:55:f4:19:b0 dynamic
2 198.22.31.151 00:e0:29:98:37:56 dynamic
...

It does not take a rocket scientist to dump the contents of the SNMP Net-to-Media table. To fully appreciate the capabilities of the SNMP-to-WMI bridge, let us look at a little more complex example—a rudimentary intrusion detection program.

Building reliable intrusion detection systems is somewhat of a black art and it often requires an intimate understanding of the inner workings of TCP/IP. With SNMP, however, it is possible to implement a simple monitor that would check for the most common intrusion scenarios. For instance, one of the first steps a potential intruder would take is fingerprinting your system with some kind of a port scanning utility. Actually, fingerprinting and port scanning are two different things. Fingerprinting involves identifying the traits of a particular computing node, such as its operating system, version of the TCP/IP stack, and so on. Port scanning, on the other hand, comes down to determining which networks services are running on the target machine in order to pick the most vulnerable area of the system. Once the make, release, and build of the OS are known and all network services are identified, an intruder may be able to exploit a known security hole in one of the network daemons, such as sendmail, for instance.

The good news is that most port scanning activities are easy to detect, although implementing a port scanner monitor requires a solid understanding of the TCP connection establishment process. The three-way handshake connection establishment procedure assumes that in order to initiate a connection, a client application will send a SYN (synchronize sequence numbers) segment that specifies the server port number to which this client wants to connect and the client's initial sequence number (ISN). The server then replies with a SYN/ACK packet—the segment that contains the server's initial sequence number as well as the acknowledgment of the client's SYN. Finally, the client acknowledges the server's SYN with another ACK segment. However, if a client attempts to connect to a port that no service is listening to the server will reply with an RST (reset) packet.

There are a few different techniques that port scanners utilize to produce a list of services running on a target machine. The simplest and the most basic form of TCP scanning is a vanilla connect scan. This technique relies on a connect system call to open a connection to each port of interest on a target machine; if the connection succeeds, there's a service listening, otherwise the port is unreachable. A TCP connect scan is very "loud" because most systems will log the failed connection attempts, and it is also very inefficient, especially over slow network links.

A much better scanning technique is SYN or half-open scanning. When using this form of scan, a client will send a SYN packet just like it would do if it were initiating a normal connection. If the server replies with SYN/ACK, the port is in service, if it sends an RST, the port is unreachable. When the client receives a reply from the server, it immediately sends back an RST packet, thus tearing down the connection so that it never goes into the established state. SYN scanning is fairly efficient, and significantly less visible than the vanilla connect scan, because the half-open connection attempts are normally not logged by the target system.

Yet, another scanning technique that is even more clandestine than SYN scanning, is FIN scanning. When FIN-scanning, a client sends a FIN (finish sending data) packet to a server. If the client receives the RST reply, the port of interest is closed; however, if the FIN packet is ignored altogether, the port is listening.

As you can see, regardless of the scanning technique used, the server will send RST replies out if packets arrive on a closed port. Therefore, in order to detect a port scan in progress, all you have to do is to compare the number of outgoing RST packets with a predefined threshold and report a possible port scan if this threshold is exceeded.

The number of outgoing RST packets is maintained as a value of the tcpOutRsts object in the SNMP tcp table. In WMI parlance, this is equivalent to the tcpOutRsts property of SNMP_RFC1213_MIB_tcp object. Thus, the port scanner monitor would have to bind to the aforementioned object and then continuously measure the number of RST packets sent out over a predefined time interval. Listing 7-3 shows the complete source code for this crude port scan monitoring utility.

Listing 7-3: Port Scan Monitor

using System;
using System.Management;

class PortScanMonitor {
public static void Main(string[] args) {
 DateTime t0 = DateTime.UtcNow;
 DateTime t1;
 ManagementObject mo = new ManagementObject(
 @"\.
ootSNMPlocalhost:SNMP_RFC1213_MIB_tcp=@");
 float v0 = float.Parse(mo["tcpOutRsts"].ToString());
 float v1 = 0;
 while(true) {
 System.Threading.Thread.Sleep(10000);
 t1 = DateTime.UtcNow;
 mo = new ManagementObject(
 @"\.
ootSNMPlocalhost:SNMP_RFC1213_MIB_tcp=@");
 v1 = float.Parse(mo["tcpOutRsts"].ToString());
 if ( (v1-v0)/(t1-t0).Seconds > 2) {
 Console.WriteLine(
 "Incoming connections refused: possible port scanner attack");
 }
 v0 = v1;
 t0 = t1;
 }
}
}

The structure of this program is very similar to that of the performance monitoring utility, shown earlier in this chapter. The code simply repeatedly binds to the instance of the SNMP_RFC1213_MIB_tcp class (note that this class is a singleton) and calculates the number of outgoing RST packets over a 10 second (10,000 milliseconds) interval. If the calculated number of RSTs exceeds the threshold (2 packets per 10 seconds), the program reports a possible port scan. Indeed, if you fire up your favorite port scanner (mine is Nmap from Insecure.Org— www.insecure.org), you will notice that our program detects the scanning activity immediately regardless of the scanning technique used.

Receiving SNMP Traps

In addition to monitoring the state of SNMP objects, the SNMP provider is capable of receiving and processing events generated by SNMP devices. In the SNMP world, events raised by the devices under different circumstances are referred to as traps, or in SNMPv2 parlance, notifications. All SNMP traps can be broadly subdivided into the following three categories:

  • Generic: Traps and notifications that are the result of named events, such as link up, link down, cold, and warm start events. In WMI, generic traps are represented by the subclasses of SnmpNotification and SnmpExtendedNotification, depending on whether the encapsulated or referent event provider is used.
  • Enterprise-specific: User-defined traps and notifications. WMI does not include any classes that correspond to enterprise-specific traps. In order to enable support for these traps, a user must manually define specific WMI classes and install them in the CIM Repository. Note that standard SNMP MIBs do not include any enterprise-specific trap definitions—it is the responsibility of the user to supply such definitions for all supported trap types.
  • Enterprise-nonspecific: Traps and events that do not correspond to either generic or enterprise-specific events. In WMI, enterprise-nonspecific traps and notifications are represented by SnmpV1Notification, SnmpV2Notification, SnmpV1ExtendedNotification, and SnmpV2ExtendedNotification classes.

Generic traps are the most widely utilized category of notifications mainly due to their platform-neutral nature. There are a handful of events that may generate generic traps. Table 7-20 lists all the available generic traps along with their descriptions and the WMI classes that represent these traps.

Table 7-20: SNMP Generic Trap Types

TRAP

DESCRIPTION

WMI CLASS

coldStart

Generated whenever the SNMP device is undergoing a reboot due to a hardware reset or power-up

SnmpColdStartNotification, SnmpColdStartExtendedNotification

warmStart

Generated whenever the SNMP agent is restarted

SnmpWarmStartNotification, SnmpWarmStartExtendedNotification

linkDown

Raised whenever a physical network link on the SNMP device is brought down

SnmpLinkDownNotification, SnmpLinkDownExtendedNotification

linkUp

Raised whenever a physical network link on the SNMP device is brought up

SnmpLinkUpNotification, SnmpLinkUpExtendedNotification

authenticationFailure

Generated whenever the SNMP agent receivesa request with either an invalid community specification or a specification for a community that is not authorized to carry out the requested operation

SnmpAuthenticationFailureNotification SnmpAuthenticationFailureExtendedNotification

egpNeighborLoss

Raised whenever the peerrelationship betweentheEGP neighbor andthe EGP peer is lost

SnmpEGPNeighborLossNotification, SnmpEGPNeighborLossExtendedNotification

As you may remember, there are two SNMP event providers: encapsulated and referent. The former delivers traps to WMI clients as subclasses of the SnmpNotification class. The latter represents notifications as subclasses of the SnmpExtendedNotification class. Both these base classes are absolutely identical and contain the same properties. Their subclasses, however, may differ depending on the trap type. For instance, take a look at the definitions for SnmpLinkDownNotification and its referent counterpart SnmpLinkDownExtendedNotification:

class SnmpLinkDownNotification : SnmpNotification {
 sint32 ifIndex;
 string ifAdminStatus;
 string ifOperStatus;
};

class SnmpLinkDownExtendedNotification : SnmpExtendedNotification {
 SNMP_RFC1213_MIB_ifTable ifIndex;
 SNMP_RFC1213_MIB_ifTable ifAdminStatus;
 SNMP_RFC1213_MIB_ifTable ifOperStatus;
};

These two classes are very similar except that they derive from different bases and their properties are of different data types. SnmpLinkDownNotification conveys the information on the respective network interface using string properties for the interface index (ifIndex), the desired status of the interface (ifAdminStatus), and the current operational status of the interface (ifOperStatus). SnmpLinkDownExtendedNotification, on the other hand, represents the same information using embedded objects of type SNMP_RFC1213_MIB_ifTable. As you may remember, the SNMP interfaces table corresponds to the aforementioned WMI class and contains a single entry per network interface installed on the SNMP device.

As I already mentioned, the user defines the enterprise-specific traps and notifications. There are few steps involved in configuring WMI in order to receive these kinds of notifications. First, trap definitions for enterprise-specific events should be added to the MIB file for the SNMP device, which is capable of generating such traps. Then, the MIB should be converted to a form that is consumable by WMI (MOF) and loaded into the CIM Repository. This conversion can be performed using the smi2smir.exe utility, described earlier. The resulting WMI class definitions will be the subclasses of either SnmpNotification or SnmpExtendedNotification—the compiler actually supports a few command-line options that provide instruction on generating the trap classes (see Table 7-15). Once defined and installed, enterprise-specific traps can be subscribed to similarly to subscribing to the generic traps.

Finally, if an SNMP event provider receives a trap or notification that it cannot map to any of the existing WMI classes, it will consider the event an enterprise-nonspecific notification and deliver it using one of the following four WMI classes: SnmpV1Notification, SnmpV2Notification, SnmpV1ExtendedNotification, or SnmpV2ExtendedNotification. Interestingly, all four of these classes are exactly the same and have just one property, VarBindList, which is an array of SnmpVarBind objects. The SnmpVarBind is a WMI base class, which represents the SNMP variable binding, and it has the following definition:

class SnmpVarBind {
 string Encoding;
 string ObjectIdentifier;
 uint8 Value[];
};

where

  • Encoding: String that indicates the type of SNMP variable (ASN.1)
  • ObjectIdentifier: String that contains an SNMP object identifier (OID) for the SNMP variable
  • Value: Raw (octets) data for the SNMP variable

This structure makes the enterprise-nonspecific trap classes very generic and suitable for conveying the information on just about any type of event.

Having done all the necessary set up work, you can subscribe to SNMP traps in the same way you would subscribe to regular WMI extrinsic events. For instance, the following snippet of code registers for all linkUp traps:

using System;
using System.Management;

public class LinkUpMonitor {
 public static void Main(string[] args) {
 ManagementBaseObject mo;
 ManagementEventWatcher ev =
 new ManagementEventWatcher(@"\.
ootSNMPlocalhost",
 "SELECT * FROM SnmpLinkUpNotification");
 while(true) {
 mo = ev.WaitForNextEvent();
 foreach(PropertyData pd in mo.Properties) {
 Console.WriteLine("{0} {1}", pd.Name, pd.Value);
 }
 }
}
}

To see this code in action, you would have to find a way to generate the linkUp event. There are a couple of ways to go about this. The easiest and the crudest technique is to simply pull the network cable out, and then plug it back in and wait for the SNMP agent to generate the trap. Unfortunately, this approach is less than elegant and may not make you a popular person among your colleagues and coworkers. Fortunately, there is a better way to generate the traps than this brute force approach. Most SNMP implementations include the snmptrap command, which allows you to create any kind of SNMP trap and dispatch it to the destination of your choice. Thus, a generic linkUp trap can be generated as follows, using the snmptrap command that comes with the University of California at Davis SNMP distribution (UCD-SNMP also known as Net-SNMP), which can be downloaded from net-snmp.sourceforge.net:

/usr/local/bin/snmptrap -v 2c -c public localhost '' 
 IF-MIB::linkUp RFC1213-MIB::ifIndex.1 i 1 RFC1213-MIB::ifOperStatus.1 
 i 1 RFC1213-MIB::ifAdminStatus.1 i 1

The syntax of this command is fairly involved and explaining it in detail is certainly outside the scope of this chapter. Suffice it to say that this command line generates a linkUp event, sets the values of relevant SNMP variables, and dispatches the trap to the SNMP agent on the local machine. The interface index, represented by the ifIndex variable, is set to 1, pointing to the first network interface for the local host. The SNMP variables ifOperStatus and ifAdminStatus are also set to the integer value 1, which corresponds to the "up" state of the interface. Once the trap is raised and intercepted by the SNMP event provider, the preceding code will produce the following output:

AgentAddress 10.22.31.112
AgentTransportAddress 10.22.31.112
AgentTransportProtocol IP
Community public
Identification 1.3.6.1.6.3.1.1.5.4
ifAdminStatus up
ifIndex 1
ifOperStatus up
TimeStamp 607804475

If you ignore the properties inherited from the SnmpNotification class, you may notice that the interface index (ifIndex) is set to 1 and both status variables—ifOperStatus and ifAdminStatus—contain the string "up", indicating that the interface 1 has been brought to the up state.

Since the code just listed issues an event query against the SnmpLinkUpNotification class, the events are supplied by the encapsulated SNMP event provider. However, using the referent provider is just as easy. The main difference is that instead of querying the SnmpLinkUpNotification, you would have to write a query against the SnmpLinkUpExtendedNotification class:

using System;
using System.Management;

public class LinkUpMonitor {
 public static void Main(string[] args) {
 ManagementBaseObject mo;
 ManagementEventWatcher ev =
 new ManagementEventWatcher(@"\.
ootSNMPlocalhost",
 "SELECT * FROM SnmpLinkUpExtendedNotification");
 while(true) {
 mo = ev.WaitForNextEvent();

 ManagementBaseObject mo1 = (ManagementBaseObject)mo["ifIndex"];
 if ( mo1 != null )
 Console.WriteLine("ifIndex: {0}", mo1["ifIndex"]);
 mo1 = (ManagementBaseObject)mo["ifAdminStatus"];
 if (mo1 != null)
 Console.WriteLine("ifAdminStatus: {0}", mo1["ifAdminStatus"]);
 mo1 = (ManagementBaseObject)mo["ifOperStatus"];
 if (mo1 != null)
 Console.WriteLine("ifOperStatus: {0}", mo1["ifOperStatus"]);
 }
 }
}

Be careful when retrieving the values of the SnmpLinkUpExtendedNotification properties; rather than being integral values, these properties are embedded objects of the SNMP_RFC1213_MIB_ifTable class, and therefore, they have to be treated as such.

Finally, I will show you how to subscribe to the enterprise-nonspecific notifications. The code to register for such events is very similar to the event handling code you have seen so far, although there are subtle differences in the way the returned event objects are processed:

using System;
using System.Management;

public class NonSpecificTrapMonitor {
 public static void Main(string[] args) {
 ManagementBaseObject mo;
 ManagementEventWatcher ev =
 new ManagementEventWatcher(@"\.
ootSNMPlocalhost",
 "SELECT * FROM SnmpV2Notification");
 while(true) {
 mo = ev.WaitForNextEvent();
 foreach(ManagementBaseObject o in (
 ManagementBaseObject[])mo["VarBindLIst"]) {
 Console.Write("{0} ({1}): 0x",
 o["ObjectIdentifier"], o["Encoding"]);
 foreach(byte b in (byte[])o["Value"]) {
 Console.Write(b.ToString("X2"));
 }
 Console.WriteLine();
 }
 }
 }
}

You should notice a few things here. First, this code issues a WQL query against the SnmpV2Notification class, which is used to represent the enterprisenonspecific notifications. All WMI classes that correspond to nonspecific traps have a single property, which is an array of SnmpVarBind objects. Thus, once an event is received, the code iterates through the array pointed to by the VarBindList property of the returned SnmpV2Notification instance and prints out the property values of each SnmpVarBind object. Outputting the ObjectIdentifier and Encoding property values is straightforward, however, handling the Value property is a bit involved. This property contains the raw variable value in octets, and therefore, it is represented by a byte array. Typically, your code should interrogate the Encoding property to determine the real data type of the variable and then cast the byte array to the appropriate type. The code fragment above simply prints the raw value in hexadecimal format.

The following UCD-SNMP command can be used in order to generate an enterprise nonspecific trap to test this code:

snmptrap -v 2c -c public localhost '' SNMPv2-MIB::snmpTraps.7
 RFC1213-MIB::ifIndex.1 i 1
 RFC1213-MIB::ifOperStatus.1 i 1 RFC1213-MIB::ifAdminStatus.1 i 1

Just like the snmptrap command for linkUp notification shown earlier, this command sets the values of ifIndex, ifOperStatus, and ifAdminStatus to the integer value 1. The trap type used here, however, is different and corresponds to the enterprise-nonspecific trap category. Upon receiving the trap generated by this command, the event handling code will produce the following output:

1.3.6.1.2.1.2.2.1.1.1 (INTEGER): 0x01000000
1.3.6.1.2.1.2.2.1.8.1 (INTEGER): 0x01000000
1.3.6.1.2.1.2.2.1.7.1 (INTEGER): 0x01000000

The dot-separated values are the OIDs of ifIndex, ifOperStatus, and ifAdminStatus SNMP objects respectively. The encoding (INTEGER) indicates that the values of these variables are indeed integers. Finally, the raw values printed in hexadecimal format correspond to the integer value of 1 (little-endian notation).

As you can see, SNMP provider is fairly versatile and lets you build powerful monitoring applications, even when you are dealing with legacy computing environments. Although integrating SNMP into a WMI-based management framework may be a bit laborious and may require a fair understanding of SNMP fundamentals, it is certainly worth the effort, which is guaranteed to eventually pay off.


Summary

This chapter provided an overview of some of the WMI providers that are distributed as part of the WMI SDK. Although there are many more providers available from Microsoft and third-party vendors, those described here are among the most interesting and least well known. Thus, having studied the material presented here, you should now be in position to

  • Understand how the performance metrics are organized and structured on Windows platforms.
  • Write simple monitoring utilities using the Performance Monitoring provider.
  • Understand the difference between conventional and high-performance WMI providers.
  • Comprehend the basic methodology for calculating the meaningful performance metrics from the raw values, obtained through the Performance Counter provider.
  • Create simple, yet powerful, performance monitoring applications with the Cooked Counter provider on Windows XP.
  • Understand the fundamentals of the Simple Network Management Protocol (SNMP).
  • Comprehend the structure of a WMI SNMP provider and determine the configuration required to access specific SNMP devices from a WMI-based management console.
  • Develop code for retrieving SNMP objects.
  • Create simple programs to receive SNMP traps and notifications.

In addition to supplying the background information on some of the WMI providers, the goal of this chapter was also to whet your appetite so that you will want to explore other, poorly documented and little-known providers that may be available from Microsoft or other software vendors. After all, sometimes solving the most challenging system management problem can be as simple as finding an appropriate WMI provider to do the job for you.




. NET System Management Services
.NET System Management Services
ISBN: 1590590589
EAN: 2147483647
Year: 2005
Pages: 79

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net