public static void Zip(string fileName) { var etlWriter = new ZippedETLWriter(fileName); etlWriter.Zip = true; etlWriter.CompressETL = true; etlWriter.DeleteInputFile = true; etlWriter.WriteArchive(); }
/// <summary> /// CollectData doe will turn on logging of data from 'eventSourceName' to the file 'dataFileName'. /// It will then call EventGenerator.CreateEvents and wait 12 seconds for it to generate some data. /// </summary> static void CollectData(string eventSourceName, string dataFileName) { // Today you have to be Admin to turn on ETW events (anyone can write ETW events). if (!(TraceEventSession.IsElevated() ?? false)) { Out.WriteLine("To turn on ETW events you need to be Administrator, please run from an Admin process."); Debugger.Break(); return; } // As mentioned below, sessions can outlive the process that created them. Thus you need a way of // naming the session so that you can 'reconnect' to it from another process. This is what the name // is for. It can be anything, but it should be descriptive and unique. If you expect multiple versions // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix) // however this is dangerous because you can leave data collection on if the program ends unexpectedly. // // In this case we tell the session to place the data in MonitorToFileData.etl. var sessionName = "SimpleTraceLogSession"; Out.WriteLine("Creating a '{0}' session writing to {1}", sessionName, dataFileName); Out.WriteLine("Use 'logman query -ets' to see active sessions."); Out.WriteLine("Use 'logman stop {0} -ets' to manually stop orphans.", sessionName); using (var session = new TraceEventSession(sessionName, dataFileName)) // Since we give it a file name, the data goes there. using (var kernelSession = new TraceEventSession(KernelTraceEventParser.KernelSessionName, Path.ChangeExtension(dataFileName, ".kernel.etl"))) { /* BY DEFAULT ETW SESSIONS SURVIVE THE DEATH OF THE PROESS THAT CREATES THEM! */ // Unlike most other resources on the system, ETW session live beyond the lifetime of the // process that created them. This is very useful in some scenarios, but also creates the // very real possibility of leaving 'orphan' sessions running. // // To help avoid this by default TraceEventSession sets 'StopOnDispose' so that it will stop // the ETW session if the TraceEventSession dies. Thus executions that 'clean up' the TraceEventSession // will clean up the ETW session. This covers many cases (including throwing exceptions) // // However if the process is killed manually (including control C) this cleanup will not happen. // Thus best practices include // // * Add a Control C handler that calls session.Dispose() so it gets cleaned up in this common case // * use the same session name run-to-run so you don't create many orphans. // // By default TraceEventSessions are in 'create' mode where it assumes you want to create a new session. // In this mode if a session already exists, it is stopped and the new one is created. // // Here we install the Control C handler. It is OK if Dispose is called more than once. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { session.Dispose(); kernelSession.Dispose(); }; // Enable kernel events. kernelSession.EnableKernelProvider(KernelTraceEventParser.Keywords.ImageLoad | KernelTraceEventParser.Keywords.Process | KernelTraceEventParser.Keywords.Thread); // Enable my provider, you can call many of these on the same session to get events from other providers // Turn on the eventSource given its name. // Note we turn on Verbose level all keywords (ulong.MaxValue == 0xFFF....) and turn on stacks for // this provider (for all events, until Windows 8.1 you can only turn on stacks for every event // for a particular provider or no stacks) var options = new TraceEventProviderOptions() { StacksEnabled = true }; var restarted = session.EnableProvider(eventSourceName, TraceEventLevel.Verbose, ulong.MaxValue, options); if (restarted) // Generally you don't bother with this warning, but for the demo we do. { Out.WriteLine("The session {0} was already active, it has been restarted.", sessionName); } // We also turn on CLR events because we need them to decode Stacks and we also get exception events (and their stacks) session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)ClrTraceEventParser.Keywords.Default); // Start another thread that Causes MyEventSource to create some events // Normally this code as well as the EventSource itself would be in a different process. EventGenerator.CreateEvents(); // Also generate some exceptions so we have interesting stacks to look at Thread.Sleep(100); EventGenerator.GenerateExceptions(); Out.WriteLine("Waiting 12 seconds for events to come in."); Thread.Sleep(12000); // Because the process in question (this process) lives both before and after the time the events were // collected, we don't have complete information about JIT compiled methods in that method. There are // some methods that were JIT compiled before the session started (e.g. SimpleTraceLog.Main) for which // we do not have information. We collect this by forcing a CLR 'rundown' which will dump method information // for JIT compiled methods that were not present. If you know that the process of interest ends before // data collection ended or that data collection started before the process started, then this is not needed. Out.WriteLine("Forcing rundown of JIT methods."); var rundownFileName = Path.ChangeExtension(dataFileName, ".clrRundown.etl"); using (var rundownSession = new TraceEventSession(sessionName + "Rundown", rundownFileName)) { rundownSession.EnableProvider(ClrRundownTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)ClrRundownTraceEventParser.Keywords.Default); // Poll until 2 second goes by without growth. for (var prevLength = new FileInfo(rundownFileName).Length; ;) { Thread.Sleep(2000); var newLength = new FileInfo(rundownFileName).Length; if (newLength == prevLength) { break; } prevLength = newLength; } } Out.WriteLine("Done with rundown."); } Out.WriteLine("Zipping the raw files into a single '{0}' file.", dataFileName); // At this point you have multiple ETL files that don't have all the information // inside them necessary for analysis off the currentn machine. To do analsysis // of the machine you need to merge the ETL files (which can be done with // TraceEventSession.MergeInPlace(dataFileName, Out); // However this does not get the symbolic information (NGEN PDBS) needed to // decode the stacks in the .NET managed framewrok on another machine. // To do the merging AND generate these NGEN images it is best to us ethe // ZipppedETLWriter that does all this (and compresses all the files in a ZIP archive). ZippedETLWriter writer = new ZippedETLWriter(dataFileName, Out); writer.WriteArchive(); Out.WriteLine("Zip complete, output file = {0}", writer.ZipArchivePath); }
public override TestResult[] Execute(ITestMethod testMethod) { TestResult[] errorResults = ValidateElevated(testMethod); if (errorResults != null) { return(errorResults); } Exception signatureException = GetMethodSignatureException(testMethod); if (signatureException != null) { return(testMethod.CreateExceptionResult(signatureException)); } var runParameters = TestRunParameters.Read(); string logFolder = runParameters.LogFolder; bool shouldLog = !string.IsNullOrEmpty(logFolder); if (shouldLog) { try { logFolder = CreateLogFolder(logFolder, testMethod.TestMethodName); } catch (Exception e) { return(testMethod.CreateExceptionResult(e)); } } int iterations = runParameters.Iterations; var results = new TestResult[iterations]; for (int iteration = 1; iteration <= iterations; iteration++) { string sessionName = $"{testMethod.TestMethodName}-{iteration}"; using (var session = new TraceEventSession(sessionName)) { EnableKernelProviders(session, shouldLog); TraceEventDispatcher source; ZippedETLWriter writer = null; if (shouldLog) { string etlPath = Path.Combine(logFolder, $"Iteration{iteration}.etl"); source = new ETWReloggerTraceEventSource(sessionName, TraceEventSourceType.Session, etlPath); writer = new ZippedETLWriter(etlPath); } else { source = session.Source; } EnableProviders(session); PerformanceTestContext context = CreateContext(source); Task <TestResult> testTask = Task.Run(() => testMethod.Invoke(new object[] { context })); // This is a blocking call that in the case of ETWReloggerTraceEventSource, must be run on the same // thread as ETWReloggerTraceEventSource was created on. It will become unblocked when the // PerformanceTestContext calls StopProcessing on the source. source.Process(); TestResult result = testTask.Result; string displayName = testMethod.TestMethodName; if (iterations > 1) { displayName += $" [{iteration}/{iterations}]"; } result.DisplayName = displayName; session.Flush(); OnIterationEnded(context); context.LogScenarios(); context.LogMemoryDelta(); context.LogMessage($"{displayName} completed. {session.EventsLost} events lost."); context.WriteLogsToResult(result, writer); results[iteration - 1] = result; } } return(results); }