コード例 #1
0
    // Scan through a trace and print a summary of the Chrome processes, optionally with
    // CPU Usage details for Chrome and other key processes.
    static void ProcessTrace(ITraceProcessor trace, bool showCPUUsage)
    {
        var pendingProcessData = trace.UseProcesses();
        // Only request CPU scheduling data when it is actually needed, to avoid
        // unecessary trace processing costs. Unfortunately this means that the
        // scheduling data sources can't be declared with 'var'.
        IPendingResult <ICpuSchedulingDataSource> pendingSchedulingData = null;

        if (showCPUUsage)
        {
            pendingSchedulingData = trace.UseCpuSchedulingData();
        }

        trace.Process();

        ICpuSchedulingDataSource schedulingData = null;

        if (showCPUUsage)
        {
            schedulingData = pendingSchedulingData.Result;
        }

        // Get a List<ProcessSummary> of all Chrome processes.
        var processSummaries = GetChromePaths(pendingProcessData.Result);

        // Group all of the chrome.exe processes by browser Pid, then by type.
        // pathByBrowserPid just maps from the browser Pid to the disk path to
        // chrome.exe
        var pathByBrowserPid = new Dictionary <int, string>();

        // processesByBrowserPid is a dictionary that is indexed by the browser Pid.
        // It contains a dictionary that is indexed by process type with each
        // entry's payload being a list of Pids (for example, a list of renderer
        // processes).
        var processesByBrowserPid = new Dictionary <int, Dictionary <string, List <int> > >();

        // parentPids is a dictionary that maps from Pids to parent Pids.
        var parentPids = new Dictionary <int, int>();

        // Dictionary of Pids and their types.
        var typesByPid = new Dictionary <int, string>();

        // Find the space-terminated word after 'type='.
        // Mark the first .* as lazy/ungreedy/reluctant so that if there are multiple
        // --type options (such as with the V8 Proxy Resolver utility process) the
        // first one will win. Or, at least, that's what the comments in the Python
        // version of this said.
        var r = new Regex(@".*? --type=(?<type>[^ ]*) .*");

        foreach (var entry in processSummaries)
        {
            var process = entry.Key;
            var exePath = entry.Value;
            if (process.ImageName == "chrome.exe")
            {
                int pid = process.Id;
                parentPids[pid] = process.ParentId;

                // Look for the process type on the command-line in the
                // --type= option. If no type is found then assume it is the
                // browser process. I could have looked for chrome.dll, but
                // that may fail in the future. I could have looked for a chrome
                // process whose parent is not chrome, but I didn't. There are
                // many ways to do this.
                string type;
                int    browserPid;
                var    match = r.Match(process.CommandLine);
                if (match.Success)
                {
                    type = match.Groups["type"].ToString();
                    if (type == "crashpad-handler")
                    {
                        type = "crashpad"; // Shorten the tag for better formatting
                    }
                    if (type == "renderer" && process.CommandLine.Contains(" --extension-process "))
                    {
                        // Extension processes are renderers with --extension-process on the command line.
                        type = "extension";
                    }
                    browserPid = process.ParentId;
                }
                else
                {
                    type       = "browser";
                    browserPid = pid;
                    pathByBrowserPid[browserPid] = exePath;
                }

                typesByPid[pid] = type;

                // Retrieve or create the list of processes associated with this
                // browser (parent) pid.
                // This involves a lot of redundant dictionary lookups, but it is
                // the cleanest way to do it.
                if (!processesByBrowserPid.ContainsKey(browserPid))
                {
                    processesByBrowserPid[browserPid] = new Dictionary <string, List <int> >();
                }
                if (!processesByBrowserPid[browserPid].ContainsKey(type))
                {
                    processesByBrowserPid[browserPid][type] = new List <int>();
                }
                var pidList = processesByBrowserPid[browserPid][type];
                pidList.Add(pid);
            }
        }

        // Clean up the data, because process trees are never simple.
        // Iterate through a copy of the keys so that we can modify the dictionary.
        foreach (var browserPid in new List <int>(processesByBrowserPid.Keys))
        {
            var childPids = processesByBrowserPid[browserPid];
            if (childPids.Count == 1)
            {
                // Linq magic to get the one-and-only key.
                string childType = childPids.Keys.First();
                string destType  = null;

                // In many cases there are two crash-handler processes and one is the
                // parent of the other. This seems to be related to --monitor-self.
                // This script will initially report the parent crashpad process as being a
                // browser process, leaving the child orphaned. This fixes that up by
                // looking for singleton crashpad browsers and then finding their real
                // crashpad parent. This will then cause two crashpad processes to be
                // listed, which is correct.
                if (childType == "crashpad")
                {
                    destType = childType;
                }

                // Also look for entries with parents in the list and no children. These
                // represent child processes whose --type= option was too far along in the
                // command line for ETW's 512-character capture to get. See crbug.com/614502
                // for how this happened.
                // Checking that there is only one entry (itself) in the list is important
                // to avoid problems caused by Pid reuse that could cause one browser process
                // to appear to be another browser process' parent.
                else if (childType == "browser")
                {
                    destType = "gpu???";
                }

                // The browserPid *should* always be present, but in a large enough corpus
                // of traces, all bets are off. This assumption failed in a 20-hour heap
                // snapshot trace.
                if (parentPids.ContainsKey(browserPid))
                {
                    // The childPids["browser"] entry needs to be appended to its
                    // parent/grandparent process since that is its browser process.
                    int parentPid = parentPids[browserPid];
                    // Create the destination type if necessary (needed for gpu???,
                    // not needed for crashpad). Handle missing data.
                    if (processesByBrowserPid.ContainsKey(parentPid))
                    {
                        if (!processesByBrowserPid[parentPid].ContainsKey(destType))
                        {
                            processesByBrowserPid[parentPid][destType] = new List <int>();
                        }
                        processesByBrowserPid[parentPid][destType].Add(childPids[childType][0]);

                        // Remove the fake 'browser' entry so that we don't try to print it.
                        processesByBrowserPid.Remove(browserPid);
                    }
                }
            }
        }

        // Map from PID to CPUUsageDetails.
        var execTimes = new Dictionary <int, CPUUsageDetails>();

        if (showCPUUsage)
        {
            var names = new string[] { "chrome.exe", "dwm.exe", "audiodg.exe", "System", "MsMpEng.exe", "software_reporter_tool.exe" };
            // Scan through the context-switch data to build up how much time
            // was spent by interesting process.
            foreach (var slice in schedulingData.CpuTimeSlices)
            {
                // Yep, this happens.
                if (slice.Process == null)
                {
                    continue;
                }
                // Ignore non-interesting names. Only accumulate for chrome.exe and
                // processes that are known to be related or interesting.
                // An existence check in an array is O(n) but because the array is
                // short this is probably faster than using a dictionary.
                if (!names.Contains(slice.Process.ImageName))
                {
                    continue;
                }
                execTimes.TryGetValue(slice.Process.Id, out CPUUsageDetails last);
                last.imageName              = slice.Process.ImageName;
                last.ns                    += slice.Duration.Nanoseconds;
                last.contextSwitches       += 1;
                execTimes[slice.Process.Id] = last;
            }

            foreach (var times in execTimes)
            {
                CPUUsageDetails details = times.Value;
                // Print details about other interesting processes:
                if (details.imageName != "chrome.exe")
                {
                    Console.WriteLine("{0,11} - {1,6} context switches, {2,8:0.00} ms CPU", details.imageName, details.contextSwitches, details.ns / 1e6);
                }
            }
            Console.WriteLine();
        }

        if (processesByBrowserPid.Count > 0)
        {
            Console.WriteLine("Chrome PIDs by process type:");
        }
        else
        {
            Console.WriteLine("No Chrome processes found.");
        }
        var browserPids = new List <int>(processesByBrowserPid.Keys);

        browserPids.Sort();
        foreach (var browserPid in browserPids)
        {
            // |processes| is a Dictionary<type, List<pid>>
            var processes = processesByBrowserPid[browserPid];

            // Total up how many processes there are in this instance.
            var detailsByType = new Dictionary <string, CPUUsageDetails>();

            int totalProcesses = 0;
            var detailsTotal   = new CPUUsageDetails();
            foreach (var type in processes)
            {
                totalProcesses += type.Value.Count;
                if (showCPUUsage)
                {
                    var detailsSubTotal = new CPUUsageDetails();
                    foreach (int pid in type.Value)
                    {
                        execTimes.TryGetValue(pid, out CPUUsageDetails details);
                        detailsTotal.ns += details.ns;
                        detailsTotal.contextSwitches += details.contextSwitches;

                        detailsSubTotal.ns += details.ns;
                        detailsSubTotal.contextSwitches += details.contextSwitches;
                    }

                    detailsByType[type.Key] = detailsSubTotal;
                }
            }

            // Print the browser path.
            if (showCPUUsage)
            {
                Console.WriteLine("{0} ({1}) - {2} context switches, {3,8:0.00} ms CPU, {4} processes",
                                  pathByBrowserPid[browserPid], browserPid, detailsTotal.contextSwitches,
                                  detailsTotal.ns / 1e6, totalProcesses);
            }
            else
            {
                // See earlier note about how the browserPid may be missing.
                string browserPath = "Unknown parent";
                if (pathByBrowserPid.ContainsKey(browserPid))
                {
                    browserPath = pathByBrowserPid[browserPid];
                }
                Console.WriteLine("{0} ({1}) - {2} processes", browserPath, browserPid, totalProcesses);
            }

            // Sort the types alphabetically for consistent printing.
            var types = new List <KeyValuePair <string, List <int> > >(processes);
            types.Sort((x, y) => x.Key.CompareTo(y.Key));

            // Print all of the child processes, grouped by type.
            foreach (var type in types)
            {
                // |type| contains type and List<pid>
                // TODO: change this to ,12 for ppapi-broker
                if (showCPUUsage)
                {
                    CPUUsageDetails detailsSum = detailsByType[type.Key];
                    Console.Write("    {0,-11} : total - {1,6} context switches, {2,8:0.00} ms CPU", type.Key, detailsSum.contextSwitches, detailsSum.ns / 1e6);
                }
                else
                {
                    Console.Write("    {0,-11} : ", type.Key);
                }
                type.Value.Sort();
                foreach (var pid in type.Value)
                {
                    if (showCPUUsage)
                    {
                        Console.Write("\n        ");
                        execTimes.TryGetValue(pid, out CPUUsageDetails details);
                        if (details.contextSwitches > 0)
                        {
                            Console.Write("{0,5} - {1,6} context switches, {2,8:0.00} ms CPU", pid, details.contextSwitches, details.ns / 1e6);
                        }
                        else
                        {
                            Console.Write("{0,5}", pid);
                        }
                    }
                    else
                    {
                        Console.Write("{0} ", pid);
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine();
        }
    }
コード例 #2
0
    // Scan through a trace and print a summary of the system activity, with
    // svchost.exe and powershell process purposes annotated, chrome and
    // some other processes grouped, and with sampled data summarized by the
    // module it hits in and which modules are on the stacks.
    static void ProcessTrace(ITraceProcessor trace, string[] moduleList)
    {
        // metadata is retrieved directly instead of being retrieved after
        // trace.Process().
        var metadata = trace.UseMetadata();

        // Needed for precise CPU usage calculations.
        var pendingSchedulingData = trace.UseCpuSchedulingData();
        // Needed for processor count and memory size.
        var pendingSystemMetadata = trace.UseSystemMetadata();
        // Needed for PID to service mapping.
        var pendingServices = trace.UseServices();
        // Needed for seeing what modules samples hit in.
        var pendingSamplingData = trace.UseCpuSamplingData();

        trace.Process();

        // Convert from pending to actual data.
        var schedulingData = pendingSchedulingData.Result;
        var systemMetadata = pendingSystemMetadata.Result;
        var services       = pendingServices.Result;
        var samplingData   = pendingSamplingData.Result;

        // Print some high-level data about the trace and system.
        var traceDuration = metadata.AnalyzerDisplayedDuration;

        Console.WriteLine("Trace length is {0:0.00} seconds, system has {1} logical processors, {2}.",
                          traceDuration.TotalSeconds, systemMetadata.ProcessorCount, systemMetadata.UsableMemorySize);
        Console.WriteLine();

        // Map from an IProcess to CPUUsageDetails.
        var execTimes = new Dictionary <IProcess, CPUUsageDetails>();

        // Scan through the context-switch data to build up how much time
        // was spent by each process.
        foreach (var slice in schedulingData.CpuTimeSlices)
        {
            // Yep, this happens.
            if (slice.Process == null)
            {
                continue;
            }
            execTimes.TryGetValue(slice.Process, out CPUUsageDetails last);
            last.ns += slice.Duration.Nanoseconds;
            last.contextSwitches    += 1;
            execTimes[slice.Process] = last;
        }

        // Report on CPU usage for theses processes by name instead of by
        // process. That is, add same-named processes together.
        string[] sumNames = { "chrome.exe", "WmiPrvSE.exe", };
        var      sumsNs   = new long[sumNames.Length];

        // Scan through the per-process CPU consumption data
        var report = new List <Tuple <double, string> >();

        foreach (var times in execTimes)
        {
            var             process = times.Key;
            CPUUsageDetails details = times.Value;

            // See if we want to sum this process name.
            int i = Array.IndexOf(sumNames, process.ImageName);
            if (i >= 0)
            {
                sumsNs[i] += times.Value.ns;
                continue;
            }

            // Find the script name or service name that is associated with this
            // process so that our report associates the CPU time with a script
            // or service.
            string context     = "";
            var    commandLine = process.CommandLine;
            if (process.ImageName == "svchost.exe")
            {
                // Look for this pid in the snapshots. This should handle it if
                // multiple services exist in one process but this has not been
                // tested.
                foreach (var service in services.Snapshots)
                {
                    if (service.ProcessId == process.Id)
                    {
                        if (context.Length > 0)
                        {
                            context += ' ';
                        }
                        context += service.Name;
                    }
                }
            }
            else if (process.ImageName == "powershell.exe")
            {
                // Look for .ps1 and then search backwards for a space or slash character.
                var scriptEnd = commandLine.IndexOf(".ps1");
                if (scriptEnd > 0)
                {
                    // Look for a slash or space character to mark the beginning of
                    // the file name of the script.
                    var slash = commandLine.LastIndexOf('\\', scriptEnd);
                    var space = commandLine.LastIndexOf(' ', scriptEnd);
                    var start = Math.Max(slash, space) + 1;
                    context = commandLine.Substring(start, scriptEnd + 4 - start);
                }

                // If no .ps1 filename was found then try something else.
                if (context.Length == 0)
                {
                    // Grab the last parameter. This really needs to handle quotes to
                    // be useful.
                    var parts = commandLine.Split(' ');
                    context = parts[parts.Length - 1];
                }
            }
            else if (process.ImageName == "ruby.exe")
            {
                // Grab the start of the command-line and hope that helps.
                context = commandLine.Substring(0, Math.Min(20, commandLine.Length));
            }

            double timeSeconds = details.ns / 1e9;
            report.Add(new Tuple <double, string>(timeSeconds, string.Format("{0,20} ({1,5}) - {2}", process.ImageName, process.Id, context)));
        }

        // Add in the processes that we sum by name instead of by processes.
        for (int i = 0; i < sumNames.Length; ++i)
        {
            double timeSeconds = sumsNs[i] / 1e9;
            report.Add(new Tuple <double, string>(timeSeconds, string.Format("{0,20}", sumNames[i])));
        }

        // Sort the data by CPU time consumed and print the busiest processes:
        report.Sort();
        report.Reverse();
        foreach (var r in report)
        {
            // Arbitrary threshold so that we ignore boring data.
            if (r.Item1 > 2.0)
            {
                double percentage = r.Item1 / (double)traceDuration.TotalSeconds;
                Console.WriteLine("{0,9:P} of a core, {1,8:0.00} s CPU, {2}", percentage, r.Item1, r.Item2);
            }
        }

        // Record two dictionaries that map from modules to sample counts.
        // One is for samples that hit in that module, the other is for samples
        // that hit when an "interesting" module is on the stack.
        // Track by module name rather than IImage because some DLLs (ntdll.dll)
        // show up as many different IImage objects, which makes for a confusing
        // report.
        var samplesByImage      = new Dictionary <string, ulong>();
        var stackSamplesByImage = new Dictionary <string, ulong>();

        foreach (var sample in samplingData.Samples)
        {
            {
                // Attribute samples to the module they hit in. This gives a
                // sense of where CPU time is being spent across all processes
                // on the system. In some cases this can give hints - perhaps a
                // lower bound - on the cost of modules which inject themselves
                // into all processes.
                if (sample.Image != null)
                {
                    samplesByImage.TryGetValue(sample.Image.FileName, out ulong count);
                    ++count;
                    samplesByImage[sample.Image.FileName] = count;
                }
            }

            if (moduleList != null && sample.Stack != null)
            {
                // Attributes samples to interesting modules that are on the stack.
                // In some cases an injected module may ask the OS or other modules
                // to do work on its behalf. If antivirus DLLs are on the
                // stack but not currently executing then the executing code *may*
                // be doing work on their behalf, or not. Thus, gives gives a
                // rough upper bound on the cost of these systems. Note that only the
                // first module hit is counted.
                foreach (var frame in sample.Stack.Frames)
                {
                    if (frame.Image != null && frame.Image.FileName != null)
                    {
                        string imageName = frame.Image.FileName;
                        if (moduleList.Contains(imageName))
                        {
                            stackSamplesByImage.TryGetValue(imageName, out ulong count);
                            ++count;
                            stackSamplesByImage[imageName] = count;
                            break;
                        }
                    }
                }
            }
        }

        Console.WriteLine();
        int totalSamples = samplingData.Samples.Count;

        Console.WriteLine("Exclusive samples by module (out of {0:#,#} samples total):", totalSamples);
        PrintSampleData(new List <KeyValuePair <string, ulong> >(samplesByImage),
                        totalSamples, "{0,9:#,#} samples {1,6:P} in {2}");

        if (moduleList != null)
        {
            Console.WriteLine("");
            Console.WriteLine("Inclusive (stacks containing them) samples (out of {0:#,#} samples total):", totalSamples);
            PrintSampleData(new List <KeyValuePair <string, ulong> >(stackSamplesByImage),
                            totalSamples, "{0,9:#,#} samples {1,6:P} with {2} on the call stack");
        }
    }