PerformanceCounters and Parent PID

One of my tools needed to track if its parent process was a shell or not. I found it very difficult to find parent process ID (PID) info without traversing into p/invoke territory. I have no idea why the .NET Process type does not include the info - seems like an easy thing, to me.

Anyway, what followed was my first peek at PerformanceCounters - there is some crazy stuff in there.

Investigation

Everything I could find related to PID in .NET either pointed to ntdll.dll calls or to PerformanceCounter. Avoiding the extern, I delved into the counters. First off, I had no idea what counters there were and all the accessors for them are by string for the counter name and its category. I also found out that some counters are singletons while other can have multiple instances.

I decided to write a quick script to dump them all.

|h PrintAllPerformanceCounters.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
 
namespace PidExamples
{
    class PrintAllPerformanceCounters
    {
        static void Main(string[] args)
        {
            ProcessUtil.PrintCategories();
        }
 
        public static void PrintCategories()
        {
            var categories = System.Diagnostics.PerformanceCounterCategory.GetCategories();
 
            for (int i = 0; i < categories.Length; i++)
            {
                PrintCounters(categories[i].CategoryName);
            }
        }
 
        public static void PrintCounters(string categoryName)
        {
            var counters = new ArrayList();
            var category = new PerformanceCounterCategory(categoryName);
 
            try
            {
                var instanceNames = category.GetInstanceNames();
                if (instanceNames.Length == 0)
                {
                    counters.AddRange(category.GetCounters());
                }
                else
                {
                    for (int i = 0; i < instanceNames.Length; i++)
                    {
                        counters.AddRange(category.GetCounters(instanceNames[i]));
                    }
                }
 
                foreach (System.Diagnostics.PerformanceCounter counter in counters)
                {
                    if (string.IsNullOrEmpty(counter.InstanceName))
                    {
                        Console.WriteLine(categoryName + ": " + counter.CounterName);
                    }
                    else
                    {
                        Console.WriteLine(categoryName + ": " + counter.CounterName + " (" + counter.InstanceName + ")");
                    }
                }
            }
            catch (System.Exception)
            {
                Console.WriteLine(categoryName + ": ?");
            }
        }
    }
}

This produced 13401 lines when I ran it on my desktop - that's far more than I was expecting. There are counters dealing with each processor and disk, as well as all sorts of ones for programs - especially Visual Studio and related dev tools. These are also just the counters - you can query any of them to get the associated data.

Digging through the list I found the lines dealing with Process information.

Process PerformanceCounters (Win 7):
  % Privileged Time
  % Processor Time
  % User Time
  Creating Process ID
  Elapsed Time
  Handle Count
  ID Process
  IO Data Bytes/sec
  IO Data Operations/sec
  IO Other Bytes/sec
  IO Other Operations/sec
  IO Read Bytes/sec
  IO Read Operations/sec
  IO Write Bytes/sec
  IO Write Operations/sec
  Page Faults/sec
  Page File Bytes
  Page File Bytes Peak
  Pool Nonpaged Bytes
  Pool Paged Bytes
  Priority Base
  Private Bytes
  Thread Count 
  Virtual Bytes
  Virtual Bytes Peak
  Working Set
  Working Set - Private
  Working Set Peak

Awesome - there's PID (ID Process) and Parent PID (Creating Process ID). So now we'll just grab performance counters for all the processes and make a quick dictionary of child PID to parent PID. We can then index into that dictionary with our PID (easily accessible from Process.GetCurrentProcess().Id) to get our parent, or we can traverse the dictionary and make a process tree if needed.

|h ParentPID.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
 
namespace PidExamples
{
    class ParentPid
    {
        static void Main(string[] args)
        {
            var childPidToParentPid = GetAllProcessParentPids();
            int currentProcessId = Process.GetCurrentProcess().Id;
 
            Console.WriteLine("Current Process PID: " + currentProcessId);
            Console.WriteLine("Parent Process PID: " + childPidToParentPid[currentProcessId]);
        }
 
        public static Dictionary<int, int> GetAllProcessParentPids()
        {
            var childPidToParentPid = new Dictionary<int, int>();
 
            var processCounters = new SortedDictionary<string, PerformanceCounter[]>();
            var category = new PerformanceCounterCategory("Process");
 
            // As the base system always has more than one process running, 
            // don't special case a single instance return.
            var instanceNames = category.GetInstanceNames();
            foreach(string t in instanceNames)
            {
                try
                {
                    processCounters[t] = category.GetCounters(t);
                }
                catch (InvalidOperationException)
                {
                    // Transient processes may no longer exist between 
                    // GetInstanceNames and when the counters are queried.
                }
            }
 
            foreach (var kvp in processCounters)
            {
                int childPid = -1;
                int parentPid = -1;
 
                foreach (var counter in kvp.Value)
                {
                    if ("ID Process".CompareTo(counter.CounterName) == 0)
                    {
                        childPid = (int)(counter.NextValue());
                    }
                    else if ("Creating Process ID".CompareTo(counter.CounterName) == 0)
                    {
                        parentPid = (int)(counter.NextValue());
                    }
                }
 
                if (childPid != -1 && parentPid != -1)
                {
                    childPidToParentPid[childPid] = parentPid;
                }
            }
 
            return childPidToParentPid;
        }
    }
}    

Note that when we actually start querying, the counters for specific processes may no longer be available. The instance list was a snapshot, and the list is changing all the time, so we need to wrap our check on the specific instance to guard against errors if a process no longer exists. If you are only interested in the PID of the current process, you could speed this up quite a bit by checking for just instances with your executable name.

Results

D:\>ParentPid.exe
Current Process PID: 90820
Parent Process PID: 92712

D:\>ParentPid.exe
Current Process PID: 92636
Parent Process PID: 92712

Conclusion

Well, the information is available. The number of performance counters is crazy to me - it really makes me wonder what kind of overhead goes into tracking and updating them.

In other news, the p/invoke method is much faster, cleaner, and better at tracking processes over time - I'd stick with that direction if you are really depending on this.

 
blog/20110818_performancecounters_and_parent_pid.txt · Last modified: 2011/08/18 20:08 by Jeremy Murray · []
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki