7.4 Providing .NET Performance Counters
As I've mentioned, the perfmon API requires that you use an inter-process communication mechanism to allow another process to read your counters. As a developer you have to provide a DLL that will be loaded into the reader process that provides one endpoint of the IPC and the other endpoint will be in your application. Your application passes the counter data over this IPC and the reader endpoint DLL will then format the data in the nested structures that the API mandates. The good news is that the .NET classes does all this work for you, and make providing counters very simple.
The most difficult part of adding performance counters to your application is deciding what information you will provide. Note that applications choose to read performance data, the data is not forced upon the reader. The application will simply fill the counter with data at whatever rate it decides to use, and the reader application reads the counter at whatever rate the reader decides, if at all. This means that you can provide as many counters as you wish and update them as often as you like without adversely affecting other processes: if anther process wants your data it can have it, but another process that does not want your data does not have to have it. Obviously updating counters does have some affect on the performance of the providing application, but this is the only downside. Contrast this with the event log where a chatty application is unrestricted from filling the (system maintained) log. This gives two downsides: first, the disk file can get very large, and second, the chatty application's messages will swamp messages from other applications, with the possibility of masking important data.
However, you do have to constrain yourself a little. There is little point in providing counters for every intermediate result because they are unlikely to be read and such results represent information leakage which is a security issue. Similarly, there is little point in updating your counter every 10ms if a user will read the counter once a second (this is the minimum read rate in the perfmon application). The extra work to update the counters will have a detrimental affect on the performance of the application and you have to balance the benefit of providing counters against the detrimental affect of providing them. It would be rather ironic for performance counters to have a significant effect on the performance of your application.
Furthermore, it usually makes little sense to use performance counters to show the final results of your application because that is what your presentation layer is for. For example, imagine an application that crawls the internet looking for reviews of a specific product; when this application finds a reference it then determines how many 'points' the review gives the product and displays the name of the site and the review result on the application's window. It makes no sense to put the review result in a counter because a user will not gain any more information from this than they can by looking at the presentation layer of the application. More relevant information to give via a counter would be how many links the crawler had to go through to get to the review page. This is more suitable for a counter because this will give information about how good or bad the crawler algorithm performs. Information like this is of little use to the final user, but it is very useful to an administrator trying to tune the system. Also, don't shy from abstract counters. Values like bytes handled per second, or calculations performed per second, are useful when trying to determine bottlenecks in the system and trying to determine if tuning the system has been successful.
The choice of the type of the counter is a very important decision. The provider
process generates raw data, the actual calculation on that data is usually done by
CounterSample.Calculate in the client according to the type of the counter. If
you make the wrong decision then the wrong calculation will be performed.
Once you have decided what you will provide through counters the actual .NET code to provide those counters is quite simple. However, remember that, so that the perfmon API knows about your performance counters, you must register them with the system. Once you have registered the categories and counters you will have to restart any clients (eg perfmon) so that they can pick up the new perfmon library. In addition, the general principles of .NET are that if you change a system when installing an application you must revert back to the previous state when the application is uninstalled, so you should also provide a mechanism to unregister the counters when your application is uninstalled. I will explain the best way to do this in a later section, but in the following example I will give a simple, but partial, solution.
Create a file (server.cs) with the following:
using System.Diagnostics;
class Server
{
const string catName = "Data Generator";
const string catHelp = "Generates random sample data";
const string ctrName = "# Connected Clients";
const string ctrHelp = "The number of clients connected to the server";
static void Main(string[] args)
{
if (args.Length > 0)
{
// Delete category
if (PerformanceCounterCategory.Exists(catName))
{
PerformanceCounterCategory.Delete(catName);
}
return;
}
if (!PerformanceCounterCategory.Exists(catName))
{
// Create new counter if it does not exist
CounterCreationDataCollection ccdc = new CounterCreationDataCollection();
CounterCreationData ccd =
new CounterCreationData(ctrName, ctrHelp,
PerformanceCounterType.NumberOfItems32);
ccdc.Add(ccd);
PerformanceCounterCategory.Create(
catName, catHelp,
PerformanceCounterCategoryType.SingleInstance,
ccdc);
Console.WriteLine("Category created, re-run the application");
return;
}
}
}
This process can be run in two ways. If no parameter is provided then the application checks for the existence of the category and if it does not exist the category and its counters are created and the process ends. If a parameter is passed (regardless of what that parameter is) then the application assumes that you want to remove the category.
Compile this code (csc server.cs). Note that to install
categories and counters your
account must be a member of the Administrators group. If this is the case then
run the application without a parameter. If perfmon is running, close
it. Now run perfmon and open the Add Counters dialog. In the
Performance object drop down list you'll see that it now contains the Data Generator
object, select this, and you'll see that the # Connected Clients counter will be shown.
Now start the registry editor (regedit). Open the
HKEY_LOCAL_MACHINE hive and navigate to the following key:
Since perfmon could find the counter it means that somewhere in this
key there should be a entry for the application. Since the subkeys have the
names of services, the most obvious key name to use would be the name of the
process (server), so navigate there and have a look.
You'll see that there isn't an entry (if there is such an entry, check its values - in particular
ImagePath - to convince yourself that it is not for your
application). The next most obvious place to look is a key with the name of
the counter's category: Data Generator. You should find a key with
this name, and it will have a Performance subkey. Here are some
values from my machine:
Open OpenPerformanceData
Close ClosePerformanceData
Collect CollectPerformanceData
Counter Names # Connected Clients
Since no path is given for the library this indicates that the library must
be
in the path. The .NET framework folder is not in the path (deliberately and
for good reason) so this implies that the library is stored in the %systemroot%\System32 folder. Type the following:
You should find that the file is listed. This is one of the few .NET
framework libraries that is installed
in the Windows folder (the only other one is mscoree.dll). The reason why .NET files are not installed
in the Windows system folder is
because it complicates versioning and opens you up to DLL Hell.
| I am surprised that this file was not installed in the framework folder and a fully qualified path given in the registry. I am also surprised that there is just one file for all versions of .NET. |
The .NET framework does not provide the code to update the perflib
key with the names of the counters, categories and help text. Instead it uses
a system tool called lodctr (or unlodctr to remove
the items). This tool is run with a parameter that is a path to a script file
that has information about the changes to be made. During the time that you
are adding counter information (and when you remove a category) the framework
will obtain a
mutex called netfxperf.1.0 so that only one thread in a .NET
application on the system can add or remove perfmon registry
information at a time. However, it will not protect you from unmanaged
applications from changing these values at that time. I think the mutex really
should be obtained by lodctr and unlodctr.
Close this dialog and perfmon and then run the application again,
but this time provide a parameter (server delete, it really does
not matter what you use as the parameter). Now start perfmon and
confirm that the object and counter have been removed. Close perfmon.
Now let's add a counter. Remember that PerformanceCounter
implements IDisposable, hence once we have finished with the
reference we must dispose the object. The class provides the Close
method to do this. Add the following static member:
static PerformanceCounter clients;
static void Main(string[] args)
This member will be available in the Main method and in other
methods in the class. Now add the
following code at the end of the Main
method:
// Provide counters
clients = new PerformanceCounter(catName, ctrName, false);
Random rand = new Random(Environment.TickCount);
int numberOfClients = 5;
Console.WriteLine("Press Ctrl-C to end the server");
while (true)
{
// Simulate clients attaching and detaching
int val = rand.Next(100);
if (val > 66) numberOfClients++;
if (val < 33) numberOfClients--;
if (numberOfClients < 0) numberOfClients = 0;
clients.RawValue = numberOfClients;
System.Threading.Thread.Sleep(100);
}
Note the constructor that is used for PerformanceCounter. This overload has the category name and
the counter name, in addition, there is a third parameter that the
documentation calls readOnly. If you use the constructors without
this parameter then a default value of true will be used, that is,
you will only be able to read the counter through the object. To be able to
write to the counter you must provide a value of false for this
parameter. (But note that you can only write to custom counters, system
provided counters are always read-only.)
The idea behind this code is that there is an everlasting loop that simulates clients
attaching and detaching: a third of the time one client will attach, a third
of the time one client will detach and a third of the time the number of
clients will remain the same. The time period, as you can see, is 100ms. In
this example I write the counter value by assigning the RawValue
property (which is a long, so that it can be used for both 32- or
64-bit counters). There are three other ways that you can provide counter
values: the Increment and Decrement methods will
change the current counter value by one, and the IncrementBy
method will change the counter by the value that you supply (the parameter is
a long so you can provide positive and negative changes.
The
code helpfully points out that if the user presses Ctrl-C they will end the
loop, and so to catch this we provide a CancelKeyPress handler
(note that if you are running .NET 1.1 or 1.0 then you'll have to explicitly
create an instance of ConsolCancelEventHandler, the syntax here
is for C# 2.0). The handler method looks like this:
{
if (clients != null) clients.Close();
Console.WriteLine("Finished");
}
Thus, when the user presses Ctrl-C this handler will be called to dispose the counter.
For this simple example this code is not strictly necessary because without
a handler the process will finish and before that happens the objects in the finalizer queue will be disposed. However, it is good practice to ensure that
you always dispose IDisposable objects, even in cases like this,
because you may decide to extend the application at a later stage, adding more
code after the while loop and then you'll allow the counter object
to live longer than it should do.
|
Compile this code and run the process, it will detect that the counters have not been registered and it will register them. Now run the process again and this time it will go into the loop, providing values for the counter. Start perfmon and add the counter. You will find that the counter will rise and fall between zero and approximately 20 (although there will not be any upper limit).
Now close perfmon and stop the application by pressing Ctrl-C. Unregister the counters by running the process with a parameter. Now let's add a counter that gives the rate of change. Add the following strings:
const string ctrRateName = "# of clients connecting per sec";
const string ctrRateHelp = "The rate that clients connect";
To make the new counter available you have to register it:
PerformanceCounterType.RateOfCountsPerSecond32);
ccdc.Add(ccd);
PerformanceCounterCategory.Create(
catName, catHelp,
PerformanceCounterCategoryType.SingleInstance,
ccdc);
Now add a static member for the new counter:
static PerformanceCounter totalClients;
create the counter in the Main method:
totalClients = new PerformanceCounter(catName, ctrRateName, false);
and dispose it in the Ctrl-C handler:
if (totalClients != null) totalClients.Close();
The idea is that the counter will give a count of the total number of
clients that have ever connected, so the change of this value over time will
give the rate of clients connecting. You do not have to calculate the rate
because you have indicated that the counter is RateOfCountsPerSecond32.
Add the following lines:
|
int numberOfClients = 5; totalClients.RawValue = numberOfClients; Console.WriteLine("Press Ctrl-C to end the server"); |
The totalClients counter keeps a running count of the clients
that have connected, which is why it is initialized with the first value of
numberOfClients (this assumes that before the loop starts in the
real application, five
clients have already "connected"). In the loop, when a client connects (that is
numberOfClients is incremented) the total number of clients is incremented.
As you can see the rate is not calculated here. The rate is calculated by the client
code, in this case, perfmon. The performance monitor will take
consecutive values provided by totalClients and then calculate
the difference and divide by the time between the samples. This gives the
rate that the total number of clients connecting changes over time.
Compile this code and then run it once to register the counters, then run it again to provide the counters. Start perfmon and add both of the counters for the Data Generator object.

In this picture the current number of clients connected is in blue and the rate that they have connected is in red, and a measurement is taken once every second. Since the horizontal scale is time the gradient of the number of clients connected will give the change and hence the rate of clients connecting and disconnecting (a negative gradient represents clients disconnecting). Performance monitor cannot show a negative value so this is why I chose to give the rate of clients connecting (hence, just the positive values).
If you look at the blue line you'll see that whenever it rises the gradient will increase and hence the rate should increase. This results in the rate (the red line) peaking before the blue line. However, the more astute of you will know that when the blue line is horizontal, the rate (and hence the red line) should be zero, but thisis not the case here. Further, when clients disconnect, and hence there is a negative gradient in the blue line, the rate of clients connecting (red line) should also be zero, but in this picture this is not the case.
The reason that the rate is apparently not showing the actual rate is
because the
counters generate values ten times a second, but the fastest that the performance monitor
can take a value is once a second. So during the two measurements that perfmon
makes there could have been, for example, six clients connected and five disconnected, this
would mean that the change in the displayed value for the # connected
clients would change by 1 between the two values, but the actual number
clients connecting during that second was 6 and hence the # of clients
connecting per sec would be 6 (6/1 sec). If it was possible to measure
these values at a rate less than one per second and at the same time as the
data is generated
then you would find that the
# of clients connecting per sec would be equivalent to the
(positive) gradient of the # connected clients counter.
One way
to get a better measure of the number of clients connecting per second is for
the application to keep a time stamp when a client connects, and then when the
next client connects it can use the difference between the times stamps to
calculate the number of connections per second and provide this value though a
counter (but note that the type of this counter should be
NumberOfItems32 so that the client performs no calculations on the
value).
| Bear this in mind when you decide to give your users a rate. It is usually better to calculate the rate yourself - because you have all of the values - than to allow the performance counter client to calculate the rate because the client will almost always have just a subset of the values. Further, for this reason, any statistical calculations performed by the client are meaningless. |
Close perfmon, stop the application and then run it again with a parameter so that it unregisters its counters.
This example used just a single instance, the category itself is registered
as only ever having a single instance with SingleInstance. If you
want to have more than one instance then you should create the category as
MultiInstance and then when you create the counter use the
constructor that takes an instance name. When you have finished with the
instance use the RemoveInstance method to remove it. However,
since the performance counters use shared memory you have to be careful that a
client is not using the instance when you remove it. The safest action is to
remove the instance when the application domain that hosts the counter is
unloading.
7.5 Performance Counter Installers
The previous example showed how to add and remove categories and counters.
However, although these methods worked fine they are inadequate for production
applications. The reason is that an application will have several components
that need to be registered any one of which can fail and hence the installation
mechanism must be
transactional. Imagine that you have an application that has performance
counters, writes to the event log, and provides enterprise services (that is,
integrates with COM+). All of these require registration during installation
but if one of these registrations fails then the installation process
must rollback all changes. Your application must register its counters
to be able to provide them, so there is an incentive to provide registration
information. However, there are few incentives (other than professional pride
in producing good software) to remove the counters when the application is
uninstalled. Before you gasp in disbelief that a developer could behave in
anything other than a professional way, take a look in the registry of your
machine. Glance through HKEY_CLASSES_ROOT at the progIds and
CLSIDs that
have been left there after applications have been uninstalled. They are
useless, they clutter up the registry and they slow down your machine. So be
professional and remove your clutter when you leave.
To help you be a considerate developer the framework provides installers.
The key to these is a utility called InstallUtil. This application is simply a
wrapper around a class called ManagedInstallerClass in the
System.Configuration.Install namespace (and in the assembly with the
same name). You call this utility, passing as the command line parameter, the name of the assembly that you
want to install. The utility uses reflection to
locate and execute installer classes in your assembly. There are a couple of
points to make about this. The first is that the assembly that you provide can
be a library, or it can be a process. This is the one occasion where you are
encouraged to export code from a process.
| A general rule of thumb with .NET is that public types are only exported from libraries. Processes are used to host classes but not to export them. The only situation when a process will export a class is when the process provides a remote objects through .NET remoting, web services or WCF. |
The other point to make is that a process assembly passed to InstallUtil cannot be written with C++ because the compiler will put unmanaged code in the process. The solution is fairly easy: you simply create a C# assembly that contains the installer class.
InstallUtil will look for a class derived from Installer
that has the [RunInstaller(true)] attribute (System.ComponentModel).
Here is the public interface of this class.
public class Installer : Component
{
public event InstallEventHandler AfterInstall;
public event InstallEventHandler AfterRollback;
public event InstallEventHandler AfterUninstall;
public event InstallEventHandler BeforeInstall;
public event InstallEventHandler BeforeRollback;
public event InstallEventHandler BeforeUninstall;
public event InstallEventHandler Committed;
public event InstallEventHandler Committing;
public Installer();
public virtual void Commit(IDictionary savedState);
public virtual void Install(IDictionary stateSaver);
public virtual void Rollback(IDictionary savedState);
public virtual void Uninstall(IDictionary savedState);
private void WriteEventHandlerError(string severity, string
eventName, Exception e);
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
Browsable(false)]
public InstallContext Context { get; set; }
[ResDescription("Desc_Installer_HelpText")]
public virtual string HelpText { get; }
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public InstallerCollection Installers { get; }
[TypeConverter(typeof(InstallerParentConverter)), Browsable(true),
ResDescription("Desc_Installer_Parent"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Installer Parent { get; set; }
}
Parent and Installers.
These give a clue about how this class is used. The Installers
collection is one or more instances of classes derived from Installer, each of
which will have a back reference to the 'parent' installer that holds the
collection of installers.
When you create an installer assembly you need to create a top level
'collection' class. To do this you derive a class from Installer
and mark it with [RunInstaller(true)]
then in the new class's constructor you create instances of the actual
installer classes (which are derived, directly, or indirectly, from Installer) and add them
to the Installers collection. You can derive your own installer
class from Installer, but in general (because it is the easiest
thing to do) you should reuse the classes provided by the framework.
| Class | Description |
|---|---|
AssemblyInstaller |
Loads an assembly and runs all the installers that it contains. This is used by the framework. |
ComponentInstaller |
Abstract base class for component installers |
DefaultManagementProjectInstaller |
Default project installer for assemblies that contain management instrumentation and do not use other installers . |
EngineInstaller |
Installs a PowerShell engine |
EventLogInstaller |
Installs event logs |
Installer |
Base class for all installers. Provides the basic infrastructure of maintaining logs and calling installation and uninstallation methods on all installers. |
ManagementInstaller |
Installs instrumented assemblies. |
MessageQueueInstaller |
Installs message queues. |
PerformanceCounterInstaller |
Installs performance counters and categories. |
PSInstaller |
Base class for the PowerShell installers |
PSSnapInInstaller |
Installs PowerShell snap-ins |
ServiceInstaller |
Installs a service |
ServiceProcessInstaller |
Installs a process that contains one or more services |
TransactedInstaller |
Provides extra code to make installation transactional, this is used by the InstallUtil tool which means that you don't have to use it. |
As mentioned previously, when the
InstallUtil utility is run to install an application it will locate the installer collection class, a class
derived from Installer with the [RunInstaller(true)]
attribute. However, it does not call your class directly, instead, it creates
an instance of TransactedInstaller and adds your installer
collection class to this new instance. The difference between Installer and TransactedInstaller
is that while both classes keep a record of the installation, including
errors, if one of the installers in the Installer class throws an
exception the installation will stop, whereas if an installer throws an
exception when run by the TransactedInstaller class then the
installation will be rolled back with a call to Rollback which
will call the
Uninstall method of all the installers that have been run.
During installation, the installer collection class will create a Hashtable
and pass this to the Install method of each installer in the
Installers collection, this parameter is used by
the method to indicate the components that have been installed. If an
installer fails to do it's work, it will throw an exception. The
TransactedInstaller object catches this exception and then calls the
Rollback method on each installer passing the Hashtable
that was created during the installation so that the installer can undo the
work that has done. If the installation is successful on all of the
installers, then the TransactedInstaller object calls
Commit on all of the installers which can be used to make the
installation final. All the methods (Install, Uninstall,
Rollback, Commit) have access to an installation
context object (an instance of InstallContext) which has information about log files.
Using the InstallUtil tool is simple, just call it passing the name of
the assembly with the installer collection class to perform an installation,
and add the /u parameter to uninstall the code.
The class for installing performance counters is called
PerformanceCounterInstaller, here is the public interface:
{
public PerformanceCounterInstaller();
public override void CopyFromComponent(IComponent component);
public override void Install(IDictionary stateSaver);
public override void Rollback(IDictionary savedState);
public override void Uninstall(IDictionary savedState);
[TypeConverter("System.Diagnostics.Design.StringValueConverter,
System.Design, "
+ "Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"),
DefaultValue(""), ResDescription("PCCategoryName")]
public string CategoryName { get; set; }
[DefaultValue(""), ResDescription("PCI_CategoryHelp")]
public string CategoryHelp { get; set; }
[ComVisible(false), DefaultValue(-1),
ResDescription("PCI_IsMultiInstance")]
public PerformanceCounterCategoryType CategoryType { get; set; }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
ResDescription("PCI_Counters")]
public CounterCreationDataCollection Counters { get; }
[ResDescription("PCI_UninstallAction"), DefaultValue(0)]
public UninstallAction UninstallAction { get; set; }
}
CategoryName and
CategoryHelp with the appropriate strings and give the
CategoryType a value to determine if the category has a single instance or
multiple instances. You then create one or more CounterCreationData
objects (as you saw earlier) to give information about the counters to
install. The UninstallAction property is used to indicate if the
counter should be removed when the application is uninstalled and the default
vale is
that the counters should be removed.
| I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com. |
Errata
If you see an error on this page, please contact me and I will fix the problem.