private static int Main(string[] args) { if (args.Length != 1) { Console.Error.WriteLine("Usage CheckForAppCrash <trace.etl>"); return(-1); } string tracePath = args[0]; using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { IPendingResult <IProcessDataSource> pendingProcesses = trace.UseProcesses(); trace.Process(); IProcessDataSource processData = pendingProcesses.Result; foreach (IProcess process in processData.Processes) { if (string.Equals("werfault.exe", process.ImageName, StringComparison.OrdinalIgnoreCase)) { return(1); } } } return(0); }
private static void Process(string path) { try { if (string.IsNullOrEmpty(path)) { Console.Error.WriteLine("ETL file location not provided..."); return; } using (ITraceProcessor trace = TraceProcessor.Create(path)) { IPendingResult <IProcessDataSource> pendingProcessData = trace.UseProcesses(); trace.Process(); IProcessDataSource processData = pendingProcessData.Result; foreach (IProcess process in processData.Processes) { log.Info(process.CommandLine); } } } catch (Exception ex) { Console.WriteLine($"An error occured while processing. {ex}"); throw ex; } }
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); } }
static void Main(string[] args) { if (args.Length != 3) { Console.Error.WriteLine("Usage: GetCpuSampleDuration.exe <trace.etl> <imageName> <functionName>"); return; } string tracePath = args[0]; string imageName = args[1]; string functionName = args[2]; Dictionary <string, Duration> matchDurationByCommandLine = new Dictionary <string, Duration>(); using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { IPendingResult <ISymbolDataSource> pendingSymbolData = trace.UseSymbols(); IPendingResult <ICpuSampleDataSource> pendingCpuSamplingData = trace.UseCpuSamplingData(); trace.Process(); ISymbolDataSource symbolData = pendingSymbolData.Result; ICpuSampleDataSource cpuSamplingData = pendingCpuSamplingData.Result; symbolData.LoadSymbolsForConsoleAsync(SymCachePath.Automatic, SymbolPath.Automatic).GetAwaiter().GetResult(); Console.WriteLine(); IThreadStackPattern pattern = AnalyzerThreadStackPattern.Parse($"{imageName}!{functionName}"); foreach (ICpuSample sample in cpuSamplingData.Samples) { if (sample.IsExecutingDeferredProcedureCall == true || sample.IsExecutingInterruptServicingRoutine == true) { continue; } if (sample.Stack != null && sample.Stack.Matches(pattern)) { string commandLine = sample.Process.CommandLine; if (!matchDurationByCommandLine.ContainsKey(commandLine)) { matchDurationByCommandLine.Add(commandLine, Duration.Zero); } matchDurationByCommandLine[commandLine] += sample.Weight; } } } foreach (string commandLine in matchDurationByCommandLine.Keys) { Console.WriteLine($"{commandLine}: {matchDurationByCommandLine[commandLine]}"); } }
static void Run(string tracePath) { using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { IPendingResult <IProcessDataSource> pendingProcessData = trace.UseProcesses(); trace.Process(); IProcessDataSource processData = pendingProcessData.Result; Console.WriteLine(processData.Processes.Count); } }
private static IReadOnlyList <string> GetServices(string tracePath) { using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { IPendingResult <IServiceDataSource> pendingServices = trace.UseServices(); trace.Process(); IServiceDataSource serviceData = pendingServices.Result; return(serviceData.Services.Select(s => Cleanup(s.Name)).ToArray()); } }
private void GatherTraceData() { using (ITraceProcessor trace = TraceProcessor.Create(EtlPath)) { IPendingResult <IProcessDataSource> pendingProcessData = trace.UseProcesses(); IPendingResult <IThreadDataSource> pendingThreadData = trace.UseThreads(); IPendingResult <ICpuSampleDataSource> pendingCpuSampleData = trace.UseCpuSamplingData(); IPendingResult <ICpuSchedulingDataSource> pendingCpuSchedulingData = trace.UseCpuSchedulingData(); trace.Process(); GatherProcessData(pendingProcessData.Result); GatherThreadData(pendingThreadData.Result); GatherCpuSampleData(pendingCpuSampleData.Result); GatherCpuSchedulingData(pendingCpuSchedulingData.Result); } }
public TraceManager( ILogger logger, ITraceProcessor traceProcessor, IConfiguration configuration) { _configuration = configuration; _traceProcessor = traceProcessor; _logger = logger; _traceActionBlock = new ActionBlock <TraceEvent>( ProcessTraceAsync, new ExecutionDataflowBlockOptions() { BoundedCapacity = configuration.TraceQueueBoundedCapacity, MaxDegreeOfParallelism = configuration.TraceQueueMaxDegreeOfParallelism, EnsureOrdered = false }); }
static void Main(string[] args) { if (args.Length != 1) { Console.Error.WriteLine("Usage: <trace.etl>"); return; } using (ITraceProcessor trace = TraceProcessor.Create(args[0])) { IPendingResult <IProcessDataSource> pendingProcessData = trace.UseProcesses(); trace.Process(); IProcessDataSource processData = pendingProcessData.Result; foreach (IProcess process in processData.Processes) { Console.WriteLine(process.CommandLine); } } }
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.Error.WriteLine("Usage: ListImages.exe <trace.etl>"); return; } string tracePath = args[0]; using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { IPendingResult <IProcessDataSource> pendingProcessData = trace.UseProcesses(); trace.Process(); IProcessDataSource processData = pendingProcessData.Result; foreach (IProcess process in processData.Processes) { foreach (IImage image in process.Images) { DataSize ImageSize = image.Size; long TimeDataStamp = image.Timestamp; string OrigFileName = image.OriginalFileName; string FileDescription = image.FileDescription; string FileVersion = image.FileVersion; Version BinFileVersion = image.FileVersionNumber; CultureInfo VerLanguage = image.Locale; string ProductName = image.ProductName; string CompanyName = image.CompanyName; string ProductVersion = image.ProductVersion; string FileId = image.CompatibilityFileId; string ProgramId = image.CompatibilityProgramId; } } } }
static Timestamp GetBootTime(string tracePath) { Timestamp result = Timestamp.Zero; using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { // Microsoft-Windows-Shell-Core trace.Use(new Guid[] { new Guid("30336ed4-e327-447c-9de0-51b652c86108") }, e => { // PerfTrack_Explorer_ExplorerStartToDesktopReady if (e.Id != 27231) { return; } result = e.Timestamp; }); trace.Process(); } return(result); }
private static IReadOnlyDictionary <PageKey, uint> GetResidentSetPageCounts(string tracePath, Timestamp startTime, Timestamp stopTime) { using (ITraceProcessor trace = TraceProcessor.Create(tracePath)) { IPendingResult <IResidentSetDataSource> pendingResidentSet = trace.UseResidentSetData(); trace.Process(); IResidentSetDataSource residentSetData = pendingResidentSet.Result; Dictionary <PageKey, uint> pageCounts = new Dictionary <PageKey, uint>(); foreach (IResidentSetSnapshot snapshot in residentSetData.Snapshots) { if (snapshot.Timestamp < startTime || snapshot.Timestamp > stopTime) { continue; } foreach (IResidentSetPage page in snapshot.Pages) { PageKey key = new PageKey(snapshot.Timestamp, page.MemoryManagerListType, page.Priority); if (!pageCounts.ContainsKey(key)) { pageCounts.Add(key, 0); } ++pageCounts[key]; } } return(pageCounts); } }
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); }
static void RunWithOptions(Options opts) { using (ITraceProcessor trace = TraceProcessor.Create(opts.etlFileName)) { IPendingResult <ICpuSampleDataSource> pendingCpuSampleData = trace.UseCpuSamplingData(); IPendingResult <ISymbolDataSource> pendingSymbolData = trace.UseSymbols(); trace.Process(); ISymbolDataSource symbolData = pendingSymbolData.Result; ICpuSampleDataSource cpuSampleData = pendingCpuSampleData.Result; var symbolProgress = new Progress <SymbolLoadingProgress>(progress => { Console.Write("\r{0:P} {1} of {2} symbols processed ({3} loaded)", (double)progress.ImagesProcessed / progress.ImagesTotal, progress.ImagesProcessed, progress.ImagesTotal, progress.ImagesLoaded); }); symbolData.LoadSymbolsAsync( SymCachePath.Automatic, SymbolPath.Automatic, symbolProgress) .GetAwaiter().GetResult(); Console.WriteLine(); var profileWriter = new ProfileWriter(opts.etlFileName, opts.includeInlinedFunctions, opts.includeProcessAndThreadIds, opts.stripSourceFileNamePrefix); var timeStart = opts.timeStart ?? 0; var timeEnd = opts.timeEnd ?? decimal.MaxValue; var exportAllProcesses = opts.processFilter == "*"; var processFilterSet = new HashSet <string>( opts.processFilter.Trim().Split(",", StringSplitOptions.RemoveEmptyEntries)); for (int i = 0; i < cpuSampleData.Samples.Count; i++) { if (i % 100 == 0) { Console.Write("\r{0:P} {1} of {2} samples processed", (double)i / cpuSampleData.Samples.Count, i, cpuSampleData.Samples.Count); } var cpuSample = cpuSampleData.Samples[i]; if ((cpuSample.IsExecutingDeferredProcedureCall ?? false) || (cpuSample.IsExecutingInterruptServicingRoutine ?? false)) { continue; } if (!exportAllProcesses) { var processImage = cpuSample.Process.Images .FirstOrDefault(image => image.FileName == cpuSample.Process.ImageName); string imagePath = processImage?.Path ?? cpuSample.Process.ImageName; if (!processFilterSet.Any(filter => imagePath.Contains(filter.Replace("/", "\\")))) { continue; } } var timestamp = cpuSample.Timestamp.RelativeTimestamp.TotalSeconds; if (timestamp < timeStart || timestamp > timeEnd) { continue; } profileWriter.AddSample(cpuSample); } Console.WriteLine(); long outputSize = profileWriter.Write(opts.outputFileName); Console.WriteLine("Wrote {0:N0} bytes to {1}", outputSize, opts.outputFileName); } }
// Process a trace and create a summary. static SnapshotSummary GetAllocSummary(ITraceProcessor trace) { var pendingSnapshotData = trace.UseHeapSnapshots(); var pendingSymbols = trace.UseSymbols(); trace.Process(); var snapshotData = pendingSnapshotData.Result; var symbols = pendingSymbols.Result; symbols.LoadSymbolsAsync(new SymCachePath(@"c:\symcache")).GetAwaiter().GetResult(); if (snapshotData.Snapshots.Count != 1) { Console.Error.WriteLine("Trace must contain exactly one heap snapshot - actually contained {0}.", snapshotData.Snapshots.Count); return(new SnapshotSummary(null, 0)); } // Scan through all of the allocations and collect them by // SnapshotUniqueStackId (which corresponds to Stack Ref#), // accumulating the bytes allocated, allocation count, and the // stack. var allocsByStackId = new Dictionary <ulong, AllocDetails>(); foreach (IHeapAllocation row in snapshotData.Snapshots[0].Allocations) { allocsByStackId.TryGetValue(row.SnapshotUniqueStackId, out AllocDetails value); value.Stack = row.Stack; value.Size += row.Size; value.Count += 1; allocsByStackId[row.SnapshotUniqueStackId] = value; } // Count how many allocations each stack frame is part of. // RtlThreadStart will presumably be near the top, along with // RtlpAllocateHeapInternal, but some clues may be found. var hotStackFrames = new Dictionary <string, long>(); foreach (var data in allocsByStackId.Values) { foreach (var entry in data.Stack) { var analyzerString = entry.GetAnalyzerString(); hotStackFrames.TryGetValue(analyzerString, out long count); count += data.Count; hotStackFrames[analyzerString] = count; } } var result = new SnapshotSummary(allocsByStackId, snapshotData.Snapshots[0].ProcessId); // Create a summary of the alloc counts and byte counts. var totalAllocBytes = DataSize.Zero; long totalAllocCount = 0; foreach (var data in allocsByStackId.Values) { totalAllocBytes += data.Size; totalAllocCount += data.Count; } result.hotStackFrames_ = hotStackFrames; result.totalBytes_ = totalAllocBytes; result.allocCount_ = totalAllocCount; return(result); }
static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Use this to summarize a heap snapshot or compare multiple heap snapshots"); Console.WriteLine("from one run of a program."); return; } SnapshotSummary lastAllocs = null; string lastTracename = ""; foreach (var arg in args) { if (!File.Exists(arg)) { Console.Error.WriteLine("File '{0}' does not exist.", arg); continue; } using (ITraceProcessor trace = TraceProcessor.Create(arg)) { Console.WriteLine("Summarizing '{0}'", Path.GetFileName(arg)); var allocs = GetAllocSummary(trace); if (allocs.allocsByStackId_ == null) { Console.WriteLine("Ignoring trace {0}.", arg); continue; } Console.WriteLine("{0,7:F2} MB from {1,9:#,#} allocations on {2,7:#,#} stacks", allocs.totalBytes_.TotalMegabytes, allocs.allocCount_, allocs.allocsByStackId_.Count); const int maxPrinted = 40; Console.WriteLine("Hottest stack frames:"); // Display a summary of the first (possibly only) heap snapshot trace. var sortedHotStackEntries = new List <KeyValuePair <string, long> >(allocs.hotStackFrames_); sortedHotStackEntries.Sort((x, y) => y.Value.CompareTo(x.Value)); for (int i = 0; i < sortedHotStackEntries.Count && i < maxPrinted; ++i) { var data = sortedHotStackEntries[i]; Console.WriteLine("{0,5} allocs cross {1}", data.Value, data.Key); } if (lastAllocs != null) { Console.WriteLine("Comparing old ({0}) to new ({1}) snapshots.", Path.GetFileName(lastTracename), Path.GetFileName(arg)); if (allocs.pid_ != lastAllocs.pid_) { Console.WriteLine("WARNING: process IDs are different ({0} and {1}) so stack IDs may not be comparable.", lastAllocs.pid_, allocs.pid_); } var hotStackFramesDelta = new Dictionary <string, long>(allocs.hotStackFrames_); // Subtract the lastAllocs stack frame counts fomr the current stack frame counts. foreach (var entry in lastAllocs.hotStackFrames_) { hotStackFramesDelta.TryGetValue(entry.Key, out long count); count -= entry.Value; hotStackFramesDelta[entry.Key] = count; } Console.WriteLine("Hottest stack frame deltas:"); // Print the biggest deltas, positive then negative. var sortedHotStackFramesDelta = new List <KeyValuePair <string, long> >(hotStackFramesDelta); sortedHotStackFramesDelta.Sort((x, y) => y.Value.CompareTo(x.Value)); // Print the first half... for (int i = 0; i < sortedHotStackFramesDelta.Count && i < maxPrinted / 2; ++i) { var data = sortedHotStackFramesDelta[i]; Console.WriteLine("{0,5} allocs cross {1}", data.Value, data.Key); } Console.WriteLine("..."); int start = sortedHotStackFramesDelta.Count - maxPrinted / 2; if (start < 0) { start = 0; } for (int i = start; i < sortedHotStackFramesDelta.Count - 1; ++i) { var data = sortedHotStackFramesDelta[i]; Console.WriteLine("{0,5} allocs cross {1}", data.Value, data.Key); } ulong newOnlyStacks = 0; ulong oldOnlyStacks = 0; foreach (var tag in allocs.allocsByStackId_.Keys) { if (!lastAllocs.allocsByStackId_.ContainsKey(tag)) { newOnlyStacks++; } } foreach (var tag in lastAllocs.allocsByStackId_.Keys) { if (!allocs.allocsByStackId_.ContainsKey(tag)) { oldOnlyStacks++; } } Console.WriteLine(" Old snapshot had {0} unique-to-it stacks, new trace had {1} unique-to-it stacks.", oldOnlyStacks, newOnlyStacks); } lastAllocs = allocs; lastTracename = arg; } } }
// 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"); } }
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); } }
// 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(); } }
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); } }
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; })); }
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(); } } }
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); } }