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;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Globalization;
5using System.Text;
6using System.Xml;
9
11{
15 public enum TimeUnit
16 {
20 DynamicPerEvent,
21
25 DynamicPerThread,
26
30 DynamicPerProfiling,
31
35 MicroSeconds,
36
40 MilliSeconds,
41
45 Seconds,
46
50 Minutes,
51
55 Hours,
56
60 Days
61 }
62
66 public class Profiler
67 {
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>();
73 private readonly ProfilerThread mainThread;
74 private readonly Stopwatch watch;
75 private DateTime started = DateTime.MinValue;
76 private double timeScale = 1;
77 private int threadOrder = 0;
78
82 public Profiler()
83 : this("Main", ProfilerThreadType.Sequential)
84 {
85 }
86
91 public Profiler(string Name)
92 : this(Name, ProfilerThreadType.Sequential)
93 {
94 }
95
101 : this("Main", Type)
102 {
103 }
104
110 public Profiler(string Name, ProfilerThreadType Type)
111 {
112 this.mainThread = this.CreateThread(Name, Type);
113 this.watch = new Stopwatch();
114 }
115
119 public ProfilerThread MainThread => this.mainThread;
120
128 {
129 lock (this.threads)
130 {
131 ProfilerThread Result = new ProfilerThread(Name, ++this.threadOrder, Type, this);
132 this.threads.Add(Result);
133 this.threadsByName[Name] = Result;
134 return Result;
135 }
136 }
137
145 {
146 lock (this.threads)
147 {
148 if (this.threadsByName.TryGetValue(Name, out ProfilerThread Result))
149 return Result;
150
151 Result = new ProfilerThread(Name, ++this.threadOrder, Type, this);
152 this.threads.Add(Result);
153 this.threadsByName[Name] = Result;
154 return Result;
155 }
156 }
157
165 internal ProfilerThread CreateThread(string Name, ProfilerThreadType Type, ProfilerThread Parent)
166 {
167 lock (this.threads)
168 {
169 ProfilerThread Result = new ProfilerThread(Name, ++this.threadOrder, Type, Parent);
170 this.threads.Add(Result);
171 this.threadsByName[Name] = Result;
172 return Result;
173 }
174 }
175
183 internal ProfilerThread GetThread(string Name, ProfilerThreadType Type, ProfilerThread Parent)
184 {
185 lock (this.threads)
186 {
187 if (this.threadsByName.TryGetValue(Name, out ProfilerThread Result))
188 return Result;
189
190 Result = new ProfilerThread(Name, ++this.threadOrder, Type, Parent);
191 this.threads.Add(Result);
192 this.threadsByName[Name] = Result;
193 return Result;
194 }
195 }
196
200 public void Start()
201 {
202 this.started = DateTime.Now;
203 this.watch.Start();
204 this.mainThread.Start();
205 }
206
210 public void Stop()
211 {
212 this.mainThread.Stop();
213 this.watch.Stop();
214 }
215
219 public long ElapsedTicks => this.watch.ElapsedTicks;
220
224 public double ElapsedSeconds => this.ToSeconds(this.ElapsedTicks);
225
229 public DateTime Started => this.started;
230
237 public long GetTicks(DateTime Timepoint)
238 {
239 double Seconds = (Timepoint - this.started).TotalSeconds;
240 return (long)(Seconds * Stopwatch.Frequency + 0.5);
241 }
242
247 public void NewState(string State)
248 {
249 this.mainThread.NewState(State);
250 }
251
256 public void NewSample(double Sample)
257 {
258 this.mainThread.NewSample(Sample);
259 }
260
264 public void Idle()
265 {
266 this.mainThread.Idle();
267 }
268
272 public void High()
273 {
274 this.mainThread.High();
275 }
276
280 public void Low()
281 {
282 this.mainThread.Low();
283 }
284
291 public void Interval(DateTime From, DateTime To, string Label)
292 {
293 this.mainThread.Interval(this.GetTicks(From), this.GetTicks(To), Label);
294 }
295
302 public void Interval(long From, long To, string Label)
303 {
304 this.mainThread.Interval(From, To, Label);
305 }
306
311 public void Event(string Name)
312 {
313 this.mainThread.Event(Name);
314 }
315
321 public void Event(string Name, string Label)
322 {
323 this.mainThread.Event(Name, Label);
324 }
325
330 public void Exception(System.Exception Exception)
331 {
332 this.mainThread.Exception(Exception);
333 }
334
340 public void Exception(System.Exception Exception, string Label)
341 {
342 this.mainThread.Exception(Exception, Label);
343 }
344
350 public double ToSeconds(long Ticks)
351 {
352 return ((double)Ticks) / Stopwatch.Frequency;
353 }
354
362 public KeyValuePair<double, string> ToTime(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit)
363 {
364 double Amount = this.ToSeconds(Ticks);
365 double Reference;
366
367 switch (TimeUnit)
368 {
369 case TimeUnit.MicroSeconds:
370 return new KeyValuePair<double, string>(Amount * 1e6, "μs");
371
372 case TimeUnit.MilliSeconds:
373 return new KeyValuePair<double, string>(Amount * 1e3, "ms");
374
375 case TimeUnit.Seconds:
376 return new KeyValuePair<double, string>(Amount, "s");
377
378 case TimeUnit.Minutes:
379 return new KeyValuePair<double, string>(Amount / 60, "min");
380
381 case TimeUnit.Hours:
382 return new KeyValuePair<double, string>(Amount / (60 * 60), "h");
383
384 case TimeUnit.Days:
385 return new KeyValuePair<double, string>(Amount / (24 * 60 * 60), "d");
386
387 case TimeUnit.DynamicPerProfiling:
388 Reference = this.ToSeconds(this.MainThread.StoppedAt ?? this.ElapsedTicks);
389 break;
390
391 case TimeUnit.DynamicPerThread:
392 Reference = this.ToSeconds(Thread.StoppedAt ?? this.ElapsedTicks);
393 break;
394
395 case TimeUnit.DynamicPerEvent:
396 default:
397 Reference = Amount;
398 break;
399 }
400
401 Reference *= this.timeScale;
402
403 if (Reference < 1)
404 {
405 Amount *= 1e3;
406 Reference *= 1e3;
407 if (Reference < 1)
408 {
409 Amount *= 1e3;
410 return new KeyValuePair<double, string>(Amount, "μs");
411 }
412 else
413 return new KeyValuePair<double, string>(Amount, "ms");
414 }
415 else if (Reference > 100)
416 {
417 Amount /= 60;
418 Reference /= 60;
419 if (Reference > 100)
420 {
421 Amount /= 60;
422 Reference /= 60;
423 if (Reference > 100)
424 {
425 Amount /= 24;
426 return new KeyValuePair<double, string>(Amount, "d");
427 }
428 else
429 return new KeyValuePair<double, string>(Amount, "h");
430 }
431 else
432 return new KeyValuePair<double, string>(Amount, "min");
433 }
434 else
435 return new KeyValuePair<double, string>(Amount, "s");
436 }
437
446 public string ToTimeStr(long Ticks, ProfilerThread Thread, TimeUnit TimeUnit, int NrDecimals)
447 {
448 KeyValuePair<double, string> Time = this.ToTime(Ticks, Thread, TimeUnit);
449 return Time.Key.ToString("F" + NrDecimals.ToString()) + " " + Time.Value;
450 }
451
458 {
459 StringBuilder sb = new StringBuilder();
460 XmlWriterSettings Settings = new XmlWriterSettings()
461 {
462 Indent = true,
463 IndentChars = "\t",
464 NewLineChars = "\r\n",
465 NewLineHandling = NewLineHandling.Replace,
466 NewLineOnAttributes = false,
467 OmitXmlDeclaration = true
468 };
469
470 using (XmlWriter Output = XmlWriter.Create(sb, Settings))
471 {
472 this.ExportXml(Output, TimeUnit);
473 }
474
475 return sb.ToString();
476 }
477
483 public void ExportXml(XmlWriter Output, TimeUnit TimeUnit)
484 {
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));
488
489 ProfilerThread[] Threads;
490
491 lock (this.threads)
492 {
493 Threads = this.threads.ToArray();
494 }
495
496 foreach (ProfilerThread Thread in Threads)
497 {
498 if (Thread.Parent is null)
499 Thread.ExportXml(Output, TimeUnit);
500 }
501
502 Output.WriteEndElement();
503 }
504
511 {
512 StringBuilder sb = new StringBuilder();
513 this.ExportPlantUml(sb, TimeUnit);
514 return sb.ToString();
515 }
516
522 public void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit)
523 {
524 this.ExportPlantUml(Output, TimeUnit, 1000);
525 }
526
533 public void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit, int GoalWidth)
534 {
535 switch (TimeUnit)
536 {
537 case TimeUnit.DynamicPerEvent:
538 case TimeUnit.DynamicPerThread:
539 throw new InvalidOperationException("Diagram requires the same time base to be used through-out.");
540 }
541
542 Output.AppendLine("@startuml");
543
544 ProfilerThread[] Threads;
545
546 lock (this.threads)
547 {
548 Threads = this.threads.ToArray();
549 }
550
551 foreach (ProfilerThread Thread in Threads)
552 {
553 if (Thread.Parent is null)
554 Thread.ExportPlantUmlDescription(Output, TimeUnit);
555 }
556
557 lock (this.eventOrdinals)
558 {
559 foreach (KeyValuePair<string, int> P in this.eventOrdinals)
560 {
561 Output.Append("concise \"");
562 Output.Append(P.Key);
563 Output.Append("\" as E");
564 Output.AppendLine(P.Value.ToString());
565 }
566 }
567
568 lock (this.exceptionOrdinals)
569 {
570 foreach (KeyValuePair<string, int> P in this.exceptionOrdinals)
571 {
572 Output.Append("concise \"");
573 Output.Append(P.Key);
574 Output.Append("\" as X");
575 Output.AppendLine(P.Value.ToString());
576 }
577 }
578
579 double TimeSpan;
580 double StepSize;
581 int NrSteps;
582
583 do
584 {
585 KeyValuePair<double, string> TotalTime = this.ToTime(this.mainThread.StoppedAt ?? this.ElapsedTicks, this.mainThread, TimeUnit);
586
587 TimeSpan = TotalTime.Key;
588 StepSize = Math.Pow(10, Math.Round(Math.Log10(TimeSpan / 10)));
589 NrSteps = (int)Math.Floor(TimeSpan / StepSize);
590
591 if (NrSteps >= 50)
592 StepSize *= 5;
593 else if (NrSteps >= 25)
594 StepSize *= 2.5;
595 else if (NrSteps >= 20)
596 StepSize *= 2;
597 else if (NrSteps <= 2)
598 StepSize /= 5;
599 else if (NrSteps <= 4)
600 StepSize /= 2.5;
601 else if (NrSteps <= 5)
602 StepSize /= 2;
603
604 if (StepSize < 1)
605 this.timeScale *= 1e-3;
606 }
607 while (StepSize < 1 && StepSize > 0);
608
609 StepSize = Math.Floor(StepSize);
610 NrSteps = (int)Math.Floor(TimeSpan / StepSize);
611 int PixelsPerStep = NrSteps > 0 ? GoalWidth / NrSteps : 0;
612
613 Output.Append("scale ");
614 Output.Append(StepSize.ToString("F0"));
615 Output.Append(" as ");
616 Output.Append(PixelsPerStep);
617 Output.AppendLine(" pixels");
618
620
621 foreach (ProfilerThread Thread in Threads)
622 {
623 if (Thread.Parent is null)
624 Thread.ExportPlantUmlEvents(States);
625 }
626
627 foreach (KeyValuePair<long, StringBuilder> P in States.ByTime)
628 {
629 KeyValuePair<double, string> Time = this.ToTime(P.Key, null, TimeUnit);
630 Output.Append('@');
631 Output.AppendLine(Time.Key.ToString("F7").Replace(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, "."));
632 Output.Append(P.Value.ToString());
633 }
634
635 Output.Append(States.Summary.ToString());
636 Output.AppendLine("@enduml");
637 }
638
644 public int GetEventOrdinal(string Event)
645 {
646 return GetOrdinal(this.eventOrdinals, Event);
647 }
648
649 private static int GetOrdinal(SortedDictionary<string, int> Ordinals, string Key)
650 {
651 lock (Ordinals)
652 {
653 if (Ordinals.TryGetValue(Key, out int Ordinal))
654 return Ordinal;
655
656 Ordinal = Ordinals.Count;
657 Ordinals[Key] = Ordinal;
658
659 return Ordinal;
660 }
661 }
662
668 public int GetExceptionOrdinal(System.Exception Exception)
669 {
670 return GetOrdinal(this.exceptionOrdinals, Exception.GetType().FullName);
671 }
672
678 public int AddNote(object Note)
679 {
680 int Result;
681
682 lock (this.notes)
683 {
684 Result = this.notes.Count + 1;
685 this.notes[Result] = Note;
686 }
687
688 return Result;
689 }
690
694 public int NoteCount
695 {
696 get
697 {
698 lock (this.notes)
699 {
700 return this.notes.Count;
701 }
702 }
703 }
704
711 public bool TryGetNote(int Index, out object Note)
712 {
713 lock (this.notes)
714 {
715 return this.notes.TryGetValue(Index, out Note);
716 }
717 }
718 }
719}
Contains internal states used during generation of PlantUML diagram.s
Class that keeps track of events and timing.
Definition: Profiler.cs:67
DateTime Started
When profiling was started.
Definition: Profiler.cs:229
double ToSeconds(long Ticks)
Number of seconds, corresponding to a measured number of high-frequency clock ticks.
Definition: Profiler.cs:350
void Stop()
Stops measuring time.
Definition: Profiler.cs:210
long GetTicks(DateTime Timepoint)
Converts a System.DateTime to number of ticks, since start of profiling.
Definition: Profiler.cs:237
void Interval(DateTime From, DateTime To, string Label)
Records an interval in the main thread.
Definition: Profiler.cs:291
void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit, int GoalWidth)
Exports events to PlantUML.
Definition: Profiler.cs:533
ProfilerThread CreateThread(string Name, ProfilerThreadType Type)
Creates a new profiler thread.
Definition: Profiler.cs:127
void Idle()
Main Thread goes idle.
Definition: Profiler.cs:264
void NewState(string State)
Main Thread changes state.
Definition: Profiler.cs:247
void Event(string Name)
Event occurred on main thread
Definition: Profiler.cs:311
Profiler(ProfilerThreadType Type)
Class that keeps track of events and timing.
Definition: Profiler.cs:100
void High()
Sets the (binary) state of the Main Thread to "high".
Definition: Profiler.cs:272
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:362
bool TryGetNote(int Index, out object Note)
Tries to get a note from the profile.
Definition: Profiler.cs:711
string ExportXml(TimeUnit TimeUnit)
Exports events to XML.
Definition: Profiler.cs:457
void Low()
Sets the (binary) state Main Thread to "low".
Definition: Profiler.cs:280
Profiler(string Name)
Class that keeps track of events and timing.
Definition: Profiler.cs:91
void NewSample(double Sample)
A new sample value has been recored
Definition: Profiler.cs:256
void Interval(long From, long To, string Label)
Records an interval in the main thread.
Definition: Profiler.cs:302
void ExportXml(XmlWriter Output, TimeUnit TimeUnit)
Exports events to XML.
Definition: Profiler.cs:483
ProfilerThread GetThread(string Name, ProfilerThreadType Type)
Gets a profiler thread. If none is available, a new is created.
Definition: Profiler.cs:144
long ElapsedTicks
Elapsed ticks since start.
Definition: Profiler.cs:219
Profiler()
Class that keeps track of events and timing.
Definition: Profiler.cs:82
string ExportPlantUml(TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:510
int NoteCount
Number of notes added.
Definition: Profiler.cs:695
Profiler(string Name, ProfilerThreadType Type)
Class that keeps track of events and timing.
Definition: Profiler.cs:110
void Exception(System.Exception Exception, string Label)
Event occurred on main thread
Definition: Profiler.cs:340
int AddNote(object Note)
Adds a note to the profile.
Definition: Profiler.cs:678
double ElapsedSeconds
Elapsed seconds since start.
Definition: Profiler.cs:224
void Event(string Name, string Label)
Event occurred on main thread
Definition: Profiler.cs:321
int GetEventOrdinal(string Event)
Gets the ordinal for an event.
Definition: Profiler.cs:644
ProfilerThread MainThread
Main thread.
Definition: Profiler.cs:119
int GetExceptionOrdinal(System.Exception Exception)
Gets the ordinal for a type of exception.
Definition: Profiler.cs:668
void Exception(System.Exception Exception)
Event occurred on main thread
Definition: Profiler.cs:330
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:446
void ExportPlantUml(StringBuilder Output, TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:522
void Start()
Starts measuring time.
Definition: Profiler.cs:200
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:16
ProfilerThreadType
Type of profiler thread.