static void Main(string[] args)
    {
        bool   showCPUUsage = false;
        string traceName    = "";

        foreach (string arg in args)
        {
            if (arg == "-c" || arg == "--cpuusage")
            {
                showCPUUsage = true;
            }
            else if (traceName.Length == 0)
            {
                traceName = arg;
            }
            else
            {
                Console.Error.WriteLine("error: unrecognized arguments: {0}", arg);
                return;
            }
        }

        if (traceName.Length == 0)
        {
            Console.Error.WriteLine("usage: IdentifyChromeProcesses.exe [-c] trace");
            Console.Error.WriteLine("error: too few arguments");
            return;
        }

        if (!File.Exists(traceName))
        {
            Console.Error.WriteLine("File '{0}' does not exist.", traceName);
            return;
        }

        try
        {
            using (ITraceProcessor trace = TraceProcessor.Create(traceName))
                ProcessTrace(trace, showCPUUsage, false);
        }
        catch (System.InvalidOperationException e)
        {
            // Note that wpaexporter doesn't seem to have a way to handle this,
            // which is one advantage of TraceProcessing. Note that traces with
            // lost events are "corrupt" in some sense so the results will be
            // unpredictable.
            Console.WriteLine(e.Message);
            Console.WriteLine("Trying again with AllowLostEvents and AllowTimeInversion specified. Results may be less reliable.");
            Console.WriteLine();

            var settings = new TraceProcessorSettings();
            settings.AllowLostEvents    = true;
            settings.AllowTimeInversion = true;
            using (ITraceProcessor trace = TraceProcessor.Create(traceName, settings))
                ProcessTrace(trace, showCPUUsage, true);
        }
    }
    public static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.Error.WriteLine("Usage: FindZombieProcess.exe <trace.etl>");
            return(1);
        }

        string tracePath = args[0];

        TraceProcessorSettings settings = new TraceProcessorSettings {
            AllowLostEvents = true
        };

        using (ITraceProcessor trace = TraceProcessor.Create(tracePath, settings))
        {
            IPendingResult <IHandleDataSource> pendingHandleData = trace.UseHandles();
            IPendingResult <ISymbolDataSource> pendingSymbolData = trace.UseSymbols();

            trace.Process();

            IHandleDataSource handleData = pendingHandleData.Result;
            ISymbolDataSource symbolData = pendingSymbolData.Result;

            symbolData.LoadSymbolsForConsoleAsync(SymCachePath.Automatic, SymbolPath.Automatic).GetAwaiter().GetResult();

            foreach (IProcessHandle processHandle in handleData.ProcessHandles)
            {
                // Zombie processes are processes which have exited but which still have a running process holding a handle to them
                if (processHandle.Process != null && !processHandle.CloseTime.HasValue &&
                    processHandle.Process.ExitTime.HasValue)
                {
                    string owningProcessName = processHandle.Owner?.ImageName ?? "Unknown";
                    string targetProcessName = processHandle.Process?.ImageName ?? "Unknown";
                    Console.WriteLine($"Owning process: {owningProcessName} has handle to: {targetProcessName}");
                }
            }

            return(0);
        }
    }
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("Specify the name of one trace to be summarized.");
                return;
            }

            var traceName = args[0];

            if (!File.Exists(traceName))
            {
                Console.Error.WriteLine("File '{0}' does not exist.", traceName);
                return;
            }

            var settings = new TraceProcessorSettings
            {
                // Don't print a setup message on first run.
                SuppressFirstTimeSetupMessage = true
            };

            using (ITraceProcessor trace = TraceProcessor.Create(traceName, settings))
            {
                // Get process details, including command lines.
                var pendingProcessData = trace.UseProcesses();
                // Get CPU performance counters, on every context switch.
                var pendingCounterData = trace.UseProcessorCounters();

                trace.Process();

                var processData = pendingProcessData.Result;
                var counterData = pendingCounterData.Result;

                var countersByProcess = FindInterestingProcesses(processData);

                // Accumulate data for all of the interesting processes.
                foreach (var entry in counterData.ContextSwitchCounterDeltas)
                {
                    // This sometimes happens - handle it.
                    if (entry.Process == null)
                    {
                        continue;
                    }

                    Counters last;
                    if (!countersByProcess.TryGetValue(entry.Process, out last))
                    {
                        continue;
                    }

                    // Accumulate counter values and execution time.
                    foreach (var key in entry.RawCounterDeltas.Keys)
                    {
                        last.counters.TryGetValue(key, out ulong lastCount);
                        lastCount         += entry.RawCounterDeltas[key];
                        last.counters[key] = lastCount;
                    }
                    last.runTime_ns      += (entry.StopTime - entry.StartTime).Nanoseconds;
                    last.contextSwitches += 1;

                    countersByProcess[entry.Process] = last;
                }

                // Sort the data by CPU time and print it.
                var sortedCounterData = new List <KeyValuePair <IProcess, Counters> >(countersByProcess);
                sortedCounterData.Sort((x, y) => y.Value.runTime_ns.CompareTo(x.Value.runTime_ns));

                bool printHeader = true;
                foreach (var entry in sortedCounterData)
                {
                    if (printHeader)
                    {
                        Console.Write("{0,-29} - CPU time (s) - context switches", "Image name");
                        foreach (var counterName in entry.Value.counters.Keys)
                        {
                            int fieldWidth = Math.Max(13, counterName.Length);
                            Console.Write(", {0}", counterName.PadLeft(fieldWidth));
                        }
                        Console.WriteLine();
                        printHeader = false;
                    }

                    // Arbitrary cutoff for what is "interesting"
                    if (entry.Value.runTime_ns < 100 * 1000 * 1000)
                    {
                        continue;
                    }

                    Console.Write("{0,-29} -     {1,8:0.00} - {2,16}", entry.Value.description,
                                  entry.Value.runTime_ns / 1e9, entry.Value.contextSwitches);
                    foreach (var counterName in entry.Value.counters.Keys)
                    {
                        int fieldWidth = Math.Max(13, counterName.Length);
                        Console.Write(", {0}",
                                      entry.Value.counters[counterName].ToString().PadLeft(fieldWidth));
                    }
                    Console.WriteLine();
                }
            }
        }
示例#4
0
    static void Main(string[] args)
    {
        string traceName = null;

        string[] moduleList = null;
        for (int i = 0; i < args.Length; /**/)
        {
            if (args[i] == "-modules")
            {
                ++i;
                if (i >= args.Length)
                {
                    Console.Error.WriteLine("Missing module list after -modules.");
                    return;
                }
                moduleList = args[i++].Split(';');
            }
            else
            {
                if (traceName != null)
                {
                    Console.Error.WriteLine("Unexpected argument '{0}'", args[i]);
                    return;
                }
                traceName = args[i++];
            }
        }

        if (traceName == null)
        {
            Console.Error.WriteLine("usage: gWindowsETLSummary.exe trace.etl [-modules module1.dll;module2.dll");
            Console.Error.WriteLine("error: too few arguments");
            Console.Error.WriteLine("The (case sensitive) -modules arguments are used to get inclusive CPU sampling data.");
            return;
        }

        if (!File.Exists(traceName))
        {
            // Print a more friendly error message for this case.
            Console.Error.WriteLine("File '{0}' does not exist.", traceName);
            return;
        }

        Console.WriteLine("Processing {0}...", traceName);
        var settings = new TraceProcessorSettings
        {
            // Don't print a setup message on first run.
            SuppressFirstTimeSetupMessage = true
        };

        try
        {
            using (ITraceProcessor trace = TraceProcessor.Create(traceName, settings))
                ProcessTrace(trace, moduleList);
        }
        catch (TraceLostEventsException e)
        {
            // Note that wpaexporter doesn't seem to have a way to handle this,
            // which is one advantage of TraceProcessing. Note that traces with
            // lost events are "corrupt" in some sense so the results will be
            // unpredictable.
            Console.WriteLine(e.Message);
            Console.WriteLine("Trying again with AllowLostEvents specified. Results may be less reliable.");
            Console.WriteLine();

            settings.AllowLostEvents = true;
            using (ITraceProcessor trace = TraceProcessor.Create(traceName, settings))
                ProcessTrace(trace, moduleList);
        }
    }
示例#5
0
        private static int Main(string[] cmdLineArgs)
        {
            ParserResult <CommandLineArguments> o = Parser.Default.ParseArguments <CommandLineArguments>(cmdLineArgs);

            return(o.MapResult(
                       options =>
            {
                string sidecarJson = File.ReadAllText(options.SideCarFile);
                CLogSidecar sidecar = CLogSidecar.FromJson(sidecarJson);


                TextReader file = Console.In;

                if (!File.Exists(options.ETLFile))
                {
                    TraceLine(TraceType.Err, $"ETL File {options.ETLFile} doesnt exist");
                    return -1;
                }

                StreamWriter outputfile = null;
                if (!String.IsNullOrEmpty(options.OutputFile))
                {
                    outputfile = new StreamWriter(new FileStream(options.OutputFile, FileMode.Create));
                }

                try
                {
                    TraceProcessorSettings traceSettings = new TraceProcessorSettings {
                        AllowLostEvents = true, AllowTimeInversion = true
                    };

                    using (ITraceProcessor etwfile = TraceProcessor.Create(options.ETLFile, traceSettings))
                    {
                        HashSet <Guid> ids = new HashSet <Guid>();

                        foreach (var m in sidecar.EventBundlesV2)
                        {
                            foreach (var prop in m.Value.ModuleProperites)
                            {
                                if (prop.Key.Equals("MANIFESTED_ETW"))
                                {
                                    ids.Add(new Guid(prop.Value["ETW_Provider"]));
                                }
                                else if (prop.Key.Equals("TRACELOGGING"))
                                {
                                    ids.Add(new Guid(prop.Value["ETW_Provider"]));
                                }
                            }
                        }

                        var events = etwfile.UseGenericEvents(ids.ToArray());
                        etwfile.Process();

                        foreach (var e in events.Result.Events)
                        {
                            string line = "";

                            try
                            {
                                Dictionary <string, IClogEventArg> fixedUpArgs = new Dictionary <string, IClogEventArg>();
                                string errorString = "ERROR";

                                if (null == e.Fields)
                                {
                                    continue;
                                }

                                Dictionary <string, IClogEventArg> args = new Dictionary <string, IClogEventArg>();

                                foreach (var f in e.Fields)
                                {
                                    args[f.Name] = new ManifestedETWEvent(f);
                                }

                                CLogDecodedTraceLine bundle = null;
                                int eidAsInt = -1;

                                foreach (var b in sidecar.EventBundlesV2)
                                {
                                    Dictionary <string, string> keys;

                                    if (!e.IsTraceLogging)
                                    {
                                        if (!b.Value.ModuleProperites.TryGetValue("MANIFESTED_ETW", out keys))
                                        {
                                            continue;
                                        }

                                        string eid;

                                        if (!keys.TryGetValue("EventID", out eid))
                                        {
                                            continue;
                                        }

                                        eidAsInt = Convert.ToInt32(eid);

                                        if (eidAsInt == e.Id)
                                        {
                                            bundle = b.Value;
                                            errorString = "ERROR:" + eidAsInt;
                                            break;
                                        }
                                    }
                                    else
                                    {
                                        if (e.ActivityName.Equals(b.Key))
                                        {
                                            bundle = b.Value;
                                            errorString = "ERROR:" + b.Key;
                                            break;
                                        }
                                    }
                                }

                                if (null == bundle)
                                {
                                    continue;
                                }

                                Dictionary <string, string> argMap;

                                if (e.IsTraceLogging)
                                {
                                    argMap = new Dictionary <string, string>();
                                    foreach (var arg in args)
                                    {
                                        argMap[arg.Key] = arg.Key;
                                    }
                                }
                                else
                                {
                                    argMap = sidecar.GetTracelineMetadata(bundle, "MANIFESTED_ETW");
                                }

                                var types = CLogFileProcessor.BuildTypes(sidecar.ConfigFile, null, bundle.TraceString, null, out string clean);

                                if (0 == types.Length)
                                {
                                    errorString = bundle.TraceString;
                                    goto toPrint;
                                }

                                int argIndex = 0;

                                foreach (var type in types)
                                {
                                    var arg = bundle.splitArgs[argIndex];
                                    CLogEncodingCLogTypeSearch node = sidecar.ConfigFile.FindType(arg);

                                    switch (node.EncodingType)
                                    {
                                    case CLogEncodingType.Synthesized:
                                        continue;

                                    case CLogEncodingType.Skip:
                                        continue;
                                    }

                                    string lookupArgName = argMap[arg.VariableInfo.SuggestedTelemetryName];

                                    if (!args.ContainsKey(lookupArgName))
                                    {
                                        Console.WriteLine($"Argmap missing {lookupArgName}");
                                        throw new Exception("InvalidType : " + node.DefinationEncoding);
                                    }

                                    if (0 != node.DefinationEncoding.CompareTo(type.TypeNode.DefinationEncoding))
                                    {
                                        Console.WriteLine("Invalid Types in Traceline");
                                        throw new Exception("InvalidType : " + node.DefinationEncoding);
                                    }

                                    fixedUpArgs[arg.VariableInfo.SuggestedTelemetryName] = args[lookupArgName];
                                    ++argIndex;
                                }

                                toPrint:

                                EventInformation ei = new EventInformation();
                                ei.Timestamp = e.Timestamp.DateTimeOffset;
                                ei.ProcessId = e.ProcessId.ToString("x");
                                ei.ThreadId = e.ThreadId.ToString("x");
                                DecodeAndTraceToConsole(outputfile, bundle, errorString, sidecar.ConfigFile, fixedUpArgs, ei, options.ShowTimestamps, options.ShowCPUInfo);
                            }
                            catch (Exception)
                            {
                                Console.WriteLine($"Invalid TraceLine : {line}");
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    CLogConsoleTrace.TraceLine(TraceType.Err, "ERROR : " + e);
                    if (null != outputfile)
                    {
                        outputfile.WriteLine("ERROR : " + e);
                    }
                }
                finally
                {
                    if (null != outputfile)
                    {
                        outputfile.Flush();
                        outputfile.Close();
                    }
                }
                return 0;
            }, err =>
            {
                Console.WriteLine("Bad Args : " + err);
                return -1;
            }));
        }
示例#6
0
        static int Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.Error.WriteLine("Usage: PotentialDelayLoads.exe <trace.etl>");
                return(1);
            }

            string tracePath = args[0];

            var settings = new TraceProcessorSettings
            {
                AllowLostEvents = true,
            };

            using (ITraceProcessor trace = TraceProcessor.Create(tracePath, settings))
            {
                IPendingResult <IReferenceSetDataSource> pendingReferenceSet  = trace.UseReferenceSetData();
                IPendingResult <IProcessDataSource>      pendingProcesses     = trace.UseProcesses();
                IPendingResult <ISymbolDataSource>       pendingSymbols       = trace.UseSymbols();
                IPendingResult <IImageSectionDataSource> pendingImageSections = trace.UseImageSections();

                trace.Process();

                IProcessDataSource      processData      = pendingProcesses.Result;
                IReferenceSetDataSource referenceSetData = pendingReferenceSet.Result;
                ISymbolDataSource       symbolData       = pendingSymbols.Result;
                IImageSectionDataSource imageSectionData = pendingImageSections.Result;

                symbolData.LoadSymbolsForConsoleAsync(SymCachePath.Automatic, SymbolPath.Automatic).GetAwaiter().GetResult();

                //
                // Create a mapping of all static images loaded into all processes during the course of the trace.
                // This is a mapping of images to a dictionary of [processes, IsPotentialDelayLoadTarget]
                //
                Dictionary <string, Dictionary <string, bool> > potentialDelayLoads = new Dictionary <string, Dictionary <string, bool> >();

                //
                // Keep track of the image data for all of the images we've seen loaded. We use this later to look up
                // section names for the offsets being accessed.
                //
                Dictionary <string, IImage> imageData = new Dictionary <string, IImage>();

                foreach (var proc in processData.Processes)
                {
                    foreach (var image in proc.Images)
                    {
                        string processName = GenerateProcessNameString(proc);
                        if (image.LoadTime != null)
                        {
                            Dictionary <string, bool> processDict;
                            if (!potentialDelayLoads.ContainsKey(image.Path))
                            {
                                processDict = new Dictionary <string, bool>();
                                potentialDelayLoads.Add(image.Path, processDict);
                            }
                            else
                            {
                                processDict = potentialDelayLoads[image.Path];
                            }

                            if (!processDict.ContainsKey(processName))
                            {
                                bool eligibleForDelayLoad = (image.LoadReason == ImageLoadReason.StaticDependency);
                                processDict.Add(processName, eligibleForDelayLoad);
                            }

                            //
                            // Save off whether or not this image is a potential delay load target. We only consider
                            // static dependencies for delay loads.
                            //
                            processDict[processName] = processDict[processName] && (image.LoadReason == ImageLoadReason.StaticDependency);

                            //
                            // Save off a pointer to the image data for this image so we can look up sections later
                            //
                            if (!imageData.ContainsKey(image.Path))
                            {
                                imageData.Add(image.Path, image);
                            }
                        }
                    }
                }

                //
                // Enumerate every page access. We're going to check each one to see if it was a 'code' page being accessed,
                // and if it was we conclude that code from this image was used during the trace by that process. Therefore,
                // it's not something that should be delay loaded.
                //
                foreach (IReferenceSetInterval refSetInterval in referenceSetData.Intervals)
                {
                    foreach (IReferenceSetAccessedPage pageAccess in refSetInterval.PageAccesses)
                    {
                        //
                        // Make sure the page was accessed from the usermode process.
                        //
                        if (pageAccess.ImpactedProcess == null)
                        {
                            continue;
                        }

                        //
                        // Ignore the memory compression process. This is a system service.
                        //
                        if (pageAccess.ImpactedProcess.ImageName.Equals("MemCompression"))
                        {
                            continue;
                        }

                        //
                        // Make sure we have a file path
                        //
                        if (pageAccess?.Page?.Path == null)
                        {
                            continue;
                        }
                        var fileBeingAccessed = pageAccess?.Page?.Path;

                        //
                        // Not all file paths are images (think MFT or data files). Make sure this is in our image
                        // dictionary.
                        //
                        if (!imageData.ContainsKey(pageAccess.Page.Path))
                        {
                            continue;
                        }

                        //
                        // Make sure that this image was listed in the image data
                        //
                        if (!potentialDelayLoads.ContainsKey(fileBeingAccessed))
                        {
                            continue;
                        }

                        //
                        // Grab the image data, and use this to get the info on the page that was being accessed.
                        //
                        var data        = imageData[pageAccess.Page.Path];
                        var sectionName = GetSectionNameFromPage(pageAccess, data, imageSectionData, pageAccess.ImpactedProcess);

                        //
                        // We really only want consider .text pages, as we want to find images where the 'code' is never
                        // used. We have to include "unknown" as well since this is what shows up for images that we
                        // can't find symbols for. This effectively means for images without symbols we consider all pages.
                        //
                        if (!(sectionName.Contains(".text") || sectionName.Contains("Unknown")))
                        {
                            continue;
                        }

                        //
                        // If the loader accessed the page, it's still a potential delay load candidiate.
                        //
                        if (IsLoaderAccessedPage(pageAccess))
                        {
                            continue;
                        }

                        //
                        // A .text page was accessed from somewhere other then the loader. This image isn't
                        // a delay load candidate for this process.
                        //
                        string processName = GenerateProcessNameString(pageAccess.ImpactedProcess);
                        if ((potentialDelayLoads[fileBeingAccessed]).ContainsKey(processName))
                        {
                            if ((potentialDelayLoads[fileBeingAccessed])[processName])
                            {
                                (potentialDelayLoads[fileBeingAccessed])[processName] = false;
                            }
                        }
                        else
                        {
                            potentialDelayLoads[fileBeingAccessed].Add(processName, false);
                        }
                    }
                }

                //
                // Print out all potential delays loads we found. We modify the output format to be in
                // process->image style for easier consumption from the console.
                //
                List <Tuple <string, string> > delayLoads = new List <Tuple <string, string> >();
                foreach (var imagesLoaded in potentialDelayLoads)
                {
                    foreach (var processesDict in imagesLoaded.Value)
                    {
                        if (processesDict.Value == true)
                        {
                            delayLoads.Add(new Tuple <string, string>(processesDict.Key, imagesLoaded.Key));
                        }
                    }
                }
                delayLoads.Sort();
                foreach (var delayload in delayLoads)
                {
                    Console.WriteLine("{0} can delay load {1}", delayload.Item1, delayload.Item2);
                }
            }

            return(0);
        }
示例#7
0
    public static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.Error.WriteLine("Usage: CyclesPerInstruction.exe <trace.etl>");
            return(1);
        }

        string tracePath = args[0];

        TraceProcessorSettings settings = new TraceProcessorSettings {
            AllowLostEvents = true
        };

        using (ITraceProcessor trace = TraceProcessor.Create(tracePath, settings))
        {
            IPendingResult <IProcessorCounterDataSource> pendingCounterData = trace.UseProcessorCounters();

            trace.Process();

            IProcessorCounterDataSource counterData = pendingCounterData.Result;

            if (!counterData.HasCycleCount)
            {
                Console.Error.WriteLine("Trace does not contain cycle count data.");
                return(2);
            }

            if (!counterData.HasInstructionCount)
            {
                Console.Error.WriteLine("Trace does not contain instruction count data.");
                return(2);
            }

            Dictionary <string, ulong> cyclesByProcess       = new Dictionary <string, ulong>();
            Dictionary <string, ulong> instructionsByProcess = new Dictionary <string, ulong>();

            foreach (IProcessorCounterContextSwitchDelta delta in counterData.ContextSwitchCounterDeltas)
            {
                string processName = delta.Thread?.Process?.ImageName ?? "Unknown";

                if (!cyclesByProcess.ContainsKey(processName))
                {
                    cyclesByProcess.Add(processName, 0);
                    instructionsByProcess.Add(processName, 0);
                }

                cyclesByProcess[processName]       += delta.CycleCount.Value;
                instructionsByProcess[processName] += delta.InstructionCount.Value;
            }

            foreach (string processName in cyclesByProcess.Keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))
            {
                ulong   cycles               = cyclesByProcess[processName];
                ulong   instructions         = instructionsByProcess[processName];
                decimal cyclesPerInstruction = ((decimal)cycles) / instructions;
                Console.WriteLine($"{processName}: Cycles: {cycles}; Instructions: {instructions}; " +
                                  $"CPI: {cyclesPerInstruction:0.####}");
            }

            return(0);
        }
    }
    public static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.Error.WriteLine("Usage: OutstandingHandleCountByProcess.exe <trace.etl>");
            return(1);
        }

        string tracePath = args[0];
        TraceProcessorSettings settings = new TraceProcessorSettings {
            AllowLostEvents = true
        };

        using (ITraceProcessor trace = TraceProcessor.Create(tracePath, settings))
        {
            IPendingResult <IHandleDataSource> pendingHandleData = trace.UseHandles();

            trace.Process();

            IHandleDataSource handleData = pendingHandleData.Result;
            // Dictionary Key is Owning Process Name
            // Dictionary Value is a struct containing outstanding handle counts by type (Process Handles & Other Handles)
            Dictionary <string, HandleCounts> outstandingHandleCounts = new Dictionary <string, HandleCounts>();

            foreach (IProcessHandle processHandle in handleData.ProcessHandles)
            {
                if (!processHandle.CloseTime.HasValue)
                {
                    string owningProcessName = processHandle.Owner?.ImageName ?? "Unknown";

                    if (outstandingHandleCounts.ContainsKey(owningProcessName))
                    {
                        HandleCounts handleCounts = outstandingHandleCounts[owningProcessName];
                        ++handleCounts.ProcessHandleCount;
                        outstandingHandleCounts[owningProcessName] = handleCounts;
                    }
                    else
                    {
                        outstandingHandleCounts[owningProcessName] = new HandleCounts(1, 0);
                    }
                }
            }

            foreach (IHandle otherHandle in handleData.OtherHandles)
            {
                if (!otherHandle.CloseTime.HasValue)
                {
                    string owningProcessName = otherHandle.Owner?.ImageName ?? "Unknown";

                    if (outstandingHandleCounts.ContainsKey(owningProcessName))
                    {
                        HandleCounts handleCounts = outstandingHandleCounts[owningProcessName];
                        ++handleCounts.OtherHandleCount;
                        outstandingHandleCounts[owningProcessName] = handleCounts;
                    }
                    else
                    {
                        outstandingHandleCounts[owningProcessName] = new HandleCounts(0, 1);
                    }
                }
            }

            foreach (string process in outstandingHandleCounts.Keys)
            {
                int openProcessHandleCount = outstandingHandleCounts[process].ProcessHandleCount;
                int openOtherHandleCount   = outstandingHandleCounts[process].OtherHandleCount;
                Console.WriteLine($"Owning process: {process}");
                Console.WriteLine($"\t{openProcessHandleCount} outstanding Process Handles" +
                                  $"\t{openOtherHandleCount} outstanding Other Handles");
            }

            return(0);
        }
    }