How-To: Make performance monitor variables work

From the rest of this site you'll probably realise that Linux is my preferred operating system. I'm paid to write software for Windows, however. When writing applications for Windows - especially those without any user interface as many of mine are - the Performance monitor application is quite useful. This can be found under Administrative Tools/Performance in the Control Panel.

Jeffrey Richter has a useful article on adding your own variables, but sadly there's a few drawbacks with his sample code. Since I spent ages trying to fix one of them, and couldn't find anything else on the web about it, I'll document that one here.

I'm not documenting fixes for the install/uninstall not being very robust, especially if you add new variables after the initial install, or how to add a null security descriptor so that you don't have to run the app and perfmon as the same user... Maybe one day I'll get round to rewriting the code completely and posting it.

Performance Counters read 0 when used over Terminal Services

If you search the web you might find that various service packs fix this issue for SQL Server, ASP, etc, but none of them are that good at telling you what the problem was so that you can fix it yourself.

To understand the problem you must understand how the application passes information to the performance monitor application. First, perfmon loads your DLL into its address space. It then calls the Open, Collect and Close functions. In Richter's example these work by opening shared memory with CreateFileMapping. CreateFileMapping creates an area of shared memory, which both the application and the DLL refer to by name. The name here is the name of your application.

The shared memory is one example of a kernel object. Others include mutexes, etc. When Terminal Services is used, each Terminal Service session has its own namespace, so when your application creates a shared memory area to write its counters to, it does this in one namespace, and when perfmon is run, it doesn't use the same memory, instead it creates a new shared memory object in its own namespace. This explains why if you write an app to read the perfmon variables yourself and run it over terminal services you don't get any errors, it just reads 0.

Once you know this, it's quite easy to fix - all you need to do is:
WCHAR globalappname[128];
wsprintf(globalappname, L"Global\\%s", m_pszAppName);

and then use globalappname in the CreateFileMapping call instead of m_pszAppName. This will cause the shared memory to be created and read from the global namespace. (If the fixed size buffer and unchecked sprintf make you nervous, then you might want to look through the rest of the example code - there were already instances where it breaks on appnames longer than 100 chars.)

I hope that helps someone else. It took me ages to work out exactly what was going on!

Posted in