Are you a terminating process? Then please pay the toll…

  Recently, I had the chance to participate in a performance review of a Line of Business application which dynamically creates portable document files (PDF, in short) which are then sent to the web user over the wire. The process of PDF creation has several steps which include some database querying, some file system operations and then a process is spawn, which is responsible for the creation of the output.

  Both the database querying steps and the file system operations can be instrumented to a degree which enables us to measure their performance. However, the high frequency of process spawning and termination makes it hard to figure out the slice of the processing time spent by these processes. Of course, you could measure everything else and then, by subtracting the overall processing time (if available), you’d get a pretty good figure for that matter, assuming no other variables would exist.

  Fortunately, there seems to be a cool solution for this. As if processes were cars, make them collect a ticket when entering the highway and then paying the toll when exiting. Great! But who’s the authority and where will you put your plazas? The answer (the same for many, many questions if you’re into IT management) is Windows Management Instrumentation . With WMI, not only can you query for the current state of an object in a Windows-based system, but you can also subscribe events such as the creation, modification and deletion of those same objects. By using this mechanism, you’ll have a dynamic view over your system, instead of discrete samples, which would only luckily capture the information you needed.

  So, how does it work? Well, take a look into the following script and I’ll dive into each fragment, so as to ensure that it may be of use as a template for your specific needs.

Set htProcesses = CreateObject("Scripting.Dictionary")

Dim ticket(2)

Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")

Set colMonitoredProcesses = objWMIService. _

    ExecNotificationQuery("select * from __instanceoperationevent " _

        & " within 10 where TargetInstance isa 'Win32_Process'")

i = 0

Do While i = 0

    Set objLatestEvent = colMonitoredProcesses.NextEvent

    Select Case objLatestEvent.Path_.Class

  

      Case "__InstanceCreationEvent"

        ticket(0) = objLatestEvent.TargetInstance.ProcessId

        ticket(1) = objLatestEvent.TargetInstance.Name

        ticket(2) = objLatestEvent.TIME_CREATED

        htProcesses.add objLatestProcess.TargetInstance.ProcessId, ticket

      Case "__InstanceDeletionEvent"

        pid = objLatestEvent.TargetInstance.ProcessId

        if htProcesses.Exists(pid) then

        Wscript.echo (htProcesses(pid)(0) & " " & _

                      htProcesses(pid)(1) & " " & _

                      htProcesses(pid)(2) & " " & _

                      objLatestEvent.TIME_CREATED & " " & _

                      objLatestEvent.TIME_CREATED - htProcesses(pid)(2))

        end if

    End Select

Loop

  Confused? Well, great! It only means you tried to figure it out. So, let’s dive into it and try to understand the details.

Set htProcesses = CreateObject("Scripting.Dictionary")

Dim ticket(2)

  These two lines instantiate the supporting data model for the script. We’ll be needing both an array for holding each process data upon creation – getting back to our metaphor, this will be a ticket the driver gets when entering the highway – and a hashtable for keeping all processes information – this would be the highway authority database, with enough information about all cars currently driving through the highway.

Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")

Set colMonitoredProcesses = objWMIService. _

    ExecNotificationQuery("select * from __instanceoperationevent " _

        & " within 10 where TargetInstance isa 'Win32_Process'")

  These lines create the connection to WMI, by subscribing the operation events – creation, modification and termination – for Windows processes. Think of it as placing your plazas on every entrance and exit of your highway. In fact, by also subscribing to modification events, you’re also getting information on your cars as they move along the highway. These events will be ignored, as you’ll see. The plazas are placed, we’re set to go!

i = 0

Do While i = 0

    Set objLatestEvent = colMonitoredProcesses.NextEvent

    […]

Loop

  Here’s the endless loop in which the information is gathered and displayed as output. Of course, the script can be canceled and the loop will end. Next, let’s take a look at the handling of process creation.

Select Case objLatestEvent.Path_.Class

  

      Case "__InstanceCreationEvent"

        ticket(0) = objLatestEvent.TargetInstance.ProcessId

        ticket(1) = objLatestEvent.TargetInstance.Name

        ticket(2) = objLatestEvent.TIME_CREATED

        htProcesses.add objLatestEvent.TargetInstance.ProcessId, ticket

  The first line above will decide what the event is about. As you’ve seen, we are subscribing events belonging to the WMI class __instanceoperationevent . This class is the superclass of three other classes - __instancecreationevent , __instancemodificationevent and __instancedeletionevent – so we’re, in fact, getting events of these specialized classes.

  By having the code look into the actual class of the event instance being handled ( objLatestEvent.Path_.Class ), you can decide what to do with that event. In case of process creation, we need to add the process information to the ticket array. If it is a process termination, the following code will print out the data, along with the time span between creation and termination in nanoseconds. As the process ID is known when it is terminating, we can look it up in the hashtable and extract the information we added when that process was started.

      Case "__InstanceDeletionEvent"

        pid = objLatestEvent.TargetInstance.ProcessId

        if htProcesses.Exists(pid) then

        Wscript.echo (htProcesses(pid)(0) & " " & _

                      htProcesses(pid)(1) & " " & _

                      htProcesses(pid)(2) & " " & _

                      objLatestEvent.TIME_CREATED & " " & _

                      objLatestEvent.TIME_CREATED - htProcesses(pid)(2))

        end if

    End Select

 

  The car has exited the highway and the ticket is paid. Hopefully, things are clearer now. A couple of remarks, though:

· The first one is that these classes representing events do not have the TIME_CREATED attribute (a value that represents the number of 100-nanosecond intervals after January 1, 1601) in Windows 2000 and older operating systems.

· The second one is that you’re subscribing these events forever, but if you cancel the script, there will still be a subscription hanging around. Be sure to use this script (or its variants, according to your needs) with moderation, or you will risk overburdening your system.

  Of course, you could be using Windows PowerShell 2.0 (or its latest CTP , by the time this post was written) and you’d be able to use things such as Eventing , which are great, as you can see below.

$ht = New-Object System.Collections.Hashtable

$query = New-Object System.Management.WqlEventQuery "__InstanceOperationEvent",

   (New-Object TimeSpan 0,0,1),

   "TargetInstance isa 'Win32_Process'"

$watcher = New-Object System.Management.ManagementEventWatcher $query

Register-ObjectEvent $watcher "EventArrived" -SupportEvent "WMI.ProcessCreated" -Action {

   [void] (New-PsEvent "PowerShell.ProcessEvent" -Sender $args[0] -EventArguments $args[1].SourceEventArgs.NewEvent)

}

Register-PsEvent "PowerShell.ProcessEvent" -Action {

  $process = $args[1].SourceArgs[0].TargetInstance

  switch ($args[1].SourceArgs[0].__CLASS)

  {

    "__InstanceCreationEvent" {

      $ht[$process.ProcessId] = $process.Name, ([WMI]'').ConvertToDateTime($process.CreationDate), $args[1].SourceArgs[0].TIME_CREATED

    }

    "__InstanceDeletionEvent" {

      Write-Host $ht[$process.ProcessId][0],

        $ht[$process.ProcessId][1].ToString("HH:mm:ss.fff"),

        ([System.Convert]::ToDecimal($args[1].SourceArgs[0].TIME_CREATED) - [System.Convert]::ToDecimal($ht[$process.ProcessId][2]))

    }

  }

}

  I won’t be getting into the details, as this is using pretty much the same principles stated for the VBScript version. One thing is sure, though: I’m definitely moving towards PowerShell. If that is also your way, be sure to make The PowerShell Guy your best friend!

 

  See you soon,

 

    Manuel Oliveira