using System; using System.Collections.Generic; using Best.HTTP.Shared; using Best.HTTP.Shared.PlatformSupport.Text; namespace Best.HTTP.Request.Timings { struct PartialEvent { public string EventName; public DateTime StartedAt; public PartialEvent(string eventName, DateTime startedAt) { this.EventName = eventName; this.StartedAt = startedAt; } public bool IsSet() => !string.IsNullOrEmpty(EventName) && StartedAt != DateTime.MinValue; public override string ToString() => $"[PartialEvent '{EventName}', {StartedAt.ToString("hh:mm:ss.fffffff")}]"; } /// /// Helper class to store, calculate and manage request related events and theirs duration, referenced by field. /// public sealed class TimingCollector { public HTTPRequest ParentRequest { get; } /// /// When the TimingCollector instance created. /// public DateTime Created { get; private set; } /// /// When the closing Finish event is sent. /// public DateTime Finished { get; private set; } /// /// List of added events. /// public List Events { get; private set; } private PartialEvent _partialEvent; internal TimingCollector(HTTPRequest parentRequest) { this.ParentRequest = parentRequest; this.Created = DateTime.Now; this.Finished = DateTime.MinValue; this._partialEvent = new PartialEvent(TimingEventNames.Initial, this.Created); } public void StartNext(string eventName) => TimingEventHelper.Enqueue(new TimingEventInfo(this.ParentRequest, TimingEvents.StartNext, eventName)); /// /// Finish the last event. /// public void Finish() => TimingEventHelper.Enqueue(new TimingEventInfo(this.ParentRequest, TimingEvents.Finish, null)); /// /// Abort the currently running event. /// internal void Abort() => TimingEventHelper.Enqueue(new TimingEventInfo(this.ParentRequest, TimingEvents.Abort)); internal void AddEvent(TimingEventInfo timingEvent) { switch(timingEvent.Event) { case TimingEvents.StartNext: if (this._partialEvent.IsSet()) { // If it's the same event name as the last one, merge the two event by not doing anything now. if (timingEvent.Name.Equals(this._partialEvent.EventName, StringComparison.OrdinalIgnoreCase)) break; AddEvent(this._partialEvent.EventName, this._partialEvent.StartedAt, timingEvent.Time - this._partialEvent.StartedAt); } if (timingEvent.Name != null) this._partialEvent = new PartialEvent(timingEvent.Name, timingEvent.Time); break; case TimingEvents.Finish: AddEvent(this._partialEvent.EventName, this._partialEvent.StartedAt, timingEvent.Time - this._partialEvent.StartedAt); var now = DateTime.Now; AddEvent(TimingEventNames.Finished, now, now - this.Created); this.Finished = now; if (HTTPManager.Logger.IsDiagnostic) HTTPManager.Logger.Information(nameof(TimingCollector), this.ToString(),ParentRequest.Context); break; } } /// /// When the event happened and for how long. /// internal void AddEvent(string name, DateTime when, TimeSpan duration) { if (this.Events == null) this.Events = new List(); if (duration == TimeSpan.Zero) { DateTime prevEventAt = this.Created; if (this.Events.Count > 0) prevEventAt = this.Events[this.Events.Count - 1].When; duration = when - prevEventAt; } this.Events.Add(new TimingEvent(name, when, duration)); } public TimingEvent FindFirst(string name) { if (this.Events == null) return TimingEvent.Empty; for (int i = 0; i < this.Events.Count; ++i) { if (this.Events[i].Name == name) return this.Events[i]; } return TimingEvent.Empty; } public TimingEvent FindLast(string name) { if (this.Events == null) return TimingEvent.Empty; for (int i = this.Events.Count - 1; i >= 0; --i) { if (this.Events[i].Name == name) return this.Events[i]; } return TimingEvent.Empty; } public override string ToString() { var sb = StringBuilderPool.Get(0); sb.Append("{\"Created\": \""); sb.Append(this.Created.ToString("yyyy-MM-dd hh:mm:ss.fffffff")); sb.Append("\",\"Finished\": \""); sb.Append(this.Finished.ToString("yyyy-MM-dd hh:mm:ss.fffffff")); if (this.Events != null) { sb.Append("\", \"Events\": "); sb.Append('['); for (int i = 0; i < this.Events.Count; ++i) { var @event = this.Events[i]; sb.Append("{\"Name\": \""); sb.Append(@event.Name); sb.Append("\", \"Duration\": \""); sb.Append(@event.Duration); sb.Append("\"}"); if (i < this.Events.Count - 1) sb.Append(','); } sb.Append(']'); } sb.Append('}'); return StringBuilderPool.ReleaseAndGrab(sb); } } }