Although the performance counters built into the CLR give you a pretty good handle on what’s going, there’s nothing quite like having your own counters to help you monitor your applications performance. There’s nothing quite like being able to see your own counters alongside the build in ones in perfmon. There are several things that make this a little tricky, first you must install your counter to make it visible to perfmon, then you need to create instances of the counters for the application to use, finally you need to remove your counters when there done with to ensure old instances don’t clog up the perfmon dialogs.
So let’s take a look at how we do all that, starting with the installation of the counters. To install the counters first we need define what counters we wish to install, this is done using the CounterCreationData class:
/// Short had function for creating counters installation information
let counterCreationData name ctype = new CounterCreationData(name, "", ctype)
/// Definitions of our counters installation information
let antCounters =
[| counterCreationData "Frames per second" PerformanceCounterType.RateOfCountsPerSecond64;
counterCreationData "Messages per second" PerformanceCounterType.RateOfCountsPerSecond64;
counterCreationData "Ants" PerformanceCounterType.NumberOfItems64|]
Fairly straight forward, the most important thing being the choice of PreformanceCounterType, here we create two counters that measure the rate per second for us and one that simply keeps a running total.
Next we come installing the counters; this is where things start to get a little, well if not tricky, then at least a little subtle:
/// Install our counters
let installCounters() =
if PerformanceCounterCategory.Exists("Ant Colony Stats") then
PerformanceCounterCategory.Delete("Ant Colony Stats")
PerformanceCounterCategory.Create("Ant Colony Stats",
"",
PerformanceCounterCategoryType.MultiInstance,
new CounterCreationDataCollection(antCounters))
|> ignore
// Install the counters if necessary
if Sys.argv.Length > 1 && Sys.argv.[1] = "--install-counters" then
installCounters()
It’s important to note that we check if our counters exist, then if they do we delete them before installing them. It may be tempting to check if the counters don’t exist and only create them if they do not exist, but this will make it difficult to reinstall your counters because there definition has changed. Also installation is a privileged operation that requires you to be local admin (not unreasonable as we’re changing machine wide settings), this means it’s advisable to only install your counters if a command line flag or other signal is given, we see this check at the end of the above code. Actually counters really should be installed at application installation time, but here we’ll do it in code since I have no intention of creating an install for this application.
Now for the subtlies, the first part is fairly obvious: the installation code must take place before the counters are initialized, but it’s tempting to reverse this organisation because of the way F# code is scoped. You want to declare your counters in global scope at the top of the module so you can use them throughout the module, but you want your installation code to run as part of your main function, which generally is place at the bottom of the code and executes after the items in global scope. This is why the test to see if the counters should be installed has been placed in at the top level. The second subtly relates to “PerformanceCounterCategoryType”, you probably want to “MultiInstance” but “SingleInstance” is easier to use. With SingleInstance you get one machine wide counter, which is okay but you probably want your counter to be scoped to a specific process. “MultiInstance” allows you to have as many instances as you like, but they are also machine wide and not scoped to specific process, you have to manage your various different instances by name and remember to delete them when you’re done with them.
We’ll see the effect of this in our next block of code:
/// Create an instance of the counter to be used, returning None if the counter
/// is not available because it has not been installed or otherwise
let createCounter name =
try
let curProc = Process.GetCurrentProcess()
Some(new PerformanceCounter("Ant Colony Stats", name, curProc.Id.ToString(), false))
with ex ->
printfn "Warning: failed to initalize counter \"%s\", the exception was: %s" name ex.Message
None
Here we create an instance of the counter, passing in the process id as the instance name so it is scoped to the current process. The other thing that’s important to notice is that if the counter creation fails we print a warning and return None, I think this is important for a robust app: we don’t want the application itself to fail simple because the performance counters are not installed.
The effect of this is that we probable want to define some functions to deal with our counters, to avoid having to perform the some/none test all the time. So we create some instances of the counters and some fire functions that have the semantics, if the counter exists fire the counter otherwise do nothing:
/// Instances of the counters
let framesCount = createCounter "Frames per second"
let messagesCounter = createCounter "Messages per second"
let antsCounter = createCounter "Ants"
/// Fire the counters or not depending on whether they exist
let fireCounter (counter:option<PerformanceCounter>) =
match counter with
| Some x -> x.Increment() |> ignore
| _ -> ()
/// Fire the counters
let fireFrames() = fireCounter framesCount
let fireMessages() = fireCounter messagesCounter
let fireAnts() = fireCounter antsCounter
Finally, to stop old instance of the counters clogging up the perfmon dialog we need to remove the counters when the process exits:
/// Remove the counter depending on whether it exists
let removeInstance (counter:option<PerformanceCounter>) =
match counter with
| Some x -> x.RemoveInstance()
| _ -> ()
// remove the counters when the domain unloads (when the process exists)
AppDomain.CurrentDomain.DomainUnload.Add(fun _ ->
removeInstance framesCount
removeInstance messagesCounter
removeInstance antsCounter)
Okay our counters don’t do anything for now, but we can already see them in perfmon.
That’s it for today. You may be able to guess what the next post will be from the names of the counters.