PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/Kudu.Core/Tracing/Tracer.cs

https://github.com/moacap/kudu
C# | 224 lines | 167 code | 39 blank | 18 comment | 16 complexity | 22f14248ae3e2330f2903f1b982eac09 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.IO.Abstractions;
  7. using System.Linq;
  8. using System.Xml.Linq;
  9. using Kudu.Contracts.Tracing;
  10. using Kudu.Core.Infrastructure;
  11. namespace Kudu.Core.Tracing
  12. {
  13. public class Tracer : ITracer
  14. {
  15. // TODO: Make this configurable
  16. private const int MaxLogEntries = 1000;
  17. private readonly Stack<TraceStep> _currentSteps = new Stack<TraceStep>();
  18. private readonly List<TraceStep> _steps = new List<TraceStep>();
  19. private readonly Stack<XElement> _elements = new Stack<XElement>();
  20. private readonly string _path;
  21. private readonly IFileSystem _fileSystem;
  22. private const string TraceRoot = "trace";
  23. private static readonly ConcurrentDictionary<string, object> _pathLocks = new ConcurrentDictionary<string, object>();
  24. public Tracer(string path)
  25. : this(new FileSystem(), path)
  26. {
  27. }
  28. public Tracer(IFileSystem fileSystem, string path)
  29. {
  30. _fileSystem = fileSystem;
  31. _path = path;
  32. if (!_pathLocks.ContainsKey(path))
  33. {
  34. _pathLocks.TryAdd(path, new object());
  35. }
  36. }
  37. public IEnumerable<TraceStep> Steps
  38. {
  39. get
  40. {
  41. return _steps.AsReadOnly();
  42. }
  43. }
  44. public IDisposable Step(string title, IDictionary<string, string> attributes)
  45. {
  46. var newStep = new TraceStep(title);
  47. var newStepElement = new XElement("step", new XAttribute("title", title),
  48. new XAttribute("date", DateTime.Now.ToString("MM/dd H:mm:ss")));
  49. foreach (var pair in attributes)
  50. {
  51. string safeValue = XmlUtility.Sanitize(pair.Value);
  52. newStepElement.Add(new XAttribute(pair.Key, safeValue));
  53. }
  54. if (_currentSteps.Count == 0)
  55. {
  56. // Add a new top level step
  57. _steps.Add(newStep);
  58. }
  59. _currentSteps.Push(newStep);
  60. _elements.Push(newStepElement);
  61. // Start profiling
  62. newStep.Start();
  63. return new DisposableAction(() =>
  64. {
  65. try
  66. {
  67. // If there's no steps then do nothing (guard against double dispose)
  68. if (_currentSteps.Count == 0)
  69. {
  70. return;
  71. }
  72. // Stop the current step
  73. _currentSteps.Peek().Stop();
  74. TraceStep current = _currentSteps.Pop();
  75. XElement stepElement = _elements.Pop();
  76. stepElement.Add(new XAttribute("elapsed", current.ElapsedMilliseconds));
  77. if (_elements.Count > 0)
  78. {
  79. XElement parent = _elements.Peek();
  80. parent.Add(stepElement);
  81. }
  82. else
  83. {
  84. // Add this element to the list
  85. Save(stepElement);
  86. }
  87. if (_currentSteps.Count > 0)
  88. {
  89. TraceStep parent = _currentSteps.Peek();
  90. parent.Children.Add(current);
  91. }
  92. }
  93. catch (Exception ex)
  94. {
  95. Debug.WriteLine(ex.Message);
  96. }
  97. });
  98. }
  99. private void Save(XElement stepElement)
  100. {
  101. lock (_pathLocks[_path])
  102. {
  103. XDocument document = GetDocument();
  104. // Make sure the size of the log doesn't go over the limit
  105. EnsureSize(document);
  106. document.Root.Add(stepElement);
  107. document.Save(_path);
  108. }
  109. }
  110. public void Trace(string value, IDictionary<string, string> attributes)
  111. {
  112. // Add a fake step
  113. using (Step(value, attributes)) { }
  114. }
  115. private void EnsureSize(XDocument document)
  116. {
  117. try
  118. {
  119. List<XElement> entries = document.Root.Elements().ToList();
  120. // Amount of entries we have to trim. It should be 1 all of the time
  121. // but just in case something went wrong, we're going to try to fix it this time around
  122. int trim = entries.Count - MaxLogEntries + 1;
  123. if (trim <= 0)
  124. {
  125. return;
  126. }
  127. // Search for all skippable requests first
  128. var filteredEntries = entries.Take(MaxLogEntries / 2)
  129. .Where(Skippable)
  130. .ToList();
  131. // If we didn't find skippable entries just remove the oldest
  132. if (filteredEntries.Count == 0)
  133. {
  134. // If there's none just use the full list
  135. filteredEntries = entries;
  136. }
  137. foreach (var e in filteredEntries.Take(trim))
  138. {
  139. e.Remove();
  140. }
  141. }
  142. catch (Exception ex)
  143. {
  144. // Something went wrong so just continue
  145. Debug.WriteLine(ex.Message);
  146. }
  147. }
  148. private static bool Skippable(XElement e)
  149. {
  150. // Git requests have type git="true"
  151. bool isGit = e.Attribute("git") != null;
  152. // The only top level exe is kudu
  153. bool isKudu = e.Attribute("type") != null && e.Attribute("path") != null;
  154. return !isGit && !isKudu;
  155. }
  156. private XDocument GetDocument()
  157. {
  158. if (!_fileSystem.File.Exists(_path))
  159. {
  160. _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(_path));
  161. return CreateDocumentRoot();
  162. }
  163. try
  164. {
  165. XDocument document;
  166. using (var stream = _fileSystem.File.OpenRead(_path))
  167. {
  168. document = XDocument.Load(stream);
  169. }
  170. return document;
  171. }
  172. catch
  173. {
  174. // If the profile gets corrupted then delete it
  175. FileSystemHelpers.DeleteFileSafe(_path);
  176. // Return a new document
  177. return CreateDocumentRoot();
  178. }
  179. }
  180. private static XDocument CreateDocumentRoot()
  181. {
  182. return new XDocument(new XElement(TraceRoot));
  183. }
  184. }
  185. }