This documentation is archived and is not being maintained.

Creating COM+ PerfMon Counters to Monitor COM+ Data

Visual Studio .NET 2003
 

Nathan Enright
Microsoft Corporation

December 2001

Summary: The most common request our COM+ support engineers get is for out-of-the-box profiling tools available for COM+ applications, most notably, for PerfMon counters. Using the techniques described in this article, you will be able to create your own PerfMon counters for your existing COM+ applications, giving you the power and flexibility to monitor the built-in data provided by the COM+ system events. Learning to leverage this event system will deepen your understanding of COM+ and hopefully be useful in detecting application bottlenecks before you reach production. (16 printed pages)

Download Sample

Contents

Introduction
COM+ Instrumentation
Setting Up the Project
Event Subscription
PerfMon Counters
   Installation and Removal of the Counters
   Addition and Removal of the Instances
   Instance manipulation
Conclusion

Introduction

This article shows you how to subscribe to the COM+ system events, create and subscribe to custom events, and turn the data from these events into useful PerfMon counters. The entire application was written with C# and uses COM interop to subscribe to the event class interfaces and manipulate the COM+ catalogue. A general understanding of COM+ transient subscriptions is helpful but not needed.

COM+ Instrumentation

The developers of COM+ had the foresight to build a rich infrastructure into COM+ that enables application programmers to get vital information about a COM+ application. Using this infrastructure, we are going to take this data and create PerfMon counters that will give us a graphical representation of what's going on behind the scenes. For the purpose of this sample I've only implemented one of the COM+ instrumentation interfaces, IComMethodEvents. However, the COM+ monitor object model is such that additional interfaces can be implemented very easily.

The IComMethodEvents interface gives us three methods: OnMethodCall, OnMethodReturn and OnMethodException. Using these methods, we are going to create a PerfMon counter that will show us the execution time of the method. This can be useful in determining an application bottleneck. Note that the IComMethodEvents events are only useful for early-bound calls. This is because a late-bound call is made through IDispatch and by calling GetIDsOfNames and Invoke. In the case of a late-bound call, the OnMethodCall and OnMethodReturn events will get fired twice, once for GetIDsOfNames and once for Invoke. There is no way for us to get the method name that Invoke is calling. However, this can be overcome by adding your own instrumentation code to your components, as well as firing and subscribing to the IComUserEvent interface.

Note   In order to receive any of the COM+ events for an individual component, the "Component supports events and statistics" option must be selected in the Component Services Snap-in for each component. This is enabled by default.

Setting Up the Project

The COM+ Monitor project is split into two parts, the COMPlusMon WinForm EXE and the COMSysLCE DLL. The bulk of the work is done in the COMSysLCE DLL and is where we will focus for this article.

The current release of the .NET Framework has not incorporated all of the functionality of COM+ that we will be using. In fact you will find that we don't even use the System.EnterpriseServices Namespace. We will be using the COM+ 1.0 Admin Type Library and the COM+ Services Type Library.

In order for us to take advantage of the functionality in these libraries, we will need to use COM interop and create an assembly for these DLLs. Visual Studio .NET will take care of this for us, simply add a new COM reference to COM+ Admin Type Library and the COM+ Services Type Library and allow Visual Studio to create the assembly.

Once the references are added and the assemblies generated we can add the COMAdmin and COMSVCSLib namespaces to our code modules, as follows:

using COMAdmin;

using COMSVCSLib;

These are the only two outside references we will need. However, here are the other namespaces that we will be making use of:

using System;

using System.Runtime.InteropServices;

using System.Diagnostics;

using System.Collections;

using Microsoft.Win32;

I wanted to focus this article on the implementation of the sample but thought it was necessary to talk a little bit about the object model. It is important to understand how things work so you can extend the sample for your own use. The COMSysLCE DLL contains six main classes and one interface:

CAdminWrap This class is used to wrap most of our COM+ Catalog work. It contains the following methods:

AddTransientSubscription

RemoveTransientSubscription

GetCollection

RemoveNamedObjectFromCollection

GetNamedObjectFromCollection

SetStringProperty

SetIUnknownProperty

CAppInfo This class is the main class that COMPlusMon.EXE interacts with. When the user selects a COM+ application to monitor, CAppInfo subscribes the specific event classes to the selected application. COMPlusMon.EXE contains a collection of all the applications that we are subscribed to. CAppInfo contains the following methods:

CAppInfo (parameterized constructor)

Dispose

AppID (read-only property)

RemoveAllSubscriptions

AddSubscription

ToggleMonitoring

RemoveSubscription

IsSubscribedToAny

CCOMSysLCE CCOMSysLCE is an abstract base class that implements the ICOMSysLCE interface. It contains the following methods:

CCOMSysLCE

EventName

GetEventType

GetEventClass

GetInterface

GetUnknown

AddPCInstances

RemovePCInstances

CCOMMethodEvent This is the class that implements CCOMSysLCE and IComMethodEvents and contains the following additional methods:

GetMethodName

GetTypeLibID

ICOMSysLCE This interface defines the following methods:

GetEventClass

GetEventType

Install

UnInstall

Monitor (read/write property)

AddPCInstances

RemovePCInstances

The easiest way to figure out the object model is to step through the code a couple of times. Put a breakpoint in the COMPlusMon project at the beginning of the chklbApps_ItemCheck event handler. Once you run the project and select an application to monitor you will hit this breakpoint.

To run the sample you need to compile both the COMSysLCE DLL and the COMPlusMon EXE. Next, you need to install the counters. For more information, see the following Installation and Removal of the Counters section. Once everything is set up you can use the Visual Basic Test DLL and EXE that is included to test the counters.

Event Subscription

Subscribing to the different event classes is the same no matter which event class you are subscribing to; except for the event class ID, which is simply the interface ID you are subscribing to and the name of the subscription. Event subscription is an exercise in COM+ catalog manipulation. To demonstrate this I will use code that is slightly different from the sample so that it is easier to follow. If you want to see where this is done in the sample, check out the AddTransientSubscription function in the CAdminWrap class COMSysLCE project. The following code demonstrates how to subscribe to the IComMethodEvents interface.

//Get a COMAdminCatalog object. This object is defined in the
//COMAdmin namespace.

ICOMAdminCatalog pICat = new COMAdminCatalog();

//Get the TransientSubscriptions collection and populate it.

ICatalogCollection pISubs = 
(ICatalogCollection)pICat.GetCollection("TransientSubscriptions");

pISubs.Populate();

//Add a new transient subscription.

ICatalogObject pISub = (ICatalogObject)pISubs.Add();

//Now that we have our new subscription CatalogObject we can begin
//filling out the details.

//Set the name of the subscription.
pISub.set_Value("Name", "Method");

//Set the event class ID. This is the GUID for
//CLSID_ComServiceEvents, and is defined in comsvcs.h.
pISub.set_Value("EventCLSID", "{ECABB0C3-7F19-11D2-978E-0000F8757E2A}");

//Set the interface ID for IComMethodEvents.
pISub.set_Value("InterfaceID, "{683130A9-2E50-11D2-98A5-00C04F8EE1C4}");

//Set the SubscriberInterface property to the IUnknown pointer of the
//subscriber. The subscriber would be the class that implements the 
//IComMethodEvents interface, for example,
//IntPtr punk = Marshal.GetIUnknownForObject(CComMethodEvent Object);
pISub.set_Value("SubscriberInterface", punk);

//Save the changes to the TransientSubscriptions catalog collection.
long lret = pISubs.SaveChanges();

//We need to get the subscription ID and save it so that we will be 
//able to remove the subscription.
string strSubID = (string)pISub.get_Value("ID");

//Now we need to get the TransientPublisherProperties collection.
string strKey = (pISub.Key).ToString();

ICatalogCollection pITPPs = 
(ICatalogCollection)pISubs.GetCollection("TransientPublisherProperties",
strKey);

pITPPs.Populate();

//Add a new TransientPublisherProperties object.
ICatalogObject pITPP = (ICatalogObject)pIProps.Add();

//Here we are going to set the publisher we want to subscribe to.
//This will be the AppID of the COM+ application we want to monitor.
//I do not set strAppID in this code snippet. You can hardcode it here
//or take a look at the sample code to see how I get it.
pITPP.set_Value("Name", "AppID");
pITPP.set_Value("Value", strAppID);


//Finally, save the changes on the TransientPublisherProperties 
//collection.
lret = pITPPs.SaveChanges();

Subscribing to the interfaces is 60% of the application. Once you are subscribed you will be notified through the interface methods when the events occur. There are a couple of things to note from the code above:

  • Use Marshal.GetIUnknownForObject to get back a COM callable IUnknown pointer for your C# object that implements the interface you are subscribing to.
  • You can use OLEView to get the interface IDs for the interfaces you want to subscribe to. In expert mode, expand the Interfaces node and search for the desired interface, for example, IComMethodEvents.
  • The event class ID will be the same no matter which interface you subscribe to.

The last thing we need to talk about is how to remove the subscriptions. If you fail to do this step, the subscription will stay around until the machine is rebooted. If this is happens, it won't hurt anything; we just don't want to litter the registry.

//Get a COMAdminCatalog object. This object is defined in the
//COMAdmin namespace.

ICOMAdminCatalog pICat = new COMAdminCatalog();

//Get the TransientSubscriptions collection and populate it.
ICatalogCollection pISubs = (ICatalogCollection)pICat.GetCollection("TransientSubscriptions");

long lCount = 0;
int i = 0;
ICatalogObject pISub;

pISubs.Populate();
            
lCount = pISubs.Count;

// If the count is 0, then there are no transient subscriptions.
if (lCount == 0)
{
  return;
}

//Loop through the collection until we find the subscription we want
//to remove. Remember, we saved the subscription ID when we created
//the subscription.
for (i=0; i<lCount; i++)
{
  pISub = (ICatalogObject)pISubs.get_Item(i);

  if (strSubID == (string)pISub.get_Value("ID"))
  {
    pISubs.Remove(i);
    pISubs.SaveChanges();
    return;
  }
}

Once again, as is the case with subscribing to the event, removing the subscription is basically a lesson in catalog manipulation and is easy once you figure out how the catalog works.

For more information about the COM+ catalog and transient subscriptions, see the following topics from the Platform SDK:

PerfMon Counters

There are three main parts to .NET PerfMon counters, as follows:

  1. Installation and Removal of the Counters
  2. Addition and Removal of the Instances
  3. Instance Manipulation

Installation and Removal of the Counters

What we are going to do is utilize .NET installation components and have our installation program handle most of the work for us. In fact, we don't even need to write any code!

To begin, add a new Installer class to the COMPlusMon project. To do this, right-click the COMPlusMon project in Solution Explorer, choose New Component from the Add menu, and then select Installer Class.

Next we are going to perform a drag-and-drop operation on a PerformanceCounterInstaller component, moving it onto the design window of the newly added Installer Class.

Note   If you don't see a PerformanceCounterInstaller component listed in the components section of the Toolbox, you can add it by right-clicking the Toolbox and choosing Customize Toolbox. You can add the PerformanceCounterInstaller component from the .NET Framework Components tab.

Finally, we need to modify the properties of the PerformanceCounterInstaller object. First specify a category name, for example, "COM+". The CategoryName property corresponds to the Performance Counter Object name. Then add entries to the Counters collection, for example, CounterName="Method Duration", CounterType=NumberOfItems32. The NumberOfItems32 counter will provide the functionality that we need. For a full list of the available counters, see PerformanceCounter.CounterType Property.

When you create your installation for the application you can add the newly created Installer class to the custom actions section of you installer project. The installation application will take care of adding and removing the counters for you when the user adds or removes your application.

Addition and Removal of the Instances

Performance counter instances are dependent on the type of event we want to subscribe to. We are going to add a Method Duration counter that will tell us the method execution time in one-hundredth of a second. The Method Duration counter will be under the COM+ object and the instances will be all the methods in the select COM+ application. This way we can pick and choose the methods that we want to monitor and get individual statistics for each method.

Add two methods to your individual event classes (for example, CCOMMethodEvent). These methods are defined in ICOMSysLCE and are virtual in CCOMSysLCE, so we must override them in the child classes. Both methods take the AppID of the COM+ application that we will be monitoring.

override public void AddPCInstances(string strAppID)
{
  CAdminWrap oAdminWrap = new CAdminWrap();
  COMAdmin.ICatalogCollection appColl, comColl, intColl, metColl;
  COMAdmin.ICatalogObject appObj, comObj, intObj, metObj;
  int nIndex;

  //Get the Admin catalog.
  COMAdminCatalog oCatalog = new COMAdminCatalog();
  
  //Get the Applications collection.
  appColl = oAdminWrap.GetCollection(oCatalog, "Applications");

  //Get the Catalog object for the passed-in AppID.
  appObj = oAdminWrap.GetNamedObjectFromCollection(appColl, strAppID,
    out nIndex, "ID");
            
  //Get the Components collection for the application.
  comColl = oAdminWrap.GetCollection(appColl, appObj, "Components");
            
  //We need to first loop through all of the entries in the Components
  //collection.
  for (int nComIndex = 0; nComIndex < comColl.Count; nComIndex++)
  {
    string strProgID;
    comObj = (ICatalogObject)comColl.get_Item(nComIndex);

    //While we are here we will grab the progID for the component.
    strProgID = (string)comObj.get_Value("ProgID");
                
    intColl = oAdminWrap.GetCollection(comColl, comObj,
      "InterfacesForComponent");

    //Then we have to loop through all of the interfaces for each 
    //component.
    for (int nIntIndex = 0; nIntIndex < intColl.Count; nIntIndex++)
    {
      intObj = (ICatalogObject)intColl.get_Item(nIntIndex);

      metColl = oAdminWrap.GetCollection(intColl, intObj,
        "MethodsForInterface");

      //Finally, we loop through the MethodsForInterfaces collection to
      //get the method name.
      for (int nMetIndex = 0; nMetIndex < metColl.Count; nMetIndex++)
      {
        metObj = (ICatalogObject)metColl.get_Item(nMetIndex);
        string strName = (string)metObj.get_Value("Name");
        
        //If the PerformanceCounter object (a class module variable)
        //hasn't been initialized yet do it here and add the first
        //instance. We add the instance as PROGID::MethodName. 
        //PerformanceCounter instances are created by setting the
        //instance name and its raw value. There is no AddInstance 
        //method.
        if (pcMethodDuration == null)
        {
          pcMethodDuration = new PerformanceCounter("COM+", "Method
            Duration", strProgID + "::" + strName, false);
        }
        else
        {
          pcMethodDuration.InstanceName = strProgID + "::" + strName;
        }

        //Set the initial value to 0.
        pcMethodDuration.RawValue = 0;
      }
    }
  }
}

override public void RemovePCInstances(string strAppID)
{
  //The Remove function is exactly the same as the Add function 
  //but with one difference.  
  
  ...
            
  for (int nComIndex = 0; nComIndex < comColl.Count; nComIndex++)
  {
    
    ...

    for (int nIntIndex = 0; nIntIndex < intColl.Count; nIntIndex++)
    {

      ...

      for (int nMetIndex = 0; nMetIndex < metColl.Count; nMetIndex++)
      {
        metObj = (ICatalogObject)metColl.get_Item(nMetIndex);
        string strName = (string)metObj.get_Value("Name");
        int nMethodIndex = (int)metObj.get_Value("Index");
        string strCLSID = (string)metObj.get_Value("CLSID");
        
        //Set the instance name.
        pcMethodDuration.InstanceName = strProgID + "::" + strName;

        //We call RemoveInstance().
        pcMethodDuration.RemoveInstance();
      }
    }
  }
}

Instance manipulation

As with the Addition and removal of the instances, instance manipulation will very based on the interface you subscribe to and what you want your counters to do. In our example, we are going to record the time when the method is fired and then subtract that from the time the method ends. We will then write this number to PerfMon. In the case of method duration the time it takes the method to run is kind of a subjective counter. What we are after is the ability to find bottlenecks and correct them. To make the counters show up on the default PerfMon graph, we will divide the duration in microseconds by 10,000 to give us the duration in one-hundredth of a second.

The first thing we need to do is record the time that the method was fired. Both the OnMethodCall and the OnMethodReturn events give us access to a COMSVCSEVENTINFO event structure. This structure gives us lTime, or the number of seconds past midnight, and lMicroTime, which is the number of microseconds past lTime. Here is the code for OnMethodCall:

void IComMethodEvents.OnMethodCall(ref COMSVCSEVENTINFO ei, ulong
  lObjID, ref Guid gClsID, ref Guid gIID, uint nIndex)
{
  //Make sure that monitoring is enabled and that our performance
  //counter has been initialized.
  if (Monitor && pcMethodDuration != null)
  {
    try
    {                    
      //We are going to store the initial value in a Sorted List 
      //collection. To do this we are going to need a key that
      //represents this call.
      string strKey = lObjID.ToString() + gClsID.ToString() + 
        gIID.ToString() + nIndex.ToString();

      //Here we add the start time to the sorted list using the key
      //created above. First, we convert lTime to microseconds and 
      //then add lMicroTime.
      sl.Add(strKey, (ei.lTime * 1000000) + ei.lMicroTime);
    }
    catch (Exception e)
    {
      Debug.WriteLine(e.Message);
    }
  }
}

As you can see, there's not much to the OnMethodCall event. On the other hand, the OnMethodReturn event has quite a bit of code and utilizes a couple of helper functions. Basically, we need to:

  • Generate the instance name.
  • Retrieve the start time.
  • Subtract the finish time from the start time.
  • Set the raw value of the instance.

I won't go through the code for the helper functions because they are outside the scope of this paper. Here is the code for the OnMethodReturn event:

void IComMethodEvents.OnMethodReturn(ref COMSVCSEVENTINFO ei, ulong loID,
  ref Guid gclsID, ref Guid giID, uint nmethod, int nhresult)
{
  CAdminWrap oAdminWrap = new CAdminWrap();
  COMAdminCatalog oCatalog = new COMAdminCatalog();
  COMAdmin.ICatalogCollection appColl, comColl, intColl, metColl;
  COMAdmin.ICatalogObject appObj, comObj, intObj, metObj;
  int nRet;

  //Make sure that we are monitoring and that our performance counter
  //has been initialized.
  if (Monitor && pcMethodDuration != null)
  {
    try
    {
      //We are going to get as much of the instance name out of the
      //catalog as we can.
      string strAppID = "{" + ei.guidApp.ToString().ToUpper() + "}";
      string strCLSID = "{" + gclsID.ToString().ToUpper() + "}";

      //Build the key so that we can get the start time.
      string strKey = loID.ToString() + gclsID.ToString() +
        giID.ToString() + nmethod.ToString();

      //Get the start time.
      int nIndex = sl.IndexOfKey(strKey);
      long lStart = (long)sl.GetByIndex(nIndex);

      appColl = oAdminWrap.GetCollection(oCatalog, "Applications");
      appObj = oAdminWrap.GetNamedObjectFromCollection(appColl,
        strAppID, out nRet, "ID");
            
      comColl = oAdminWrap.GetCollection(appColl, appObj,
        "Components");
      comObj = oAdminWrap.GetNamedObjectFromCollection(comColl,
        strCLSID, out nRet, "CLSID");
            
      //Here is the ProgID part of the instance name.
      string strProgID = (string)comObj.get_Value("ProgID");

      intColl = oAdminWrap.GetCollection(comColl, comObj,
        "InterfacesForComponent");

      string strMN = "";

      for (int nCount = 0; nCount < intColl.Count; nCount++)
      {
        Guid gIID = new
          Guid((string)
          ((ICatalogObject)
          intColl.get_Item(nCount)).get_Value("IID"));

        //The GetMethodName function is the helper function that uses
        //LoadRegTypeLib to get the method name directly from the 
        //type library. If we don't do this then we won't be able to 
        //check for GetIDsOfNames and Invoke.
        strMN = GetMethodName(gIID, (int)nmethod);

        //We will ignore the call if the method is GetIDsOfNames or
        //Invoke.
        if (strMN != "" && strMN != "GetIDsOfNames" && strMN !=
          "Invoke")
        {
          //Now that we've got the correct instance name we can update
          //the raw value with the finish time minus the start time.
          pcMethodDuration.InstanceName = strProgID + "::" + strMN;
          pcMethodDuration.RawValue = (((ei.lTime * 1000000) +
            ei.lMicroTime) - lStart)/100;
                    
          //Finally, we need to remove the entry from the sorted list.
          sl.Remove(strKey);
        }
      }
    }
    catch (Exception e)
    {
      Debug.WriteLine(e.Message);
    }
  }
}

Conclusion

Just to recap, we've seen how to add and remove the counters using the .NET Installer Components, add and remove the instances, and manipulate those instances. The counters and the instances that you add will be based on the events you want to receive.

Knowing how your COM+ applications are performing can be invaluable both in development and production. This knowledge can help you quickly fix problems that might not have been apparent during development. In addition to problem isolation and identification, custom instrumentation code could be added to provide custom counters; allowing you to get PerfMon data on anything from orders to users.

The COM+ Monitor sample gives you the know how to build a performance and instrumentation application designed to let you take control of your COM+ applications and to leverage Windows Performance Monitoring.

Show: