PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/DICK.B1/IronPython/Runtime/Profiler.cs

https://bitbucket.org/williamybs/uidipythontool
C# | 416 lines | 305 code | 47 blank | 64 comment | 20 complexity | fee1190040e1d3bb6bfe2f83d26b45eb MD5 | raw file
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Microsoft Public License. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Microsoft Public License, please send an email to
  8. * ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Microsoft Public License.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. #if !CLR2
  16. using MSAst = System.Linq.Expressions;
  17. #else
  18. using MSAst = Microsoft.Scripting.Ast;
  19. #endif
  20. using System;
  21. using System.Collections.Generic;
  22. using System.Diagnostics;
  23. using System.Reflection;
  24. using System.Reflection.Emit;
  25. using System.Text;
  26. using System.Threading;
  27. using Microsoft.Scripting;
  28. using Microsoft.Scripting.Actions.Calls;
  29. using IronPython.Compiler;
  30. namespace IronPython.Runtime {
  31. using Ast = MSAst.Expression;
  32. using AstUtils = Microsoft.Scripting.Ast.Utils;
  33. /// <summary>
  34. /// Manages the acquisition of profiling data for a single ScriptRuntime
  35. /// </summary>
  36. public sealed class Profiler {
  37. private readonly Dictionary<MethodBase/*!*/, int>/*!*/ _methods; // Unique lookup of methods -> profile indices
  38. private readonly Dictionary<string/*!*/, int>/*!*/ _names; // Unique lookup of names -> profile indices; not all names are unique
  39. private readonly List<string/*!*/>/*!*/ _counters;
  40. private readonly List<long[,]>/*!*/ _profiles;
  41. private long[,] _profileData;
  42. const int _initialSize = 100; // we double each time we run out of space
  43. // Indexes into profile data
  44. const int TimeInBody = 0;
  45. const int TimeInChildMethods = 1;
  46. const int NumberOfCalls = 2;
  47. [MultiRuntimeAware]
  48. private static readonly object _profileKey = new object();
  49. /// <summary>
  50. /// Get the unique Profiler instance for this ScriptRuntime
  51. /// </summary>
  52. public static Profiler GetProfiler(PythonContext/*!*/ context) {
  53. return context.GetOrCreateModuleState(_profileKey, () => new Profiler());
  54. }
  55. private Profiler() {
  56. _methods = new Dictionary<MethodBase, int>();
  57. _names = new Dictionary<string, int>();
  58. _counters = new List<string>();
  59. _profiles = new List<long[,]>();
  60. _profileData = new long[_initialSize, 3];
  61. }
  62. private static string FormatMethodName(MethodBase method) {
  63. var sb = new StringBuilder();
  64. if (method.DeclaringType != null) {
  65. sb.Append("type ");
  66. sb.Append(method.DeclaringType.Name);
  67. sb.Append(": ");
  68. }
  69. sb.Append("method: ");
  70. sb.Append(method.Name);
  71. sb.Append('(');
  72. bool comma = false;
  73. foreach (var p in method.GetParameters()) {
  74. if (comma) {
  75. sb.Append(", ");
  76. } else {
  77. comma = true;
  78. }
  79. sb.Append(p.ParameterType.Name);
  80. }
  81. sb.Append(')');
  82. return sb.ToString();
  83. }
  84. /// <summary>
  85. /// Given a MethodBase, return an index into the array of perf data. Treat each
  86. /// CLR method as unique.
  87. /// </summary>
  88. private int GetProfilerIndex(MethodBase/*!*/ method) {
  89. lock (_methods) {
  90. int index;
  91. if (!_methods.TryGetValue(method, out index)) {
  92. index = GetNewProfilerIndex(FormatMethodName(method));
  93. _methods[method] = index;
  94. }
  95. return index;
  96. }
  97. }
  98. /// <summary>
  99. /// Given the unique name of something we're profiling, return an index into the array of perf data.
  100. /// </summary>
  101. private int GetProfilerIndex(string/*!*/ name) {
  102. lock (_methods) {
  103. int index;
  104. if (!_names.TryGetValue(name, out index)) {
  105. index = GetNewProfilerIndex(name);
  106. _names[name] = index;
  107. }
  108. return index;
  109. }
  110. }
  111. /// <summary>
  112. /// Add a new profiler entry. Not all names are unique.
  113. /// </summary>
  114. private int GetNewProfilerIndex(string/*!*/ name) {
  115. int index;
  116. lock (_counters) {
  117. index = _counters.Count;
  118. _counters.Add(name);
  119. if (index >= (_profileData.Length / 3)) {
  120. long[,] newProfileData = new long[index * 2, 3];
  121. _profiles.Add(Interlocked.Exchange(ref _profileData, newProfileData));
  122. }
  123. }
  124. return index;
  125. }
  126. /// <summary>
  127. /// Gets the current summary of profile data
  128. /// </summary>
  129. public List<Data>/*!*/ GetProfile(bool includeUnused) {
  130. var result = new List<Data>(_counters.Count);
  131. lock (_counters) {
  132. // capture the current profile
  133. int length = (_profileData.Length / 3);
  134. long[,] newProfileData = new long[length, 3];
  135. long[,] totals = Interlocked.Exchange(ref _profileData, newProfileData);
  136. // TODO: There's a slim possibility of data being lost here if the runtime helper on
  137. // another thread acquires the memory reference and then loses the quantum before
  138. // performing the interlocked add, and then this thread replaces the _profileData
  139. // reference and uses the pre-add profile value.
  140. for (int i = 0; i < _profiles.Count; i++) {
  141. for (int j = 0; j < length; j++) {
  142. if (j < (_profiles[i].Length / 3)) {
  143. totals[j, TimeInBody] += _profiles[i][j, TimeInBody];
  144. totals[j, TimeInChildMethods] += _profiles[i][j, TimeInChildMethods];
  145. totals[j, NumberOfCalls] += _profiles[i][j, NumberOfCalls];
  146. }
  147. }
  148. }
  149. _profiles.Clear();
  150. _profiles.Add(totals);
  151. for (int i = 0; i < _counters.Count; i++) {
  152. if (includeUnused || totals[i, NumberOfCalls] > 0) {
  153. result.Add(new Data(
  154. _counters[i],
  155. DateTimeTicksFromTimeData(totals[i, TimeInBody] + totals[i, TimeInChildMethods]),
  156. DateTimeTicksFromTimeData(totals[i, TimeInBody]),
  157. (int)totals[i, NumberOfCalls]
  158. ));
  159. }
  160. }
  161. }
  162. return result;
  163. }
  164. /// <summary>
  165. /// Resets the current summary of profile data back to zero
  166. /// </summary>
  167. public void Reset() {
  168. lock (_counters) {
  169. // capture the current profile
  170. int length = (_profileData.Length / 3);
  171. long[,] newProfileData = new long[length, 3];
  172. Interlocked.Exchange(ref _profileData, newProfileData);
  173. _profiles.Clear();
  174. }
  175. }
  176. private static long DateTimeTicksFromTimeData(long elapsedStopwatchTicks) {
  177. #if !SILVERLIGHT
  178. if (Stopwatch.IsHighResolution) {
  179. return (long)(((double)elapsedStopwatchTicks) * 10000000.0 / (double)Stopwatch.Frequency);
  180. }
  181. #endif
  182. return elapsedStopwatchTicks;
  183. }
  184. #region Runtime helpers
  185. public long StartCall(int index) {
  186. Interlocked.Increment(ref _profileData[index, NumberOfCalls]);
  187. #if !SILVERLIGHT
  188. return Stopwatch.GetTimestamp();
  189. #else
  190. return DateTime.Now.Ticks;
  191. #endif
  192. }
  193. public long StartNestedCall(int index, long timestamp) {
  194. #if !SILVERLIGHT
  195. long now = Stopwatch.GetTimestamp();
  196. #else
  197. long now = DateTime.Now.Ticks;
  198. #endif
  199. Interlocked.Add(ref _profileData[index, TimeInBody], now - timestamp);
  200. return now;
  201. }
  202. public long FinishNestedCall(int index, long timestamp) {
  203. #if !SILVERLIGHT
  204. long now = Stopwatch.GetTimestamp();
  205. #else
  206. long now = DateTime.Now.Ticks;
  207. #endif
  208. Interlocked.Add(ref _profileData[index, TimeInChildMethods], now - timestamp);
  209. return now;
  210. }
  211. public void FinishCall(int index, long timestamp) {
  212. #if !SILVERLIGHT
  213. long now = Stopwatch.GetTimestamp();
  214. #else
  215. long now = DateTime.Now.Ticks;
  216. #endif
  217. Interlocked.Add(ref _profileData[index, TimeInBody], now - timestamp);
  218. }
  219. #endregion
  220. #region Compilation support
  221. internal MSAst.Expression AddOuterProfiling(MSAst.Expression/*!*/ body, MSAst.ParameterExpression/*!*/ tick, int profileIndex) {
  222. return Ast.Block(
  223. Ast.Assign(
  224. tick,
  225. Ast.Call(
  226. Ast.Constant(this, typeof(Profiler)),
  227. typeof(Profiler).GetMethod("StartCall"),
  228. AstUtils.Constant(profileIndex)
  229. )
  230. ),
  231. AstUtils.Try(
  232. body
  233. ).Finally(
  234. Ast.Call(
  235. Ast.Constant(this, typeof(Profiler)),
  236. typeof(Profiler).GetMethod("FinishCall"),
  237. AstUtils.Constant(profileIndex),
  238. tick
  239. )
  240. )
  241. );
  242. }
  243. internal MSAst.Expression AddInnerProfiling(MSAst.Expression/*!*/ body, MSAst.ParameterExpression/*!*/ tick, int profileIndex) {
  244. return Ast.Block(
  245. Ast.Assign(
  246. tick,
  247. Ast.Call(
  248. Ast.Constant(this, typeof(Profiler)),
  249. typeof(Profiler).GetMethod("StartNestedCall"),
  250. AstUtils.Constant(profileIndex),
  251. tick
  252. )
  253. ),
  254. AstUtils.Try(
  255. body
  256. ).Finally(
  257. Ast.Assign(
  258. tick,
  259. Ast.Call(
  260. Ast.Constant(this, typeof(Profiler)),
  261. typeof(Profiler).GetMethod("FinishNestedCall"),
  262. AstUtils.Constant(profileIndex),
  263. tick
  264. )
  265. )
  266. )
  267. );
  268. }
  269. private sealed class InnerMethodProfiler : MSAst.ExpressionVisitor {
  270. private readonly Profiler/*!*/ _profiler;
  271. private readonly MSAst.ParameterExpression/*!*/ _tick;
  272. private readonly int _profileIndex;
  273. public InnerMethodProfiler(Profiler/*!*/ profiler, MSAst.ParameterExpression/*!*/ tick, int profileIndex) {
  274. _profiler = profiler;
  275. _tick = tick;
  276. _profileIndex = profileIndex;
  277. }
  278. protected override MSAst.Expression/*!*/ VisitDynamic(MSAst.DynamicExpression/*!*/ node) {
  279. return _profiler.AddInnerProfiling(node, _tick, _profileIndex);
  280. }
  281. protected override MSAst.Expression/*!*/ VisitExtension(MSAst.Expression node) {
  282. if (node is ReducableDynamicExpression) {
  283. return _profiler.AddInnerProfiling(node, _tick, _profileIndex);
  284. }
  285. return base.VisitExtension(node);
  286. }
  287. protected override MSAst.Expression VisitMethodCall(MSAst.MethodCallExpression node) {
  288. var result = base.VisitMethodCall(node);
  289. if (IgnoreMethod(node.Method)) {
  290. // To ignore the called method, we need to prevent its time from being added to the current method's total
  291. return _profiler.AddInnerProfiling(node, _tick, _profileIndex);
  292. }
  293. return result;
  294. }
  295. protected override MSAst.Expression VisitLambda<T>(MSAst.Expression<T> node) {
  296. // Don't trace into embedded function
  297. return node;
  298. }
  299. }
  300. private static bool IgnoreMethod(MethodBase method) {
  301. var attrs = method.GetCustomAttributes(typeof(ProfilerTreatsAsExternalAttribute), false);
  302. return attrs.Length > 0;
  303. }
  304. /// <summary>
  305. /// Adds profiling calls to a Python method.
  306. /// Calculates both the time spent only in this method
  307. /// </summary>
  308. internal MSAst.Expression AddProfiling(MSAst.Expression/*!*/ body, MSAst.ParameterExpression/*!*/ tick, string/*!*/ name, bool unique) {
  309. int profileIndex = GetProfilerIndex(name);
  310. return AddOuterProfiling(new InnerMethodProfiler(this, tick, profileIndex).Visit(body), tick, profileIndex);
  311. }
  312. /// <summary>
  313. /// Wraps a call to a MethodInfo with profiling capture for that MethodInfo
  314. /// </summary>
  315. internal MSAst.Expression AddProfiling(MSAst.Expression/*!*/ body, MethodBase/*!*/ method) {
  316. if ((method is DynamicMethod) || IgnoreMethod(method)) {
  317. return body;
  318. }
  319. int profileIndex = GetProfilerIndex(method);
  320. MSAst.ParameterExpression tick = Ast.Variable(typeof(long), "$tick");
  321. return Ast.Block(
  322. new MSAst.ParameterExpression[] { tick },
  323. AddOuterProfiling(body, tick, profileIndex)
  324. );
  325. }
  326. /// <summary>
  327. /// Wraps a call to a MethodInfo with profiling capture for that MethodInfo
  328. /// </summary>
  329. internal OptimizingCallDelegate AddProfiling(OptimizingCallDelegate/*!*/ callDelgate, MethodBase/*!*/ method) {
  330. if ((method is DynamicMethod) || IgnoreMethod(method)) {
  331. return callDelgate;
  332. }
  333. int profileIndex = GetProfilerIndex(method);
  334. return delegate(object[] callArgs, out bool shouldOptimize) {
  335. long callStart = StartCall(profileIndex);
  336. try {
  337. return callDelgate(callArgs, out shouldOptimize);
  338. } finally {
  339. FinishCall(profileIndex, callStart);
  340. }
  341. };
  342. }
  343. #endregion
  344. /// <summary>
  345. /// Encapsulates profiler data to return to clients
  346. /// </summary>
  347. public struct Data {
  348. public string Name;
  349. public long InclusiveTime;
  350. public long ExclusiveTime;
  351. public int Calls;
  352. public Data(string _name, long _inclusive, long _exclusive, int _calls) {
  353. Name = _name;
  354. InclusiveTime = _inclusive;
  355. ExclusiveTime = _exclusive;
  356. Calls = _calls;
  357. }
  358. }
  359. }
  360. /// <summary>
  361. /// Marks that this built-in method should be treated as external by the profiler.
  362. /// When placed on a call emitted into a Python method, all the time spent in this
  363. /// call will still show up in its parent's inclusive time, but will not be
  364. /// part of its exclusive time.
  365. /// </summary>
  366. [AttributeUsage(AttributeTargets.Method)]
  367. public sealed class ProfilerTreatsAsExternalAttribute : Attribute {
  368. }
  369. }