Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Profiler.cs
1using System;
3using System.Diagnostics;
4using System.Globalization;
5using System.Text;
6using System.Xml;
10
12{
16 public enum TimeUnit
17 {
21 DynamicPerEvent,
22
26 DynamicPerThread,
27
31 DynamicPerProfiling,
32
36 MicroSeconds,
37
41 MilliSeconds,
42
46 Seconds,
47
51 Minutes,
52
56 Hours,
57
61 Days
62 }
63
67 public class Profiler
68 {
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);
71 private readonly ChunkedList<ProfilerThread> threads = new ChunkedList<ProfilerThread>();
72 private readonly SortedDictionary<int, object> notes = new SortedDictionary<int, object>();
73 private readonly Dictionary<string, ProfilerThread> threadsByName = new Dictionary<string, ProfilerThread>();
74 private readonly ProfilerThread mainThread;
75 private readonly Stopwatch watch;
76 private DateTime started = DateTime.MinValue;
77 private double timeScale = 1;
78 private int threadOrder = 0;
79
83 public Profiler()
84 : this("Main", ProfilerThreadType.Sequential)
85 {
86 }
87
92 public Profiler(string Name)
93 : this(Name, ProfilerThreadType.Sequential)
94 {
95 }
96
102 : this("Main", Type)
103 {
104 }
105
111 public Profiler(string Name, ProfilerThreadType Type)
112 {
113 this.mainThread = this.CreateThread(Name, Type);
114 this.watch = new Stopwatch();
115 }
116
120 public ProfilerThread MainThread => this.mainThread;
121
129 {
130 lock (this.threads)
131 {
132 ProfilerThread Result = new ProfilerThread(Name, ++this.threadOrder, Type, this);
133 this.threads.Add(Result);
134 this.threadsByName[Name] = Result;
135 return Result;
136 }
137 }
138
146 {
147 lock (this.threads)
148 {
149 if (this.threadsByName.TryGetValue(Name, out ProfilerThread Result))
150 return Result;
151
152 Result = new ProfilerThread(Name, ++this.threadOrder, Type, this);
153 this.threads.Add(Result);
154 this.threadsByName[Name] = Result;
155 return Result;
156 }
157 }
158
164 public ProfilerThread TryGetThread(string Name)
165 {
166 lock (this.threads)
167 {
168 if (this.threadsByName.TryGetValue(Name, out ProfilerThread Result))
169 return Result;
170
171 return null;
172 }
173 }
174
182 internal ProfilerThread CreateThread(string Name, ProfilerThreadType Type, ProfilerThread Parent)
183 {
184 lock (this.threads)
185 {
186 ProfilerThread Result = new ProfilerThread(Name, ++this.threadOrder, Type, Parent);
187 this.threads.Add(Result);
188 this.threadsByName[Name] = Result;
189 return Result;
190 }
191 }
192
200 internal ProfilerThread GetThread(string Name, ProfilerThreadType Type, ProfilerThread Parent)
201 {
202 lock (this.threads)
203 {
204 if (this.threadsByName.TryGetValue(Name, out ProfilerThread Result))
205 return Result;
206
207 Result = new ProfilerThread(Name, ++this.threadOrder, Type, Parent);
208 this.threads.Add(Result);
209 this.threadsByName[Name] = Result;
210 return Result;
211 }
212 }
213
217 public void Start()
218 {
219 this.started = DateTime.Now;
220 this.watch.Start();
221 this.mainThread.Start();
222 }
223
227 public void Stop()
228 {
229 this.mainThread.Stop();
230 this.watch.Stop();
231 }
232
236 public long ElapsedTicks => this.watch.ElapsedTicks;
237
241 public double ElapsedSeconds => this.ToSeconds(this.ElapsedTicks);
242
246 public DateTime Started => this.started;
247
254 public long GetTicks(DateTime Timepoint)
255 {
256 if (Timepoint.Kind == DateTimeKind.Utc)
257 Timepoint = Timepoint.ToLocalTime();
258
259 double Seconds = (Timepoint - this.started).TotalSeconds;
260 return (long)(Seconds * Stopwatch.Frequency + 0.5);
261 }
262
267 public void NewState(string State)
268 {
269 this.mainThread.NewState(State);
270 }
271
276 public void NewSample(double Sample)
277 {
278 this.mainThread.NewSample(Sample);
279 }
280
284 public void Idle()
285 {
286 this.mainThread.Idle();
287 }
288
292 public void High()
293 {
294 this.mainThread.High();
295 }
296
300 public void Low()
301 {
302 this.mainThread.Low();
303 }
304
311 public void Interval(DateTime From, DateTime To, string Label)
312 {
313 this.mainThread.Interval(this.GetTicks(From), this.GetTicks(To), Label);
314 }
315
322 public void Interval(long From, long To, string Label)
323 {
324 this.mainThread.Interval(From, To, Label);
325 }
326
331 public void Event(string Name)
332 {
333 this.mainThread.Event(Name);
334 }
335
341 public void Event(string Name, string Label)
342 {
343 this.mainThread.Event(Name, Label);
344 }
345
350 public void Exception(System.Exception Exception)
351 {
352 this.mainThread.Exception(Exception);
353 }
354
360 public void Exception(System.Exception Exception, string Label)
361 {
362 this.mainThread.Exception(Exception, Label);
363 }
364
370 public double ToSeconds(long Ticks)
371 {
372 return ((double)Ticks) / Stopwatch.Frequency;
373 }
374
382 public KeyValuePair<double, string> ToTime(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit)
383 {
384 double Amount = this.ToSeconds(Ticks);
385 double Reference;
386
387 switch (TimeUnit)
388 {
389 case TimeUnit.MicroSeconds:
390 return new KeyValuePair<double, string>(Amount * 1e6, "μs");
391
392 case TimeUnit.MilliSeconds:
393 return new KeyValuePair<double, string>(Amount * 1e3, "ms");
394
395 case TimeUnit.Seconds:
396 return new KeyValuePair<double, string>(Amount, "s");
397
398 case TimeUnit.Minutes:
399 return new KeyValuePair<double, string>(Amount / 60, "min");
400
401 case TimeUnit.Hours:
402 return new KeyValuePair<double, string>(Amount / (60 * 60), "h");
403
404 case TimeUnit.Days:
405 return new KeyValuePair<double, string>(Amount / (24 * 60 * 60), "d");
406
407 case TimeUnit.DynamicPerProfiling:
408 Reference = this.ToSeconds(this.MainThread.StoppedAt ?? this.ElapsedTicks);
409 break;
410
411 case TimeUnit.DynamicPerThread:
412 Reference = this.ToSeconds(Thread.StoppedAt ?? this.ElapsedTicks);
413 break;
414
415 case TimeUnit.DynamicPerEvent:
416 default:
417 Reference = Amount;
418 break;
419 }
420
421 Reference *= this.timeScale;
422
423 if (Reference < 1)
424 {
425 Amount *= 1e3;
426 Reference *= 1e3;
427 if (Reference < 1)
428 {
429 Amount *= 1e3;
430 return new KeyValuePair<double, string>(Amount, "μs");
431 }
432 else
433 return new KeyValuePair<double, string>(Amount, "ms");
434 }
435 else if (Reference > 100)
436 {
437 Amount /= 60;
438 Reference /= 60;
439 if (Reference > 100)
440 {
441 Amount /= 60;
442 Reference /= 60;
443 if (Reference > 100)
444 {
445 Amount /= 24;
446 return new KeyValuePair<double, string>(Amount, "d");
447 }
448 else
449 return new KeyValuePair<double, string>(Amount, "h");
450 }
451 else
452 return new KeyValuePair<double, string>(Amount, "min");
453 }
454 else
455 return new KeyValuePair<double, string>(Amount, "s");
456 }
457
466 public string ToTimeStr(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit, int NrDecimals)
467 {
468 KeyValuePair<double, string> Time = this.ToTime(Ticks, Thread, TimeUnit);
469 return Time.Key.ToString("F" + NrDecimals.ToString()) + " " + Time.Value;
470 }
471
478 {
479 StringBuilder sb = new StringBuilder();
480 XmlWriterSettings Settings = new XmlWriterSettings()
481 {
482 Indent = true,
483 IndentChars = "\t",
484 NewLineChars = "\r\n",
485 NewLineHandling = NewLineHandling.Replace,
486 NewLineOnAttributes = false,
487 OmitXmlDeclaration = true
488 };
489
490 using (XmlWriter Output = XmlWriter.Create(sb, Settings))
491 {
492 this.ExportXml(Output, TimeUnit);
493 }
494
495 return sb.ToString();
496 }
497
503 public void ExportXml(XmlWriter Output, TimeUnit TimeUnit)
504 {
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));
508
509 ProfilerThread[] Threads;
510
511 lock (this.threads)
512 {
513 Threads = this.threads.ToArray();
514 }
515
516 foreach (ProfilerThread Thread in Threads)
517 {
518 if (Thread.Parent is null)
519 Thread.ExportXml(Output, TimeUnit);
520 }
521
522 Output.WriteEndElement();
523 }
524
531 {
532 StringBuilder sb = new StringBuilder();
533 this.ExportPlantUml(sb, TimeUnit);
534 return sb.ToString();
535 }
536
542 public void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit)
543 {
544 this.ExportPlantUml(Output, TimeUnit, 1000);
545 }
546
553 public void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit, int GoalWidth)
554 {
555 switch (TimeUnit)
556 {
557 case TimeUnit.DynamicPerEvent:
558 case TimeUnit.DynamicPerThread:
559 throw new InvalidOperationException("Diagram requires the same time base to be used through-out.");
560 }
561
562 Output.AppendLine("@startuml");
563
564 ProfilerThread[] Threads;
565
566 lock (this.threads)
567 {
568 Threads = this.threads.ToArray();
569 }
570
571 foreach (ProfilerThread Thread in Threads)
572 {
573 if (Thread.Parent is null)
574 Thread.ExportPlantUmlDescription(Output, TimeUnit);
575 }
576
577 lock (this.eventOrdinals)
578 {
579 foreach (KeyValuePair<string, int> P in this.eventOrdinals)
580 {
581 Output.Append("concise \"");
582 Output.Append(EscapeLabel(P.Key));
583 Output.Append("\" as E");
584 Output.AppendLine(P.Value.ToString());
585 }
586 }
587
588 lock (this.exceptionOrdinals)
589 {
590 foreach (KeyValuePair<string, int> P in this.exceptionOrdinals)
591 {
592 Output.Append("concise \"");
593 Output.Append(EscapeLabel(P.Key));
594 Output.Append("\" as X");
595 Output.AppendLine(P.Value.ToString());
596 }
597 }
598
599 double TimeSpan;
600 double StepSize;
601 int NrSteps;
602
603 do
604 {
605 KeyValuePair<double, string> TotalTime = this.ToTime(this.mainThread.StoppedAt ?? this.ElapsedTicks, this.mainThread, TimeUnit);
606
607 TimeSpan = TotalTime.Key;
608 StepSize = Math.Pow(10, Math.Round(Math.Log10(TimeSpan / 10)));
609 NrSteps = (int)Math.Floor(TimeSpan / StepSize);
610
611 if (NrSteps >= 50)
612 StepSize *= 5;
613 else if (NrSteps >= 25)
614 StepSize *= 2.5;
615 else if (NrSteps >= 20)
616 StepSize *= 2;
617 else if (NrSteps <= 2)
618 StepSize /= 5;
619 else if (NrSteps <= 4)
620 StepSize /= 2.5;
621 else if (NrSteps <= 5)
622 StepSize /= 2;
623
624 if (StepSize < 1)
625 {
626 if (TimeUnit != TimeUnit.DynamicPerProfiling &&
627 TimeUnit != TimeUnit.DynamicPerEvent &&
628 TimeUnit != TimeUnit.DynamicPerThread)
629 {
630 break;
631 }
632
633 this.timeScale *= 1e-3;
634 }
635 }
636 while (StepSize < 1 && StepSize > 0);
637
638 StepSize = Math.Ceiling(StepSize);
639 NrSteps = Math.Max(1, (int)Math.Floor(TimeSpan / StepSize));
640 int PixelsPerStep = Math.Max(1, GoalWidth / NrSteps);
641
642 Output.Append("scale ");
643 Output.Append(Math.Ceiling(StepSize).ToString("F0"));
644 Output.Append(" as ");
645 Output.Append(PixelsPerStep);
646 Output.AppendLine(" pixels");
647
649
650 foreach (ProfilerThread Thread in Threads)
651 {
652 if (Thread.Parent is null)
653 Thread.ExportPlantUmlEvents(States);
654 }
655
656 foreach (KeyValuePair<long, StringBuilder> P in States.ByTime)
657 {
658 KeyValuePair<double, string> Time = this.ToTime(P.Key, null, TimeUnit);
659 Output.Append('@');
660 Output.AppendLine(Time.Key.ToString("F7", CultureInfo.InvariantCulture));
661 Output.Append(P.Value.ToString());
662 }
663
664 Output.Append(States.Summary.ToString());
665 Output.AppendLine("@enduml");
666 }
667
668 internal static string EscapeLabel(string s)
669 {
670 return s.
671 Replace("\\", "\\\\").
672 Replace('"', '\'').
673 Replace("\r", "\\r").
674 Replace("\n", "\\n");
675 }
676
682 public int GetEventOrdinal(string Event)
683 {
684 return GetOrdinal(this.eventOrdinals, Event);
685 }
686
687 private static int GetOrdinal(SortedDictionary<string, int> Ordinals, string Key)
688 {
689 lock (Ordinals)
690 {
691 if (Ordinals.TryGetValue(Key, out int Ordinal))
692 return Ordinal;
693
694 Ordinal = Ordinals.Count;
695 Ordinals[Key] = Ordinal;
696
697 return Ordinal;
698 }
699 }
700
707 {
708 return GetOrdinal(this.exceptionOrdinals, Exception.GetType().FullName);
709 }
710
716 public int AddNote(object Note)
717 {
718 int Result;
719
720 lock (this.notes)
721 {
722 Result = this.notes.Count + 1;
723 this.notes[Result] = Note;
724 }
725
726 return Result;
727 }
728
732 public int NoteCount
733 {
734 get
735 {
736 lock (this.notes)
737 {
738 return this.notes.Count;
739 }
740 }
741 }
742
749 public bool TryGetNote(int Index, out object Note)
750 {
751 lock (this.notes)
752 {
753 return this.notes.TryGetValue(Index, out Note);
754 }
755 }
756 }
757}
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
Contains internal states used during generation of PlantUML diagram.s
Class that keeps track of events and timing.
Definition: Profiler.cs:68
DateTime Started
When profiling was started.
Definition: Profiler.cs:246
double ToSeconds(long Ticks)
Number of seconds, corresponding to a measured number of high-frequency clock ticks.
Definition: Profiler.cs:370
void Stop()
Stops measuring time.
Definition: Profiler.cs:227
long GetTicks(DateTime Timepoint)
Converts a System.DateTime to number of ticks, since start of profiling.
Definition: Profiler.cs:254
void Interval(DateTime From, DateTime To, string Label)
Records an interval in the main thread.
Definition: Profiler.cs:311
void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit, int GoalWidth)
Exports events to PlantUML.
Definition: Profiler.cs:553
ProfilerThread CreateThread(string Name, ProfilerThreadType Type)
Creates a new profiler thread.
Definition: Profiler.cs:128
void Idle()
Main Thread goes idle.
Definition: Profiler.cs:284
void NewState(string State)
Main Thread changes state.
Definition: Profiler.cs:267
void Event(string Name)
Event occurred on main thread
Definition: Profiler.cs:331
Profiler(ProfilerThreadType Type)
Class that keeps track of events and timing.
Definition: Profiler.cs:101
void High()
Sets the (binary) state of the Main Thread to "high".
Definition: Profiler.cs:292
KeyValuePair< double, string > ToTime(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit)
Time (amount, unit), corresponding to a measured number of high-frequency clock ticks.
Definition: Profiler.cs:382
bool TryGetNote(int Index, out object Note)
Tries to get a note from the profile.
Definition: Profiler.cs:749
string ExportXml(TimeUnit TimeUnit)
Exports events to XML.
Definition: Profiler.cs:477
void Low()
Sets the (binary) state Main Thread to "low".
Definition: Profiler.cs:300
Profiler(string Name)
Class that keeps track of events and timing.
Definition: Profiler.cs:92
void NewSample(double Sample)
A new sample value has been recored
Definition: Profiler.cs:276
void Interval(long From, long To, string Label)
Records an interval in the main thread.
Definition: Profiler.cs:322
void ExportXml(XmlWriter Output, TimeUnit TimeUnit)
Exports events to XML.
Definition: Profiler.cs:503
ProfilerThread GetThread(string Name, ProfilerThreadType Type)
Gets a profiler thread. If none is available, a new is created.
Definition: Profiler.cs:145
long ElapsedTicks
Elapsed ticks since start.
Definition: Profiler.cs:236
Profiler()
Class that keeps track of events and timing.
Definition: Profiler.cs:83
string ExportPlantUml(TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:530
int NoteCount
Number of notes added.
Definition: Profiler.cs:733
Profiler(string Name, ProfilerThreadType Type)
Class that keeps track of events and timing.
Definition: Profiler.cs:111
void Exception(System.Exception Exception, string Label)
Event occurred on main thread
Definition: Profiler.cs:360
int AddNote(object Note)
Adds a note to the profile.
Definition: Profiler.cs:716
ProfilerThread TryGetThread(string Name)
Tries to get an existing profiler thread. If none is available, null is returned.
Definition: Profiler.cs:164
double ElapsedSeconds
Elapsed seconds since start.
Definition: Profiler.cs:241
void Event(string Name, string Label)
Event occurred on main thread
Definition: Profiler.cs:341
int GetEventOrdinal(string Event)
Gets the ordinal for an event.
Definition: Profiler.cs:682
ProfilerThread MainThread
Main thread.
Definition: Profiler.cs:120
int GetExceptionOrdinal(System.Exception Exception)
Gets the ordinal for a type of exception.
Definition: Profiler.cs:706
void Exception(System.Exception Exception)
Event occurred on main thread
Definition: Profiler.cs:350
string ToTimeStr(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit, int NrDecimals)
String representation of time, corresponding to a measured number of high-frequency clock ticks.
Definition: Profiler.cs:466
void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:542
void Start()
Starts measuring time.
Definition: Profiler.cs:217
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.
Definition: Profiler.cs:17
ProfilerThreadType
Type of profiler thread.