3using System.Diagnostics;
4using System.Globalization;
69 private readonly SortedDictionary<string, int> exceptionOrdinals =
new SortedDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
70 private readonly SortedDictionary<string, int> eventOrdinals =
new SortedDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
72 private readonly SortedDictionary<int, object> notes =
new SortedDictionary<int, object>();
73 private readonly Dictionary<string, ProfilerThread> threadsByName =
new Dictionary<string, ProfilerThread>();
75 private readonly Stopwatch watch;
76 private DateTime started = DateTime.MinValue;
77 private double timeScale = 1;
78 private int threadOrder = 0;
114 this.watch =
new Stopwatch();
133 this.threads.Add(Result);
134 this.threadsByName[Name] = Result;
149 if (this.threadsByName.TryGetValue(Name, out
ProfilerThread Result))
152 Result =
new ProfilerThread(Name, ++this.threadOrder, Type,
this);
153 this.threads.Add(Result);
154 this.threadsByName[Name] = Result;
168 if (this.threadsByName.TryGetValue(Name, out
ProfilerThread Result))
187 this.threads.Add(Result);
188 this.threadsByName[Name] = Result;
204 if (this.threadsByName.TryGetValue(Name, out
ProfilerThread Result))
207 Result =
new ProfilerThread(Name, ++this.threadOrder, Type, Parent);
208 this.threads.Add(Result);
209 this.threadsByName[Name] = Result;
219 this.started = DateTime.Now;
221 this.mainThread.Start();
229 this.mainThread.Stop();
256 if (Timepoint.Kind == DateTimeKind.Utc)
257 Timepoint = Timepoint.ToLocalTime();
259 double Seconds = (Timepoint - this.started).TotalSeconds;
260 return (
long)(Seconds * Stopwatch.Frequency + 0.5);
269 this.mainThread.NewState(State);
278 this.mainThread.NewSample(Sample);
286 this.mainThread.Idle();
294 this.mainThread.High();
302 this.mainThread.Low();
311 public void Interval(DateTime From, DateTime To,
string Label)
322 public void Interval(
long From,
long To,
string Label)
324 this.mainThread.Interval(From, To, Label);
333 this.mainThread.Event(Name);
341 public void Event(
string Name,
string Label)
343 this.mainThread.Event(Name, Label);
362 this.mainThread.Exception(
Exception, Label);
372 return ((
double)Ticks) / Stopwatch.Frequency;
390 return new KeyValuePair<double, string>(Amount * 1e6,
"μs");
393 return new KeyValuePair<double, string>(Amount * 1e3,
"ms");
396 return new KeyValuePair<double, string>(Amount,
"s");
399 return new KeyValuePair<double, string>(Amount / 60,
"min");
402 return new KeyValuePair<double, string>(Amount / (60 * 60),
"h");
405 return new KeyValuePair<double, string>(Amount / (24 * 60 * 60),
"d");
421 Reference *= this.timeScale;
430 return new KeyValuePair<double, string>(Amount,
"μs");
433 return new KeyValuePair<double, string>(Amount,
"ms");
435 else if (Reference > 100)
446 return new KeyValuePair<double, string>(Amount,
"d");
449 return new KeyValuePair<double, string>(Amount,
"h");
452 return new KeyValuePair<double, string>(Amount,
"min");
455 return new KeyValuePair<double, string>(Amount,
"s");
468 KeyValuePair<double, string> Time = this.
ToTime(Ticks, Thread,
TimeUnit);
469 return Time.Key.ToString(
"F" + NrDecimals.ToString()) +
" " + Time.Value;
479 StringBuilder sb =
new StringBuilder();
480 XmlWriterSettings Settings =
new XmlWriterSettings()
484 NewLineChars =
"\r\n",
485 NewLineHandling = NewLineHandling.Replace,
486 NewLineOnAttributes =
false,
487 OmitXmlDeclaration =
true
490 using (XmlWriter Output = XmlWriter.Create(sb, Settings))
495 return sb.ToString();
505 Output.WriteStartElement(
"Profiler",
"http://waher.se/schema/Profiler.xsd");
506 Output.WriteAttributeString(
"ticksPerSecond", Stopwatch.Frequency.ToString());
507 Output.WriteAttributeString(
"timePerTick", this.
ToTimeStr(1, this.mainThread,
TimeUnit.DynamicPerEvent, 7));
513 Threads = this.threads.ToArray();
518 if (Thread.
Parent is
null)
522 Output.WriteEndElement();
532 StringBuilder sb =
new StringBuilder();
534 return sb.ToString();
559 throw new InvalidOperationException(
"Diagram requires the same time base to be used through-out.");
562 Output.AppendLine(
"@startuml");
568 Threads = this.threads.ToArray();
573 if (Thread.
Parent is
null)
577 lock (this.eventOrdinals)
579 foreach (KeyValuePair<string, int> P
in this.eventOrdinals)
581 Output.Append(
"concise \"");
582 Output.Append(EscapeLabel(P.Key));
583 Output.Append(
"\" as E");
584 Output.AppendLine(P.Value.ToString());
588 lock (this.exceptionOrdinals)
590 foreach (KeyValuePair<string, int> P
in this.exceptionOrdinals)
592 Output.Append(
"concise \"");
593 Output.Append(EscapeLabel(P.Key));
594 Output.Append(
"\" as X");
595 Output.AppendLine(P.Value.ToString());
605 KeyValuePair<double, string> TotalTime = this.
ToTime(this.mainThread.StoppedAt ??
this.ElapsedTicks,
this.mainThread,
TimeUnit);
607 TimeSpan = TotalTime.Key;
608 StepSize = Math.Pow(10, Math.Round(Math.Log10(TimeSpan / 10)));
609 NrSteps = (int)Math.Floor(TimeSpan / StepSize);
613 else if (NrSteps >= 25)
615 else if (NrSteps >= 20)
617 else if (NrSteps <= 2)
619 else if (NrSteps <= 4)
621 else if (NrSteps <= 5)
633 this.timeScale *= 1e-3;
636 while (StepSize < 1 && StepSize > 0);
638 StepSize = Math.Ceiling(StepSize);
639 NrSteps = Math.Max(1, (
int)Math.Floor(TimeSpan / StepSize));
640 int PixelsPerStep = Math.Max(1, GoalWidth / NrSteps);
642 Output.Append(
"scale ");
643 Output.Append(Math.Ceiling(StepSize).ToString(
"F0"));
644 Output.Append(
" as ");
645 Output.Append(PixelsPerStep);
646 Output.AppendLine(
" pixels");
652 if (Thread.
Parent is
null)
656 foreach (KeyValuePair<long, StringBuilder> P
in States.ByTime)
658 KeyValuePair<double, string> Time = this.
ToTime(P.Key,
null,
TimeUnit);
660 Output.AppendLine(Time.Key.ToString(
"F7", CultureInfo.InvariantCulture));
661 Output.Append(P.Value.ToString());
664 Output.Append(States.Summary.ToString());
665 Output.AppendLine(
"@enduml");
668 internal static string EscapeLabel(
string s)
671 Replace(
"\\",
"\\\\").
673 Replace(
"\r",
"\\r").
674 Replace(
"\n",
"\\n");
684 return GetOrdinal(this.eventOrdinals,
Event);
687 private static int GetOrdinal(SortedDictionary<string, int> Ordinals,
string Key)
691 if (Ordinals.TryGetValue(Key, out
int Ordinal))
694 Ordinal = Ordinals.Count;
695 Ordinals[Key] = Ordinal;
708 return GetOrdinal(this.exceptionOrdinals,
Exception.GetType().FullName);
722 Result = this.notes.Count + 1;
723 this.notes[Result] = Note;
738 return this.notes.Count;
753 return this.notes.TryGetValue(Index, out Note);
A chunked list is a linked list of chunks of objects of type T .
Contains internal states used during generation of PlantUML diagram.s
Class that keeps track of events and timing.
DateTime Started
When profiling was started.
double ToSeconds(long Ticks)
Number of seconds, corresponding to a measured number of high-frequency clock ticks.
void Stop()
Stops measuring time.
long GetTicks(DateTime Timepoint)
Converts a System.DateTime to number of ticks, since start of profiling.
void Interval(DateTime From, DateTime To, string Label)
Records an interval in the main thread.
void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit, int GoalWidth)
Exports events to PlantUML.
ProfilerThread CreateThread(string Name, ProfilerThreadType Type)
Creates a new profiler thread.
void Idle()
Main Thread goes idle.
void NewState(string State)
Main Thread changes state.
void Event(string Name)
Event occurred on main thread
Profiler(ProfilerThreadType Type)
Class that keeps track of events and timing.
void High()
Sets the (binary) state of the Main Thread to "high".
KeyValuePair< double, string > ToTime(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit)
Time (amount, unit), corresponding to a measured number of high-frequency clock ticks.
bool TryGetNote(int Index, out object Note)
Tries to get a note from the profile.
string ExportXml(TimeUnit TimeUnit)
Exports events to XML.
void Low()
Sets the (binary) state Main Thread to "low".
Profiler(string Name)
Class that keeps track of events and timing.
void NewSample(double Sample)
A new sample value has been recored
void Interval(long From, long To, string Label)
Records an interval in the main thread.
void ExportXml(XmlWriter Output, TimeUnit TimeUnit)
Exports events to XML.
ProfilerThread GetThread(string Name, ProfilerThreadType Type)
Gets a profiler thread. If none is available, a new is created.
long ElapsedTicks
Elapsed ticks since start.
Profiler()
Class that keeps track of events and timing.
string ExportPlantUml(TimeUnit TimeUnit)
Exports events to PlantUML.
int NoteCount
Number of notes added.
Profiler(string Name, ProfilerThreadType Type)
Class that keeps track of events and timing.
void Exception(System.Exception Exception, string Label)
Event occurred on main thread
int AddNote(object Note)
Adds a note to the profile.
ProfilerThread TryGetThread(string Name)
Tries to get an existing profiler thread. If none is available, null is returned.
double ElapsedSeconds
Elapsed seconds since start.
void Event(string Name, string Label)
Event occurred on main thread
int GetEventOrdinal(string Event)
Gets the ordinal for an event.
ProfilerThread MainThread
Main thread.
int GetExceptionOrdinal(System.Exception Exception)
Gets the ordinal for a type of exception.
void Exception(System.Exception Exception)
Event occurred on main thread
string ToTimeStr(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit, int NrDecimals)
String representation of time, corresponding to a measured number of high-frequency clock ticks.
void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit)
Exports events to PlantUML.
void Start()
Starts measuring time.
Class that keeps track of events and timing for one thread.
long? StoppedAt
Ticks when thread was stopped.
void ExportXml(XmlWriter Output, TimeUnit TimeUnit)
Exports events to XML.
void ExportPlantUmlEvents(PlantUmlStates States)
Exports events to PlantUML.
ProfilerThread Parent
Parent profiler thread, if any.
void ExportPlantUmlDescription(StringBuilder Output, TimeUnit TimeUnit)
Exports events to PlantUML.
TimeUnit
Options for presenting time in reports.
ProfilerThreadType
Type of profiler thread.