2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Globalization;
68 private readonly SortedDictionary<string, int> exceptionOrdinals =
new SortedDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
69 private readonly SortedDictionary<string, int> eventOrdinals =
new SortedDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
70 private readonly List<ProfilerThread> threads =
new List<ProfilerThread>();
71 private readonly SortedDictionary<int, object> notes =
new SortedDictionary<int, object>();
72 private readonly Dictionary<string, ProfilerThread> threadsByName =
new Dictionary<string, ProfilerThread>();
74 private readonly Stopwatch watch;
75 private DateTime started = DateTime.MinValue;
76 private double timeScale = 1;
77 private int threadOrder = 0;
113 this.watch =
new Stopwatch();
132 this.threads.Add(Result);
133 this.threadsByName[Name] = Result;
148 if (this.threadsByName.TryGetValue(Name, out
ProfilerThread Result))
151 Result =
new ProfilerThread(Name, ++this.threadOrder, Type,
this);
152 this.threads.Add(Result);
153 this.threadsByName[Name] = Result;
170 this.threads.Add(Result);
171 this.threadsByName[Name] = Result;
187 if (this.threadsByName.TryGetValue(Name, out
ProfilerThread Result))
190 Result =
new ProfilerThread(Name, ++this.threadOrder, Type, Parent);
191 this.threads.Add(Result);
192 this.threadsByName[Name] = Result;
202 this.started = DateTime.Now;
204 this.mainThread.Start();
212 this.mainThread.Stop();
239 double Seconds = (Timepoint - this.started).TotalSeconds;
240 return (
long)(Seconds * Stopwatch.Frequency + 0.5);
249 this.mainThread.NewState(State);
258 this.mainThread.NewSample(Sample);
266 this.mainThread.Idle();
274 this.mainThread.High();
282 this.mainThread.Low();
291 public void Interval(DateTime From, DateTime To,
string Label)
302 public void Interval(
long From,
long To,
string Label)
304 this.mainThread.Interval(From, To, Label);
313 this.mainThread.Event(Name);
321 public void Event(
string Name,
string Label)
323 this.mainThread.Event(Name, Label);
342 this.mainThread.Exception(
Exception, Label);
352 return ((
double)Ticks) / Stopwatch.Frequency;
370 return new KeyValuePair<double, string>(Amount * 1e6,
"μs");
373 return new KeyValuePair<double, string>(Amount * 1e3,
"ms");
376 return new KeyValuePair<double, string>(Amount,
"s");
379 return new KeyValuePair<double, string>(Amount / 60,
"min");
382 return new KeyValuePair<double, string>(Amount / (60 * 60),
"h");
385 return new KeyValuePair<double, string>(Amount / (24 * 60 * 60),
"d");
401 Reference *= this.timeScale;
410 return new KeyValuePair<double, string>(Amount,
"μs");
413 return new KeyValuePair<double, string>(Amount,
"ms");
415 else if (Reference > 100)
426 return new KeyValuePair<double, string>(Amount,
"d");
429 return new KeyValuePair<double, string>(Amount,
"h");
432 return new KeyValuePair<double, string>(Amount,
"min");
435 return new KeyValuePair<double, string>(Amount,
"s");
448 KeyValuePair<double, string> Time = this.
ToTime(Ticks, Thread,
TimeUnit);
449 return Time.Key.ToString(
"F" + NrDecimals.ToString()) +
" " + Time.Value;
459 StringBuilder sb =
new StringBuilder();
460 XmlWriterSettings Settings =
new XmlWriterSettings()
464 NewLineChars =
"\r\n",
465 NewLineHandling = NewLineHandling.Replace,
466 NewLineOnAttributes =
false,
467 OmitXmlDeclaration =
true
470 using (XmlWriter Output = XmlWriter.Create(sb, Settings))
475 return sb.ToString();
485 Output.WriteStartElement(
"Profiler",
"http://waher.se/schema/Profiler.xsd");
486 Output.WriteAttributeString(
"ticksPerSecond", Stopwatch.Frequency.ToString());
487 Output.WriteAttributeString(
"timePerTick", this.
ToTimeStr(1, this.mainThread,
TimeUnit.DynamicPerEvent, 7));
493 Threads = this.threads.ToArray();
498 if (Thread.
Parent is
null)
502 Output.WriteEndElement();
512 StringBuilder sb =
new StringBuilder();
514 return sb.ToString();
539 throw new InvalidOperationException(
"Diagram requires the same time base to be used through-out.");
542 Output.AppendLine(
"@startuml");
548 Threads = this.threads.ToArray();
553 if (Thread.
Parent is
null)
557 lock (this.eventOrdinals)
559 foreach (KeyValuePair<string, int> P
in this.eventOrdinals)
561 Output.Append(
"concise \"");
562 Output.Append(P.Key);
563 Output.Append(
"\" as E");
564 Output.AppendLine(P.Value.ToString());
568 lock (this.exceptionOrdinals)
570 foreach (KeyValuePair<string, int> P
in this.exceptionOrdinals)
572 Output.Append(
"concise \"");
573 Output.Append(P.Key);
574 Output.Append(
"\" as X");
575 Output.AppendLine(P.Value.ToString());
585 KeyValuePair<double, string> TotalTime = this.
ToTime(this.mainThread.StoppedAt ??
this.ElapsedTicks,
this.mainThread,
TimeUnit);
587 TimeSpan = TotalTime.Key;
588 StepSize = Math.Pow(10, Math.Round(Math.Log10(TimeSpan / 10)));
589 NrSteps = (int)Math.Floor(TimeSpan / StepSize);
593 else if (NrSteps >= 25)
595 else if (NrSteps >= 20)
597 else if (NrSteps <= 2)
599 else if (NrSteps <= 4)
601 else if (NrSteps <= 5)
605 this.timeScale *= 1e-3;
607 while (StepSize < 1 && StepSize > 0);
609 StepSize = Math.Floor(StepSize);
610 NrSteps = (int)Math.Floor(TimeSpan / StepSize);
611 int PixelsPerStep = NrSteps > 0 ? GoalWidth / NrSteps : 0;
613 Output.Append(
"scale ");
614 Output.Append(StepSize.ToString(
"F0"));
615 Output.Append(
" as ");
616 Output.Append(PixelsPerStep);
617 Output.AppendLine(
" pixels");
623 if (Thread.
Parent is
null)
627 foreach (KeyValuePair<long, StringBuilder> P
in States.ByTime)
629 KeyValuePair<double, string> Time = this.
ToTime(P.Key,
null,
TimeUnit);
631 Output.AppendLine(Time.Key.ToString(
"F7").Replace(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator,
"."));
632 Output.Append(P.Value.ToString());
635 Output.Append(States.Summary.ToString());
636 Output.AppendLine(
"@enduml");
646 return GetOrdinal(this.eventOrdinals,
Event);
649 private static int GetOrdinal(SortedDictionary<string, int> Ordinals,
string Key)
653 if (Ordinals.TryGetValue(Key, out
int Ordinal))
656 Ordinal = Ordinals.Count;
657 Ordinals[Key] = Ordinal;
670 return GetOrdinal(this.exceptionOrdinals,
Exception.GetType().FullName);
684 Result = this.notes.Count + 1;
685 this.notes[Result] = Note;
700 return this.notes.Count;
715 return this.notes.TryGetValue(Index, out Note);
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.
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.