/Blocks/SemanticLogging/Src/TraceEvent1.2.7/TraceEventSession.cs

http://entlib.codeplex.com · C# · 1399 lines · 956 code · 117 blank · 326 comment · 172 complexity · 5e701d5cfe666827d424d840465e052d MD5 · raw file

Large files are truncated click here to view the full file

  1. using Microsoft.Win32;
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // This file is best viewed using outline mode (Ctrl-M Ctrl-O)
  4. //
  5. // This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
  6. // It is available from http://www.codeplex.com/hyperAddin
  7. //
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.IO;
  12. using System.Runtime.InteropServices;
  13. using System.Text;
  14. using System.Threading;
  15. using Utilities;
  16. using Diagnostics.Tracing.Parsers;
  17. // TraceEventSession defintions See code:#Introduction to get started.
  18. namespace Diagnostics.Tracing
  19. {
  20. /// <summary>
  21. /// #Introduction
  22. ///
  23. /// A TraceEventSession represents a single ETW Tracing Session (something that logs a
  24. /// single output moduleFile). Every ETL output moduleFile has exactly one session assoicated with it,
  25. /// although you can have 'real time' sessions that have no output file and you can connect to
  26. /// 'directly' to get events without ever creating a file. You signify this simply by passing
  27. /// 'null' as the name of the file. You extract data from these 'real time' sources by specifying
  28. /// the session name to the constructor of code:ETWTraceEventSource). Sessions are MACHINE WIDE and can
  29. /// OUTLIVE the process that creates them. This it takes some care to insure that sessions are cleaned up
  30. /// in all cases.
  31. ///
  32. /// Code that generated ETW events are called Providers. The Kernel has a provider (and it is often the
  33. /// most intersting) but other components are free to use public OS APIs (eg WriteEvent), to create
  34. /// user-mode providers. Each Provider is given a GUID that is used to identify it. You can get a list of
  35. /// all providers on the system as well as their GUIDs by typing the command
  36. ///
  37. /// logman query providers
  38. ///
  39. /// The basic model is that you start a session (which creates a ETL moduleFile), and then you call
  40. /// code:TraceEventSession.EnableProvider on it to add all the providers (event sources), that you are
  41. /// interested in. A session is given a name (which is MACHINE WIDE), so that you can connect back up to
  42. /// it from another process (since it might outlive the process that created it), so you can modify it or
  43. /// (more commonly) close the session down later from another process.
  44. ///
  45. /// For implementation reasons, this is only one Kernel provider and it can only be specified in a
  46. /// special 'Kernel Mode' session. There can be only one kernel mode session (MACHINE WIDE) and it is
  47. /// distinguished by a special name 'NT Kernel Logger'. The framework allows you to pass flags to the
  48. /// provider to control it and the Kernel provider uses these bits to indicate which particular events
  49. /// are of interest. Because of these restrictions, you often need two sessions, one for the kernel
  50. /// events and one for all user-mode events.
  51. ///
  52. /// Sample use. Enabling the Kernel's DLL image logging to the moduleFile output.etl
  53. ///
  54. /// * TraceEventSession session = new TraceEventSession(, KernelTraceEventParser.Keywords.ImageLoad);
  55. /// * Run you scenario
  56. /// * session.Close();
  57. ///
  58. /// Once the scenario is complete, you use the code:TraceEventSession.Close methodIndex to shut down a
  59. /// session. You can also use the code:TraceEventSession.GetActiveSessionNames to get a list of all
  60. /// currently running session on the machine (in case you forgot to close them!).
  61. ///
  62. /// When the sesion is closed, you can use the code:ETWTraceEventSource to parse the events in the ETL
  63. /// moduleFile. Alternatively, you can use code:TraceLog.CreateFromETL to convert the ETL file into an ETLX file.
  64. /// Once it is an ETLX file you have a much richer set of processing options availabe from code:TraceLog.
  65. /// </summary>
  66. // [SecuritySafeCritical]
  67. unsafe public sealed class TraceEventSession : IDisposable
  68. {
  69. /// <summary>
  70. /// Create a new logging session.
  71. /// </summary>
  72. /// <param name="sessionName">
  73. /// The name of the session. Since session can exist beyond the lifetime of the process this name is
  74. /// used to refer to the session from other threads.
  75. /// </param>
  76. /// <param name="fileName">
  77. /// The output moduleFile (by convention .ETL) to put the event data. If this parameter is null, it means
  78. /// that the data is 'real time' (stored in the session memory itself)
  79. /// </param>
  80. public TraceEventSession(string sessionName, string fileName)
  81. {
  82. this.m_BufferSizeMB = Math.Max(64, System.Environment.ProcessorCount * 2); // The default size.
  83. this.m_SessionHandle = TraceEventNativeMethods.INVALID_HANDLE_VALUE;
  84. this.m_FileName = fileName; // filename = null means real time session
  85. this.m_SessionName = sessionName;
  86. this.m_Create = true;
  87. this.CpuSampleIntervalMSec = 1.0F;
  88. }
  89. /// <summary>
  90. /// Open an existing Windows Event Tracing Session, with name 'sessionName'. To create a new session,
  91. /// use TraceEventSession(string, string)
  92. /// </summary>
  93. /// <param name="sessionName"> The name of the session to open (see GetActiveSessionNames)</param>
  94. public TraceEventSession(string sessionName)
  95. {
  96. this.m_SessionHandle = TraceEventNativeMethods.INVALID_HANDLE_VALUE;
  97. this.m_SessionName = sessionName;
  98. this.CpuSampleIntervalMSec = 1.0F;
  99. // Get the filename
  100. var propertiesBuff = stackalloc byte[PropertiesSize];
  101. var properties = GetProperties(propertiesBuff);
  102. int hr = TraceEventNativeMethods.ControlTrace(0UL, sessionName, properties, TraceEventNativeMethods.EVENT_TRACE_CONTROL_QUERY);
  103. if (hr == 4201) // Instance name not found. This means we did not start
  104. throw new FileNotFoundException("The session " + sessionName + " is not active."); // Not really a file, but not bad.
  105. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr));
  106. this.m_FileName = new string((char*)(((byte*)properties) + properties->LogFileNameOffset));
  107. this.m_BufferSizeMB = (int)properties->MinimumBuffers;
  108. if ((properties->LogFileMode & TraceEventNativeMethods.EVENT_TRACE_FILE_MODE_CIRCULAR) != 0)
  109. m_CircularBufferMB = (int)properties->MaximumFileSize;
  110. }
  111. public bool EnableKernelProvider(KernelTraceEventParser.Keywords flags)
  112. {
  113. return EnableKernelProvider(flags, KernelTraceEventParser.Keywords.None);
  114. }
  115. /// <summary>
  116. /// #EnableKernelProvider
  117. /// Enable the kernel provider for the session. If the session must be called 'NT Kernel Session'.
  118. /// <param name="flags">
  119. /// Specifies the particular kernel events of interest</param>
  120. /// <param name="stackCapture">
  121. /// Specifies which events should have their eventToStack traces captured too (VISTA+ only)</param>
  122. /// <returns>Returns true if the session had existed before and is now restarted</returns>
  123. /// </summary>
  124. public unsafe bool EnableKernelProvider(KernelTraceEventParser.Keywords flags, KernelTraceEventParser.Keywords stackCapture)
  125. {
  126. bool systemTraceProvider = false;
  127. var version = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor;
  128. if (m_SessionName != KernelTraceEventParser.KernelSessionName)
  129. {
  130. systemTraceProvider = true;
  131. if (version < 62)
  132. throw new NotSupportedException("System Tracing is only supported on Windows 8 and above.");
  133. }
  134. else
  135. {
  136. if (m_SessionHandle != TraceEventNativeMethods.INVALID_HANDLE_VALUE)
  137. throw new Exception("The kernel provider must be enabled as the only provider.");
  138. if (version < 60)
  139. throw new NotSupportedException("Kernel Event Tracing is only supported on Windows 6.0 (Vista) and above.");
  140. }
  141. // The Profile event requires the SeSystemProfilePrivilege to succeed, so set it.
  142. if ((flags & (KernelTraceEventParser.Keywords.Profile | KernelTraceEventParser.Keywords.PMCProfile)) != 0)
  143. {
  144. TraceEventNativeMethods.SetSystemProfilePrivilege();
  145. // TODO FIX NOW never fails.
  146. if (CpuSampleIntervalMSec != 1)
  147. {
  148. if (!TraceEventNativeMethods.CanSetCpuSamplingRate())
  149. throw new ApplicationException("Changing the CPU sampling rate is currently not supported on this OS.");
  150. }
  151. var cpu100ns = (CpuSampleIntervalMSec * 10000.0 + .5);
  152. // The API seems to have an upper bound of 1 second.
  153. if (cpu100ns >= int.MaxValue || ((int)cpu100ns) > 10000000)
  154. throw new ApplicationException("CPU Sampling rate is too high.");
  155. var succeeded = TraceEventNativeMethods.SetCpuSamplingRate((int)cpu100ns); // Always try to set, since it may not be the default
  156. if (!succeeded && CpuSampleIntervalMSec != 1.0F)
  157. throw new InvalidOperationException("Can't set CPU sampling to " + CpuSampleIntervalMSec.ToString("f3") + "Msec.");
  158. }
  159. var propertiesBuff = stackalloc byte[PropertiesSize];
  160. var properties = GetProperties(propertiesBuff);
  161. // Initialize the stack collecting information
  162. const int stackTracingIdsMax = 96;
  163. int numIDs = 0;
  164. var stackTracingIds = stackalloc TraceEventNativeMethods.STACK_TRACING_EVENT_ID[stackTracingIdsMax];
  165. if (stackCapture != KernelTraceEventParser.Keywords.None)
  166. numIDs = SetStackTraceIds(stackCapture, stackTracingIds, stackTracingIdsMax);
  167. bool ret = false;
  168. int dwErr;
  169. try
  170. {
  171. if (systemTraceProvider)
  172. {
  173. properties->LogFileMode = properties->LogFileMode | TraceEventNativeMethods.EVENT_TRACE_SYSTEM_LOGGER_MODE;
  174. InsureStarted(properties);
  175. dwErr = TraceEventNativeMethods.TraceSetInformation(m_SessionHandle,
  176. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceStackTracingInfo,
  177. stackTracingIds,
  178. (numIDs * sizeof(TraceEventNativeMethods.STACK_TRACING_EVENT_ID)));
  179. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(dwErr));
  180. ulong* systemTraceFlags = stackalloc ulong[1];
  181. systemTraceFlags[0] = (ulong)(flags & ~KernelTraceEventParser.Keywords.NonOSKeywords);
  182. dwErr = TraceEventNativeMethods.TraceSetInformation(m_SessionHandle,
  183. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceSystemTraceEnableFlagsInfo,
  184. systemTraceFlags,
  185. sizeof(ulong));
  186. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(dwErr));
  187. ret = true;
  188. }
  189. else
  190. {
  191. properties->Wnode.Guid = KernelTraceEventParser.ProviderGuid;
  192. properties->EnableFlags = (uint)flags;
  193. dwErr = StartKernelTrace(out m_SessionHandle, properties, stackTracingIds, numIDs);
  194. if (dwErr == 0xB7) // STIERR_HANDLEEXISTS
  195. {
  196. ret = true;
  197. Stop();
  198. m_Stopped = false;
  199. Thread.Sleep(100); // Give it some time to stop.
  200. dwErr = StartKernelTrace(out m_SessionHandle, properties, stackTracingIds, numIDs);
  201. }
  202. }
  203. }
  204. catch (BadImageFormatException)
  205. {
  206. // We use a small native DLL called KernelTraceControl that needs to be
  207. // in the same directory as the EXE that used TraceEvent.dll. Unlike IL
  208. // Native DLLs are specific to a processor type (32 or 64 bit) so the easiestC:\Users\vancem\Documents\etw\traceEvent\TraceEventSession.cs
  209. // way to insure this is that the EXE that uses TraceEvent is built for 32 bit
  210. // and that you use the 32 bit version of KernelTraceControl.dll
  211. throw new BadImageFormatException("Could not load KernelTraceControl.dll (likely 32-64 bit process mismatch)");
  212. }
  213. catch (DllNotFoundException)
  214. {
  215. // In order to start kernel session, we need a support DLL called KernelTraceControl.dll
  216. // This DLL is available by downloading the XPERF.exe tool (see
  217. // http://msdn.microsoft.com/en-us/performance/cc825801.aspx for instructions)
  218. // It is recommended that you get the 32 bit version of this (it works on 64 bit machines)
  219. // and build your EXE that uses TraceEvent to launch as a 32 bit application (This is
  220. // the default for VS 2010 projects).
  221. throw new DllNotFoundException("KernelTraceControl.dll missing from distribution.");
  222. }
  223. if (dwErr == 5 && Environment.OSVersion.Version.Major > 5) // On Vista and we get a 'Accessed Denied' message
  224. throw new UnauthorizedAccessException("Error Starting ETW: Access Denied (Administrator rights required to start ETW)");
  225. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(dwErr));
  226. m_IsActive = true;
  227. if (version >= 62 && StackCompression)
  228. TraceEventNativeMethods.EnableStackCaching(m_SessionHandle);
  229. return ret;
  230. }
  231. /// <summary>
  232. /// EventSources have a convention for converting its name to a GUID. Use this convention to
  233. /// convert 'name' to a GUID.
  234. /// </summary>
  235. public static Guid GetEventSourceGuidFromName(string name)
  236. {
  237. name = name.ToUpperInvariant(); // names are case insenstive.
  238. // The algorithm below is following the guidance of http://www.ietf.org/rfc/rfc4122.txt
  239. // Create a blob containing a 16 byte number representing the namespace
  240. // followed by the unicode bytes in the name.
  241. var bytes = new byte[name.Length * 2 + 16];
  242. uint namespace1 = 0x482C2DB2;
  243. uint namespace2 = 0xC39047c8;
  244. uint namespace3 = 0x87F81A15;
  245. uint namespace4 = 0xBFC130FB;
  246. // Write the bytes most-significant byte first.
  247. for (int i = 3; 0 <= i; --i)
  248. {
  249. bytes[i] = (byte)namespace1;
  250. namespace1 >>= 8;
  251. bytes[i + 4] = (byte)namespace2;
  252. namespace2 >>= 8;
  253. bytes[i + 8] = (byte)namespace3;
  254. namespace3 >>= 8;
  255. bytes[i + 12] = (byte)namespace4;
  256. namespace4 >>= 8;
  257. }
  258. // Write out the name, most significant byte first
  259. for (int i = 0; i < name.Length; i++)
  260. {
  261. bytes[2 * i + 16 + 1] = (byte)name[i];
  262. bytes[2 * i + 16] = (byte)(name[i] >> 8);
  263. }
  264. // Compute the Sha1 hash
  265. var sha1 = System.Security.Cryptography.SHA1.Create();
  266. byte[] hash = sha1.ComputeHash(bytes);
  267. // Create a GUID out of the first 16 bytes of the hash (SHA-1 create a 20 byte hash)
  268. int a = (((((hash[3] << 8) + hash[2]) << 8) + hash[1]) << 8) + hash[0];
  269. short b = (short)((hash[5] << 8) + hash[4]);
  270. short c = (short)((hash[7] << 8) + hash[6]);
  271. c = (short)((c & 0x0FFF) | 0x5000); // Set high 4 bits of octet 7 to 5, as per RFC 4122
  272. Guid guid = new Guid(a, b, c, hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]);
  273. return guid;
  274. }
  275. /// <summary>
  276. /// Add an additional USER MODE provider prepresented by 'providerGuid' (a list of
  277. /// providers is available by using 'logman query providers').
  278. /// </summary>
  279. /// <param name="providerGuid">
  280. /// The GUID that represents the event provider to turn on. Use 'logman query providers' or
  281. /// for a list of possible providers. Note that additional user mode (but not kernel mode)
  282. /// providers can be added to the session by using EnableProvider.</param>
  283. /// <param name="providerLevel">The verbosity to turn on</param>
  284. /// <param name="matchAnyKeywords">A bitvector representing the areas to turn on. Only the
  285. /// low 32 bits are used by classic providers and passed as the 'flags' value. Zero
  286. /// is a special value which is a provider defined default, which is usuall 'everything'</param>
  287. /// <param name="matchAllKeywords">A bitvector representing keywords of an event that must
  288. /// be on for a particular event for the event to be logged. A value of zero means
  289. /// that no keyword must be on, which effectively ignores this value. </param>
  290. /// <param name="options">Additional options for the provider (e.g. taking a stack trace)</param>
  291. /// <param name="values">This is set of key-value strings that are passed to the provider
  292. /// for provider-specific interpretation. Can be null if no additional args are needed.
  293. /// If the special key-value pair 'Command'='SendManifest' is provided, then the 'SendManifest'
  294. /// command will be sent (which causes EventSources to redump their manifest to the ETW log. </param>
  295. /// <returns>true if the session already existed and needed to be restarted.</returns>
  296. public bool EnableProvider(Guid providerGuid, TraceEventLevel providerLevel = TraceEventLevel.Verbose, ulong matchAnyKeywords = ulong.MaxValue, ulong matchAllKeywords = 0, TraceEventOptions options = 0, IEnumerable<KeyValuePair<string, string>> values = null)
  297. {
  298. byte[] valueData = null;
  299. int valueDataSize = 0;
  300. int valueDataType = 0;
  301. if (values != null)
  302. {
  303. valueDataType = 0; // ControllerCommand.Update // TODO use enumeration
  304. valueData = new byte[1024];
  305. foreach (KeyValuePair<string, string> keyValue in values)
  306. {
  307. if (keyValue.Key == "Command")
  308. {
  309. if (keyValue.Value == "SendManifest")
  310. valueDataType = -1; // ControllerCommand.SendManifest
  311. else
  312. {
  313. int val;
  314. if (int.TryParse(keyValue.Value, out val))
  315. valueDataType = val;
  316. }
  317. }
  318. valueDataSize += Encoding.UTF8.GetBytes(keyValue.Key, 0, keyValue.Key.Length, valueData, valueDataSize);
  319. if (valueDataSize >= 1023)
  320. throw new Exception("Too much provider data"); // TODO better message.
  321. valueData[valueDataSize++] = 0;
  322. valueDataSize += Encoding.UTF8.GetBytes(keyValue.Value, 0, keyValue.Value.Length, valueData, valueDataSize);
  323. if (valueDataSize >= 1023)
  324. throw new Exception("Too much provider data"); // TODO better message.
  325. valueData[valueDataSize++] = 0;
  326. }
  327. }
  328. return EnableProvider(providerGuid, providerLevel, matchAnyKeywords, matchAllKeywords, options, valueDataType, valueData, valueDataSize);
  329. }
  330. /// <summary>
  331. /// Disables a provider completely
  332. /// </summary>
  333. public void DisableProvider(Guid providerGuid)
  334. {
  335. int hr;
  336. try
  337. {
  338. try
  339. {
  340. // Try the Win7 API
  341. var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS { Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION };
  342. hr = TraceEventNativeMethods.EnableTraceEx2(
  343. m_SessionHandle, ref providerGuid, TraceEventNativeMethods.EVENT_CONTROL_CODE_DISABLE_PROVIDER,
  344. 0, 0, 0, 0, ref parameters);
  345. }
  346. catch (EntryPointNotFoundException)
  347. {
  348. // OK that did not work, try the VISTA API
  349. hr = TraceEventNativeMethods.EnableTraceEx(ref providerGuid, null, m_SessionHandle, 0, 0, 0, 0, 0, null);
  350. }
  351. }
  352. catch (EntryPointNotFoundException)
  353. {
  354. // Try with the old pre-vista API
  355. hr = TraceEventNativeMethods.EnableTrace(0, 0, 0, ref providerGuid, m_SessionHandle);
  356. }
  357. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr));
  358. }
  359. /// <summary>
  360. /// Once started, event sessions will persist even after the process that created them dies. They are
  361. /// only stoped by this explicit Stop() API.
  362. /// </summary>
  363. public bool Stop(bool noThrow = false)
  364. {
  365. if (m_Stopped)
  366. return true;
  367. m_Stopped = true;
  368. TraceEventNativeMethods.SetCpuSamplingRate(10000); // Set sample rate back to default 1 Msec
  369. var propertiesBuff = stackalloc byte[PropertiesSize];
  370. var properties = GetProperties(propertiesBuff);
  371. int hr = TraceEventNativeMethods.ControlTrace(0UL, m_SessionName, properties, TraceEventNativeMethods.EVENT_TRACE_CONTROL_STOP);
  372. if (hr != 4201) // Instance name not found. This means we did not start
  373. {
  374. if (!noThrow)
  375. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr));
  376. return false; // Stop failed
  377. }
  378. return true;
  379. }
  380. /// <summary>
  381. /// Sends the CAPUTURE_STATE command to the provider
  382. ///
  383. /// This routine only works Win7 and above, since previous versions don't have this concept. The providers also has
  384. /// to support it.
  385. ///
  386. /// You can use KernelTraceEventParser.ProviderGuid here to cause rundown for the system.
  387. /// </summary>
  388. /// <param name="providerGuid">The GUID that identifies the provider to send the CaptureState command to</param>
  389. /// <param name="matchAnyKeywords">The Keywords to send as part of the command (can influnced what is sent back)</param>
  390. /// <param name="filterType">if non-zero, this is passed along to the provider as type of the filter data.</param>
  391. /// <param name="data">If non-null this is either an int, or a byte array and is passed along as filter data.</param>
  392. public void CaptureState(Guid providerGuid, ulong matchAnyKeywords = ulong.MaxValue, int filterType = 0, object data = null)
  393. {
  394. var parameters = new TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS();
  395. var filter = new TraceEventNativeMethods.EVENT_FILTER_DESCRIPTOR();
  396. parameters.Version = TraceEventNativeMethods.ENABLE_TRACE_PARAMETERS_VERSION;
  397. byte[] asArray = data as byte[];
  398. if (data is int)
  399. {
  400. int intVal = (int)data;
  401. asArray = new byte[4];
  402. asArray[0] = (byte)intVal;
  403. asArray[1] = (byte)(intVal >> 8);
  404. asArray[2] = (byte)(intVal >> 16);
  405. asArray[3] = (byte)(intVal >> 24);
  406. }
  407. fixed (byte* filterDataPtr = asArray)
  408. {
  409. if (asArray != null)
  410. {
  411. parameters.EnableFilterDesc = &filter;
  412. filter.Type = filterType;
  413. filter.Size = asArray.Length;
  414. filter.Ptr = filterDataPtr;
  415. }
  416. int hr = TraceEventNativeMethods.EnableTraceEx2(
  417. m_SessionHandle, ref providerGuid, TraceEventNativeMethods.EVENT_CONTROL_CODE_CAPTURE_STATE,
  418. (byte)TraceEventLevel.Verbose, matchAnyKeywords, 0, 0, ref parameters);
  419. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr));
  420. }
  421. }
  422. /// <summary>
  423. /// Cause the log to be a circular buffer. The buffer size (in MegaBytes) is the value of this property.
  424. /// Setting this to 0 will cause it to revert to non-circular mode. This routine can only be called BEFORE
  425. /// a provider is enabled.
  426. /// </summary>
  427. public int CircularBufferMB
  428. {
  429. get { return m_CircularBufferMB; }
  430. set
  431. {
  432. if (IsActive)
  433. throw new InvalidOperationException("Property can't be changed after A provider has started.");
  434. if (m_FileName == null)
  435. throw new InvalidOperationException("Circular buffers only allowed on sessions with files.");
  436. m_CircularBufferMB = value;
  437. }
  438. }
  439. /// <summary>
  440. /// Sets the size of the buffer the operating system should reserve to avoid lost packets. Starts out
  441. /// as a very generous 32MB for files. If events are lost, this can be increased.
  442. /// </summary>
  443. public int BufferSizeMB
  444. {
  445. get { return m_BufferSizeMB; }
  446. set
  447. {
  448. if (IsActive)
  449. throw new InvalidOperationException("Property can't be changed after A provider has started.");
  450. m_BufferSizeMB = value;
  451. }
  452. }
  453. /// <summary>
  454. /// If set then Stop() will be called automatically when this object is Disposed or GCed (which
  455. /// will happen on program exit unless a unhandled exception occurs.
  456. /// </summary>
  457. public bool StopOnDispose { get { return m_StopOnDispose; } set { m_StopOnDispose = value; } }
  458. /// <summary>
  459. /// The name of the session that can be used by other threads to attach to the session.
  460. /// </summary>
  461. public string SessionName
  462. {
  463. get { return m_SessionName; }
  464. }
  465. /// <summary>
  466. /// The name of the moduleFile that events are logged to. Null means the session is real time.
  467. /// </summary>
  468. public string FileName
  469. {
  470. get
  471. {
  472. return m_FileName;
  473. }
  474. }
  475. /// <summary>
  476. /// Creating a TraceEventSession does not actually interact with the operating system until a
  477. /// provider is enabled. At that point the session is considered active (OS state that survives a
  478. /// process exit has been modified). IsActive returns true if the session is active.
  479. /// </summary>
  480. public bool IsActive
  481. {
  482. get
  483. {
  484. return m_IsActive;
  485. }
  486. }
  487. /// <summary>
  488. /// The rate at which CPU samples are collected. By default this is 1 (once a millisecond per CPU).
  489. /// There is alower bound on this (typically .125 Msec)
  490. /// </summary>
  491. public float CpuSampleIntervalMSec { get; set; }
  492. /// <summary>
  493. /// Indicate that this session should use compress the stacks to save space.
  494. /// Must be set before any providers are enabled. Currently only works for kernel events.
  495. /// TODO FIX NOW untested.
  496. /// </summary>
  497. public bool StackCompression { get; set; }
  498. // OS Heap Provider support.
  499. /// <summary>
  500. /// Turn on windows heap logging (stack for allocation) for a particular existing process.
  501. /// </summary>
  502. public void EnableWindowsHeapProvider(int pid)
  503. {
  504. throw new NotSupportedException("This version of PerfView does not support collection of OS Heap events.");
  505. }
  506. /// <summary>
  507. /// Turn on windows heap logging for a particular EXE file name (just the file name, no directory, but it DOES include the .exe extension)
  508. /// </summary>
  509. /// <param name="exeFileName"></param>
  510. public void EnableWindowsHeapProvider(string exeFileName)
  511. {
  512. throw new NotSupportedException("This version of PerfView does not support collection of OS Heap events.");
  513. }
  514. // CPU counter support
  515. /// <summary>
  516. /// Returned by GetProfileSourceInfo, describing the CPU counter (ProfileSource) available on the machine.
  517. /// </summary>
  518. public class ProfileSourceInfo
  519. {
  520. public string Name; // Human readable name of the CPU performance counter (eg BranchInstructions, TotalIssues ...)
  521. public int ID; // The ID that can be passed to SetProfileSources
  522. public int Interval; // This many events are skipped for each sample that is actually recorded
  523. public int MinInterval; // The smallest Interval can be (typically 4K)
  524. public int MaxInterval; // The largest Interval can be (typically maxInt).
  525. }
  526. /// <summary>
  527. /// Returns a ditionary of keyed by name of ProfileSourceInfo structures for all the CPU counters available on the machine.
  528. /// TODO FIX NOW remove log parameter.
  529. /// </summary>
  530. public static unsafe Dictionary<string, ProfileSourceInfo> GetProfileSourceInfo()
  531. {
  532. var version = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor;
  533. if (version < 62)
  534. throw new ApplicationException("Profile source only availabe on Win8 and beyond.");
  535. var ret = new Dictionary<string, ProfileSourceInfo>(StringComparer.OrdinalIgnoreCase);
  536. // Figure out how much space we need.
  537. int retLen = 0;
  538. var result = TraceEventNativeMethods.TraceQueryInformation(0,
  539. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceProfileSourceListInfo,
  540. null, 0, ref retLen);
  541. Debug.Assert(result == 24); // Not enough space.
  542. if (retLen != 0)
  543. {
  544. // Do it for real.
  545. byte* buffer = stackalloc byte[retLen];
  546. result = TraceEventNativeMethods.TraceQueryInformation(0,
  547. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceProfileSourceListInfo,
  548. buffer, retLen, ref retLen);
  549. if (result == 0)
  550. {
  551. var interval = new TraceEventNativeMethods.TRACE_PROFILE_INTERVAL();
  552. var profileSource = (TraceEventNativeMethods.PROFILE_SOURCE_INFO*)buffer;
  553. for (int i = 0; i < 10; i++)
  554. {
  555. char* namePtr = (char*)&profileSource[1]; // points off the end of the array;
  556. interval.Source = profileSource->Source;
  557. interval.Interval = 0;
  558. result = TraceEventNativeMethods.TraceQueryInformation(0,
  559. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceSampledProfileIntervalInfo,
  560. &interval, sizeof(TraceEventNativeMethods.TRACE_PROFILE_INTERVAL), ref retLen);
  561. if (result != 0)
  562. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(result));
  563. var name = new string(namePtr);
  564. ret.Add(name, new ProfileSourceInfo()
  565. {
  566. Name = name,
  567. ID = profileSource->Source,
  568. Interval = interval.Interval,
  569. MinInterval = profileSource->MinInterval,
  570. MaxInterval = profileSource->MaxInterval,
  571. });
  572. if (profileSource->NextEntryOffset == 0)
  573. break;
  574. profileSource = (TraceEventNativeMethods.PROFILE_SOURCE_INFO*)(profileSource->NextEntryOffset + (byte*)profileSource);
  575. }
  576. }
  577. else
  578. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(result));
  579. }
  580. return ret;
  581. }
  582. /// <summary>
  583. /// Sets the Profile Sources (CPU machine counters) that will be used if PMC (Precise Machine Counters)
  584. /// are turned on. Each CPU counter is given a id (the profileSourceID) and has an interval
  585. /// (the number of counts you skip for each event you log). You can get the human name for
  586. /// all the supported CPU counters by calling GetProfileSourceInfo. Then choose the ones you want
  587. /// and configure them here (the first array indicating the CPU counters to enable, and the second
  588. /// array indicating the interval. The second array can be shorter then the first, in which case
  589. /// the existing interval is used (it persists and has a default on boot).
  590. /// </summary>
  591. public static unsafe void SetProfileSources(int[] profileSourceIDs, int[] profileSourceIntervals)
  592. {
  593. var version = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor;
  594. if (version < 62)
  595. throw new ApplicationException("Profile source only availabe on Win8 and beyond.");
  596. TraceEventNativeMethods.SetSystemProfilePrivilege();
  597. var interval = new TraceEventNativeMethods.TRACE_PROFILE_INTERVAL();
  598. for (int i = 0; i < profileSourceIntervals.Length; i++)
  599. {
  600. interval.Source = profileSourceIDs[i];
  601. interval.Interval = profileSourceIntervals[i];
  602. var result = TraceEventNativeMethods.TraceSetInformation(0,
  603. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceSampledProfileIntervalInfo,
  604. &interval, sizeof(TraceEventNativeMethods.TRACE_PROFILE_INTERVAL));
  605. if (result != 0)
  606. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(result));
  607. }
  608. fixed (int* sourcesPtr = profileSourceIDs)
  609. {
  610. var result = TraceEventNativeMethods.TraceSetInformation(0,
  611. TraceEventNativeMethods.TRACE_INFO_CLASS.TraceProfileSourceConfigInfo,
  612. sourcesPtr, profileSourceIDs.Length * sizeof(int));
  613. if (result != 0)
  614. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(result));
  615. }
  616. }
  617. // Post processing (static methods)
  618. /// <summary>
  619. /// It is sometimes useful to merge the contents of several ETL files into a single
  620. /// output ETL file. This routine does that. It also will attach additional
  621. /// information that will allow correct file name and symbolic lookup if the
  622. /// ETL file is used on a machine other than the one that the data was collected on.
  623. /// If you wish to transport the file to another machine you need to merge them.
  624. /// </summary>
  625. /// <param name="inputETLFileNames"></param>
  626. /// <param name="outputETLFileName"></param>
  627. public static void Merge(string[] inputETLFileNames, string outputETLFileName)
  628. {
  629. IntPtr state = IntPtr.Zero;
  630. // If we happen to be in the WOW, disable file system redirection as you don't get the System32 dlls otherwise.
  631. bool disableRedirection = TraceEventNativeMethods.Wow64DisableWow64FsRedirection(ref state);
  632. try
  633. {
  634. Debug.Assert(disableRedirection || System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8);
  635. int retValue = TraceEventNativeMethods.CreateMergedTraceFile(
  636. outputETLFileName, inputETLFileNames, inputETLFileNames.Length,
  637. TraceEventNativeMethods.EVENT_TRACE_MERGE_EXTENDED_DATA.IMAGEID |
  638. TraceEventNativeMethods.EVENT_TRACE_MERGE_EXTENDED_DATA.BUILDINFO |
  639. TraceEventNativeMethods.EVENT_TRACE_MERGE_EXTENDED_DATA.WINSAT |
  640. TraceEventNativeMethods.EVENT_TRACE_MERGE_EXTENDED_DATA.EVENT_METADATA |
  641. TraceEventNativeMethods.EVENT_TRACE_MERGE_EXTENDED_DATA.VOLUME_MAPPING);
  642. if (retValue != 0)
  643. throw new ApplicationException("Merge operation failed.");
  644. }
  645. finally
  646. {
  647. if (disableRedirection)
  648. TraceEventNativeMethods.Wow64RevertWow64FsRedirection(state);
  649. }
  650. }
  651. /// <summary>
  652. /// This variation of the Merge command takes the 'primary' etl file name (X.etl)
  653. /// and will merge in any files that match .clr*.etl .user*.etl. and .kernel.etl.
  654. /// </summary>
  655. public static void MergeInPlace(string etlFileName, TextWriter log)
  656. {
  657. var dir = Path.GetDirectoryName(etlFileName);
  658. if (dir.Length == 0)
  659. dir = ".";
  660. var baseName = Path.GetFileNameWithoutExtension(etlFileName);
  661. List<string> mergeInputs = new List<string>();
  662. mergeInputs.Add(etlFileName);
  663. mergeInputs.AddRange(Directory.GetFiles(dir, baseName + ".kernel.etl"));
  664. mergeInputs.AddRange(Directory.GetFiles(dir, baseName + ".clr*.etl"));
  665. mergeInputs.AddRange(Directory.GetFiles(dir, baseName + ".user*.etl"));
  666. string tempName = Path.ChangeExtension(etlFileName, ".etl.new");
  667. try
  668. {
  669. // Do the merge;
  670. Merge(mergeInputs.ToArray(), tempName);
  671. // Delete the originals.
  672. foreach (var mergeInput in mergeInputs)
  673. FileUtilities.ForceDelete(mergeInput);
  674. // Place the output in its final resting place.
  675. FileUtilities.ForceMove(tempName, etlFileName);
  676. }
  677. finally
  678. {
  679. // Insure we clean up.
  680. if (File.Exists(tempName))
  681. File.Delete(tempName);
  682. }
  683. }
  684. // Session Discovery
  685. /// <summary>
  686. /// ETW trace sessions survive process shutdown. Thus you can attach to existing active sessions.
  687. /// GetActiveSessionNames() returns a list of currently existing session names. These can be passed
  688. /// to the code:TraceEventSession constructor to control it.
  689. /// </summary>
  690. /// <returns>A enumeration of strings, each of which is a name of a session</returns>
  691. public unsafe static IEnumerable<string> GetActiveSessionNames()
  692. {
  693. const int MAX_SESSIONS = 64;
  694. int sizeOfProperties = sizeof(TraceEventNativeMethods.EVENT_TRACE_PROPERTIES) +
  695. sizeof(char) * MaxNameSize + // For log moduleFile name
  696. sizeof(char) * MaxNameSize; // For session name
  697. byte* sessionsArray = stackalloc byte[MAX_SESSIONS * sizeOfProperties];
  698. TraceEventNativeMethods.EVENT_TRACE_PROPERTIES** propetiesArray = stackalloc TraceEventNativeMethods.EVENT_TRACE_PROPERTIES*[MAX_SESSIONS];
  699. for (int i = 0; i < MAX_SESSIONS; i++)
  700. {
  701. TraceEventNativeMethods.EVENT_TRACE_PROPERTIES* properties = (TraceEventNativeMethods.EVENT_TRACE_PROPERTIES*)&sessionsArray[sizeOfProperties * i];
  702. properties->Wnode.BufferSize = (uint)sizeOfProperties;
  703. properties->LoggerNameOffset = (uint)sizeof(TraceEventNativeMethods.EVENT_TRACE_PROPERTIES);
  704. properties->LogFileNameOffset = (uint)sizeof(TraceEventNativeMethods.EVENT_TRACE_PROPERTIES) + sizeof(char) * MaxNameSize;
  705. propetiesArray[i] = properties;
  706. }
  707. int sessionCount = 0;
  708. int hr = TraceEventNativeMethods.QueryAllTraces((IntPtr)propetiesArray, MAX_SESSIONS, ref sessionCount);
  709. Marshal.ThrowExceptionForHR(TraceEventNativeMethods.GetHRFromWin32(hr));
  710. List<string> activeTraceNames = new List<string>();
  711. for (int i = 0; i < sessionCount; i++)
  712. {
  713. byte* propertiesBlob = (byte*)propetiesArray[i];
  714. string sessionName = new string((char*)(&propertiesBlob[propetiesArray[i]->LoggerNameOffset]));
  715. activeTraceNames.Add(sessionName);
  716. }
  717. return activeTraceNames;
  718. }
  719. // Dicovering providers and their Keywords
  720. /// <summary>
  721. /// Returns the names of every registered provider on the system. This is a long list (1000s of entries.
  722. /// You can get its Guid with GetProviderByName.
  723. /// </summary>
  724. public static IEnumerable<string> RegisteredProviders
  725. {
  726. get
  727. {
  728. return ProviderNameToGuid.Keys;
  729. }
  730. }
  731. /// <summary>
  732. /// Returns a list of provider GUIDs that are registered in a process with 'processID'.
  733. /// This is a nice way to filter down the providers you might care about.
  734. /// </summary>
  735. public static List<Guid> ProvidersInProcess(int processID)
  736. {
  737. var ret = new List<Guid>();
  738. // For every provider
  739. foreach (var guid in ProviderNameToGuid.Values)
  740. {
  741. // See what process it is in.
  742. int buffSize = 0;
  743. Guid localGuid = guid;
  744. var hr = TraceEventNativeMethods.EnumerateTraceGuidsEx(TraceEventNativeMethods.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
  745. &localGuid, sizeof(Guid), null, 0, ref buffSize);
  746. if (hr != 122)
  747. continue; // TODO should we be ignoring errors?
  748. Debug.Assert(hr == 122); // ERROR_INSUFFICIENT_BUFFER
  749. var buffer = stackalloc byte[buffSize];
  750. hr = TraceEventNativeMethods.EnumerateTraceGuidsEx(TraceEventNativeMethods.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
  751. &localGuid, sizeof(Guid), buffer, buffSize, ref buffSize);
  752. if (hr != 0)
  753. throw new InvalidOperationException("TraceGuidQueryInfo failed."); // TODO better error message
  754. var providerInfos = (TraceEventNativeMethods.TRACE_GUID_INFO*)buffer;
  755. var provider = (TraceEventNativeMethods.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1];
  756. for (int i = 0; i < providerInfos->InstanceCount; i++)
  757. {
  758. if (provider->Pid == processID)
  759. {
  760. ret.Add(guid);
  761. break; // We can go on since we found what we were looking for.
  762. }
  763. if (provider->NextOffset == 0)
  764. break;
  765. Debug.Assert(0 <= provider->NextOffset && provider->NextOffset < buffSize);
  766. var structBase = (byte*)provider;
  767. provider = (TraceEventNativeMethods.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[provider->NextOffset];
  768. }
  769. }
  770. return ret;
  771. }
  772. /// <summary>
  773. /// Given the friendly name of a provider (e.g. Microsoft-Windows-DotNETRuntimeStress) return the
  774. /// GUID for the provider. Returns Guid.Empty on failure.
  775. /// </summary>
  776. public static Guid GetProviderByName(string name)
  777. {
  778. Guid ret;
  779. ProviderNameToGuid.TryGetValue(name, out ret);
  780. return ret;
  781. }
  782. /// <summary>
  783. /// Finds the friendly name for 'providerGuid' Returns the Guid as a string if can't be found
  784. /// </summary>
  785. public static string GetProviderName(Guid providerGuid)
  786. {
  787. string ret;
  788. ProviderGuidToName.TryGetValue(providerGuid, out ret);
  789. if (ret == null)
  790. ret = providerGuid.ToString();
  791. return ret;
  792. }
  793. /// <summary>
  794. /// Returns the keywords the provider represented by 'providerGuid' supports.
  795. /// </summary>
  796. public static List<ProviderDataItem> GetProviderKeywords(Guid providerGuid)
  797. {
  798. return GetProviderFields(providerGuid, TraceEventNativeMethods.EVENT_FIELD_TYPE.EventKeywordInformation);
  799. }
  800. // Misc
  801. /// <summary>
  802. /// Is the current process Elevated (allowed to turn on a ETW provider). Does not really belong here
  803. /// but it useful since ETW does need to be elevated.
  804. /// </summary>
  805. /// <returns></returns>
  806. public static bool? IsElevated() { return TraceEventNativeMethods.IsElevated(); }
  807. #region Private
  808. /// <summary>
  809. /// Returns a sorted dictionary of names and Guids for every provider registered on the system.
  810. /// </summary>
  811. private static SortedDictionary<string, Guid> ProviderNameToGuid
  812. {
  813. get
  814. {
  815. if (s_providersByName == null)
  816. {
  817. s_providersByName = new SortedDictionary<string, Guid>();
  818. int buffSize = 0;
  819. var hr = TraceEventNativeMethods.TdhEnumerateProviders(null, ref buffSize);
  820. Debug.Assert(hr == 122); // ERROR_INSUFFICIENT_BUFFER
  821. var buffer = stackalloc byte[buffSize];
  822. var providersDesc = (TraceEventNativeMethods.PROVIDER_ENUMERATION_INFO*)buffer;
  823. hr = TraceEventNativeMethods.TdhEnumerateProviders(providersDesc, ref buffSize);
  824. if (hr != 0)
  825. throw new InvalidOperationException("TdhEnumerateProviders failed."); // TODO better error message
  826. var providers = (TraceEventNativeMethods.TRACE_PROVIDER_INFO*)&providersDesc[1];
  827. for (int i = 0; i < providersDesc->NumberOfProviders; i++)
  828. {
  829. var name = new string((char*)&buffer[providers[i].ProviderNameOffset]);
  830. s_providersByName[name] = providers[i].ProviderGuid;
  831. }
  832. }
  833. return s_providersByName;
  834. }
  835. }
  836. private static Dictionary<Guid, string> ProviderGuidToName
  837. {
  838. get
  839. {
  840. if (s_providerNames == null)
  841. {
  842. foreach (var keyValue in ProviderNameToGuid)
  843. s_providerNames[keyValue.Value] = keyValue.Key;
  844. }
  845. return s_providerNames;
  846. }
  847. }
  848. static SortedDictionary<string, Guid> s_providersByName;
  849. static Dictionary<Guid, string> s_providerNames;
  850. private static List<ProviderDataItem> GetProviderFields(Guid providerGuid, TraceEventNativeMethods.EVENT_FIELD_TYPE fieldType)
  851. {
  852. var ret = new List<ProviderDataItem>();
  853. int buffSize = 0;
  854. var hr = TraceEventNativeMethods.TdhEnumerateProviderFieldInformation(ref providerGuid, fieldType, null, ref buffSize);
  855. if (hr != 122)
  856. return ret; // TODO FIX NOW Do I want to simply return nothing or give a more explicit error?
  857. Debug.Assert(hr == 122); // ERROR_INSUFFICIENT_BUFFER
  858. var buffer = stackalloc byte[buffSize];
  859. var fieldsDesc = (TraceEventNativeMethods.PROVIDER_FIELD_INFOARRAY*)buffer;
  860. hr = TraceEventNativeMethods.TdhEnumerateProviderFieldInformation(ref providerGuid, fieldType, fieldsDesc, ref buffSize);
  861. if (hr != 0)
  862. throw new InvalidOperationException("TdhEnumerateProviderFieldInformation failed."); // TODO better error message
  863. var fields = (TraceEventNativeMethods.PROVIDER_FIELD_INFO*)&fieldsDesc[1];
  864. for (int i = 0; i < fieldsDesc->NumberOfElements; i++)
  865. {
  866. var field = new ProviderDataItem();
  867. field.Name = new string((char*)&buffer[fields[i].NameOffset]);
  868. field.Description = new string((char*)&buffer[fields[i].DescriptionOffset]);
  869. field.Value = fields[i].Value;
  870. ret.Add(field);
  871. }
  872. return ret;
  873. }
  874. /// <summary>
  875. /// We wrap this because sadly the PMC suppport is private, so we have to do it a different way if that is present.
  876. /// </summary>
  877. int StartKernelTrace(
  878. out UInt64 TraceHandle,
  879. TraceEventNativeMethods.EVENT_TRACE_PROPERTIES* properties,
  880. TraceEventNativeMethods.STACK_TRACING_EVENT_ID* s