PageRenderTime 49ms CodeModel.GetById 21ms app.highlight 10ms RepoModel.GetById 14ms app.codeStats 0ms

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