Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
Profiler.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6 using System.Linq;
7 using System.Text;
8 using System.Threading;
9 using SiliconStudio.Core.Collections;
10 
11 namespace SiliconStudio.Core.Diagnostics
12 {
13  /// <summary>
14  /// Delegate called when a <see cref="ProfilingState"/> is disposed (end of profiling).
15  /// </summary>
16  /// <param name="profilingState">State of the profile.</param>
17  public delegate void ProfilerDisposeEventDelegate(ref ProfilingState profilingState);
18 
19  /// <summary>
20  /// High level CPU Profiler. For usage see remarks.
21  /// </summary>
22  /// <remarks>
23  /// This class is a lightweight profiler that can log detailed KPI (Key Performance Indicators) of an application.
24  /// To use it, simply enclose in a <c>using</c> code the section of code you want to profile:
25  /// <code>
26  /// public static readonly ProfilingKey GameInitialization = new ProfilingKey("Game", "Initialization");
27  ///
28  /// // This will log a 'Begin' profiling event.
29  /// using (var profile = Profiler.Begin(GameInitialization))
30  /// {
31  /// // Long running code here...
32  ///
33  ///
34  /// // You can log 'Mark' profiling event
35  /// profile.Mark("CriticalPart");
36  ///
37  /// // Adds an attribute that will be logged in End event
38  /// profile.SetAttribute("ModelCount", modelCount);
39  /// } // here a 'End' profiling event will be issued.
40  /// </code>
41  /// By default, the profiler is not enabled, so there is a minimum performance impact leaving it in the code (it has
42  /// the costs of a lock and a dictionary lookup). It doesn't measure anything and doesn't produce any KPI.
43  ///
44  /// To enable a particular profiler (before using <see cref="Begin()"/> method):
45  /// <code>
46  /// Profiler.Enable(GameInitialization);
47  /// </code>
48  /// To enable all profilers, use <c>Profiler.Enable()</c> method.
49  ///
50  /// When the profiler is enabled, it is logged using the logging system through the standard <see cref="Logger"/> infrastructure.
51  /// The logger module name used is "Profile." concatenates with the name of the profile.
52  ///
53  /// Note also that when profiling, it is possible to attach some property values (counters, indicators...etc.) to a profiler state. This
54  /// property values will be displayed along the standard profiler state. You can use <see cref="ProfilingState.SetAttribute"/> to attach
55  /// a property value to a <see cref="ProfilingState"/>.
56  /// </remarks>
57  public static class Profiler
58  {
59  internal static Logger Logger = GlobalLogger.GetLogger("Profiler"); // Global logger for all profiling
60  private static readonly FastList<ProfilingEvent> events = new FastList<ProfilingEvent>();
61  private static readonly Dictionary<ProfilingKey, ProfilingResult> eventsByKey = new Dictionary<ProfilingKey, ProfilingResult>();
62  private static readonly List<ProfilingResult> profilingResults = new List<ProfilingResult>();
63  private static readonly StringBuilder profilerResultBuilder = new StringBuilder();
64  private static readonly object Locker = new object();
65  private static bool enableAll;
66  private static int profileId;
67 
68  /// <summary>
69  /// Enables all profilers.
70  /// </summary>
71  public static void EnableAll()
72  {
73  lock (Locker)
74  {
75  enableAll = true;
76  }
77  }
78 
79  /// <summary>
80  /// Disable all profilers.
81  /// </summary>
82  public static void DisableAll()
83  {
84  lock (Locker)
85  lock (ProfilingKey.AllKeys)
86  {
87  foreach (var profilingKey in ProfilingKey.AllKeys)
88  {
89  profilingKey.Enabled = false;
90  }
91 
92  enableAll = false;
93  }
94  }
95 
96  /// <summary>
97  /// Enables the specified profiler.
98  /// </summary>
99  /// <param name="profilingKey">The profile key.</param>
100  public static bool IsEnabled(ProfilingKey profilingKey)
101  {
102  return enableAll || profilingKey.Enabled;
103  }
104 
105  /// <summary>
106  /// Enables the specified profiler.
107  /// </summary>
108  /// <param name="profilingKey">The profile key.</param>
109  public static void Enable(ProfilingKey profilingKey)
110  {
111  lock (Locker)
112  {
113  profilingKey.Enabled = true;
114  foreach (var child in profilingKey.Children)
115  {
116  Enable(child);
117  }
118  }
119  }
120 
121  /// <summary>
122  /// Disables the specified profiler.
123  /// </summary>
124  /// <param name="profilingKey">The profile key.</param>
125  public static void Disable(ProfilingKey profilingKey)
126  {
127  lock (Locker)
128  {
129  profilingKey.Enabled = false;
130  foreach (var child in profilingKey.Children)
131  {
132  Disable(child);
133  }
134  }
135  }
136 
137  /// <summary>
138  /// Creates a profiler with the specified name. The returned object must be disposed at the end of the section
139  /// being profiled. See remarks.
140  /// </summary>
141  /// <param name="profilingKey">The profile key.</param>
142  /// <returns>A profiler state.</returns>
143  /// <remarks>It is recommended to call this method with <c>using (var profile = Profiler.Profile(...))</c> in order to make sure that the Dispose() method will be called on the
144  /// <see cref="ProfilingState" /> returned object.</remarks>
145  public static ProfilingState New(ProfilingKey profilingKey)
146  {
147  if (profilingKey == null) throw new ArgumentNullException("profilingKey");
148 
149  var localProfileId = Interlocked.Increment(ref profileId) - 1;
150  var isProfileActive = IsEnabled(profilingKey);
151 
152  return new ProfilingState(localProfileId, profilingKey, isProfileActive);
153  }
154 
155  /// <summary>
156  /// Creates a profiler with the specified key. The returned object must be disposed at the end of the section
157  /// being profiled. See remarks.
158  /// </summary>
159  /// <param name="profilingKey">The profile key.</param>
160  /// <returns>A profiler state.</returns>
161  /// <remarks>It is recommended to call this method with <c>using (var profile = Profiler.Profile(...))</c> in order to make sure that the Dispose() method will be called on the
162  /// <see cref="ProfilingState" /> returned object.</remarks>
163  public static ProfilingState Begin(ProfilingKey profilingKey)
164  {
165  var profiler = New(profilingKey);
166  profiler.Begin();
167  return profiler;
168  }
169 
170  /// <summary>
171  /// Creates a profiler with the specified key. The returned object must be disposed at the end of the section
172  /// being profiled. See remarks.
173  /// </summary>
174  /// <param name="profilingKey">The profile key.</param>
175  /// <param name="text">The text to log with the profile.</param>
176  /// <returns>A profiler state.</returns>
177  /// <remarks>It is recommended to call this method with <c>using (var profile = Profiler.Profile(...))</c> in order to make sure that the Dispose() method will be called on the
178  /// <see cref="ProfilingState" /> returned object.</remarks>
179  public static ProfilingState Begin(ProfilingKey profilingKey, string text)
180  {
181  var profiler = New(profilingKey);
182  profiler.Begin(text);
183  return profiler;
184  }
185 
186  /// <summary>
187  /// Creates a profiler with the specified key. The returned object must be disposed at the end of the section
188  /// being profiled. See remarks.
189  /// </summary>
190  /// <param name="profilingKey">The profile key.</param>
191  /// <param name="textFormat">The text to format.</param>
192  /// <param name="textFormatArguments">The text format arguments.</param>
193  /// <returns>A profiler state.</returns>
194  /// <remarks>It is recommended to call this method with <c>using (var profile = Profiler.Profile(...))</c> in order to make sure that the Dispose() method will be called on the
195  /// <see cref="ProfilingState" /> returned object.</remarks>
196  public static ProfilingState Begin(ProfilingKey profilingKey, string textFormat, params object[] textFormatArguments)
197  {
198  var profiler = New(profilingKey);
199  profiler.Begin(textFormat, textFormatArguments);
200  return profiler;
201  }
202 
203  /// <summary>
204  /// Resets the id counter to zero and disable all registered profiles.
205  /// </summary>
206  public static void Reset()
207  {
208  DisableAll();
209  profileId = 0;
210  }
211 
212  public static void ProcessEvent(ref ProfilingEvent profilingEvent)
213  {
214  // Add event
215  lock (Locker)
216  {
217  events.Add(profilingEvent);
218  }
219 
220  // Log it
221  if ((profilingEvent.Key.Flags & ProfilingKeyFlags.Log) != 0)
222  Logger.Log(new ProfilingMessage(profilingEvent.Id, profilingEvent.Key, profilingEvent.Type) { Attributes = profilingEvent.Attributes, ElapsedTime = new TimeSpan((profilingEvent.ElapsedTime * 10000000) / Stopwatch.Frequency), Text = profilingEvent.Text });
223  }
224 
225  struct ProfilingResult
226  {
227  public ProfilingKey Key;
228  public long AccumulatedTime;
229  public long MinTime;
230  public long MaxTime;
231  public int Count;
232  }
233 
234  public static string ReportEvents()
235  {
236  lock (Locker)
237  {
238  if (events.Count == 0)
239  return "No profiling events.";
240 
241  // Group by profiling keys
242  var elapsedTime = events.Count > 0 ? events[events.Count - 1].TimeStamp - events[0].TimeStamp : 0;
243 
244  foreach (var profilingEvent in events)
245  {
246  ProfilingResult profilingResult;
247  if (!eventsByKey.TryGetValue(profilingEvent.Key, out profilingResult))
248  {
249  profilingResult.Key = profilingEvent.Key;
250  profilingResult.MinTime = long.MaxValue;
251  }
252 
253  //if (profilingEvent.Type == ProfilingMessageType.Begin)
254  //{
255  // Console.WriteLine("{0} {1}: {2}", profilingEvent.TimeStamp, profilingEvent.Type, profilingEvent.Key.Name);
256  //}
257  //else if (profilingEvent.Type == ProfilingMessageType.End)
258  //{
259  // Console.WriteLine("{0} {1}: {2} ({3})", profilingEvent.TimeStamp, profilingEvent.Type, profilingEvent.Key.Name, profilingEvent.ElapsedTime);
260  //}
261 
262  if (profilingEvent.Type == ProfilingMessageType.End)
263  {
264  profilingResult.AccumulatedTime += profilingEvent.ElapsedTime;
265  if (profilingEvent.ElapsedTime < profilingResult.MinTime)
266  profilingResult.MinTime = profilingEvent.ElapsedTime;
267  if (profilingEvent.ElapsedTime > profilingResult.MaxTime)
268  profilingResult.MaxTime = profilingEvent.ElapsedTime;
269 
270  }
271  else // counter incremented only for Begin and Mark
272  {
273  profilingResult.Count++;
274  }
275 
276  eventsByKey[profilingResult.Key] = profilingResult;
277  }
278 
279  foreach (var profilingResult in eventsByKey)
280  {
281  profilingResults.Add(profilingResult.Value);
282  }
283 
284  // Clear events
285  events.Clear();
286  eventsByKey.Clear();
287 
288  // Sort by accumulated time
289  profilingResults.Sort((x1, x2) => Math.Sign(x2.AccumulatedTime - x1.AccumulatedTime));
290 
291  // Generate result string
292  foreach (var profilingResult in profilingResults)
293  {
294  profilerResultBuilder.AppendFormat("{0,5:P1} | ", (double)profilingResult.AccumulatedTime / (double)elapsedTime);
295  AppendTime(profilerResultBuilder, profilingResult.AccumulatedTime);
296 
297  profilerResultBuilder.Append(" | ");
298  AppendTime(profilerResultBuilder, profilingResult.MinTime);
299  profilerResultBuilder.Append(" | ");
300  AppendTime(profilerResultBuilder, profilingResult.Count != 0 ? profilingResult.AccumulatedTime / profilingResult.Count : 0);
301  profilerResultBuilder.Append(" | ");
302  AppendTime(profilerResultBuilder, profilingResult.MaxTime);
303 
304  profilerResultBuilder.AppendFormat(" | {0:00000} | {1}", profilingResult.Count, profilingResult.Key);
305  profilerResultBuilder.AppendLine();
306  }
307 
308  profilingResults.Clear();
309 
310  var result = profilerResultBuilder.ToString();
311  profilerResultBuilder.Clear();
312  return result;
313  }
314  }
315 
316  private static void AppendTime(StringBuilder builder, long accumulatedTime)
317  {
318  var accumulatedTimeSpan = new TimeSpan((accumulatedTime * 10000000) / Stopwatch.Frequency);
319  if (accumulatedTimeSpan > new TimeSpan(0, 0, 1, 0))
320  {
321  builder.AppendFormat("{0:000.000}m ", accumulatedTimeSpan.TotalMinutes);
322  }
323  else if (accumulatedTimeSpan > new TimeSpan(0, 0, 0, 0, 1000))
324  {
325  builder.AppendFormat("{0:000.000}s ", accumulatedTimeSpan.TotalSeconds);
326  }
327  else
328  {
329  builder.AppendFormat("{0:000.000}ms", accumulatedTimeSpan.TotalMilliseconds);
330  }
331  }
332  }
333 }
static void ProcessEvent(ref ProfilingEvent profilingEvent)
Definition: Profiler.cs:212
A profiler state contains information of a portion of code being profiled. See remarks.
High level CPU Profiler. For usage see remarks.
Definition: Profiler.cs:57
static ProfilingState New(ProfilingKey profilingKey)
Creates a profiler with the specified name. The returned object must be disposed at the end of the se...
Definition: Profiler.cs:145
A key to identify a specific profile.
Definition: ProfilingKey.cs:11
delegate void ProfilerDisposeEventDelegate(ref ProfilingState profilingState)
Delegate called when a ProfilingState is disposed (end of profiling).
static ProfilingState Begin(ProfilingKey profilingKey, string text)
Creates a profiler with the specified key. The returned object must be disposed at the end of the sec...
Definition: Profiler.cs:179
static bool IsEnabled(ProfilingKey profilingKey)
Enables the specified profiler.
Definition: Profiler.cs:100
List< ProfilingKey > Children
Gets the children.
Definition: ProfilingKey.cs:77
Base implementation for ILogger.
Definition: Logger.cs:10
ProfilingMessageType
Type of a profiling message.
static void Enable(ProfilingKey profilingKey)
Enables the specified profiler.
Definition: Profiler.cs:109
A log message generate by Profiler.
static ProfilingState Begin(ProfilingKey profilingKey, string textFormat, params object[] textFormatArguments)
Creates a profiler with the specified key. The returned object must be disposed at the end of the sec...
Definition: Profiler.cs:196
static void DisableAll()
Disable all profilers.
Definition: Profiler.cs:82
void Log(ILogMessage logMessage)
Logs the specified log message.
Definition: Logger.cs:100
static void EnableAll()
Enables all profilers.
Definition: Profiler.cs:71
static ProfilingState Begin(ProfilingKey profilingKey)
Creates a profiler with the specified key. The returned object must be disposed at the end of the sec...
Definition: Profiler.cs:163
static void Disable(ProfilingKey profilingKey)
Disables the specified profiler.
Definition: Profiler.cs:125
static void Reset()
Resets the id counter to zero and disable all registered profiles.
Definition: Profiler.cs:206
A profiling event generated by Profiler.