add all
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Hosts.Connections;
|
||||
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")}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to store, calculate and manage request related events and theirs duration, referenced by <see cref="HTTPRequest.Timing"/> field.
|
||||
/// </summary>
|
||||
public sealed class TimingCollector
|
||||
{
|
||||
public HTTPRequest ParentRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When the TimingCollector instance created.
|
||||
/// </summary>
|
||||
public DateTime Created { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the closing Finish event is sent.
|
||||
/// </summary>
|
||||
public DateTime Finished { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of added events.
|
||||
/// </summary>
|
||||
public List<TimingEvent> Events { get; private set; }
|
||||
|
||||
private PartialEvent _partialEvent;
|
||||
|
||||
internal TimingCollector(HTTPRequest parentRequest)
|
||||
{
|
||||
this.ParentRequest = parentRequest;
|
||||
this.Created = DateTime.UtcNow;
|
||||
this.Finished = DateTime.MinValue;
|
||||
this._partialEvent = new PartialEvent(TimingEventNames.Initial, this.Created);
|
||||
}
|
||||
|
||||
public void StartNext(string eventName) => RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.ParentRequest, new TimingEventInfo(this.ParentRequest, TimingEvents.StartNext, eventName)));
|
||||
|
||||
internal void AddEvent(TimingEventInfo timingEvent)
|
||||
{
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Information(nameof(TimingCollector), $"AddEvent({timingEvent}, {this._partialEvent})", ParentRequest.Context);
|
||||
|
||||
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 (this.Finished != DateTime.MinValue)
|
||||
AddEvent(timingEvent.Name, timingEvent.Time, timingEvent.Time - this.Events[^1].Start);
|
||||
else 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);
|
||||
|
||||
this._partialEvent = default;
|
||||
var now = DateTime.UtcNow;
|
||||
AddEvent(TimingEventNames.Finished, now, now - this.Created);
|
||||
|
||||
this.Finished = now;
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Information(nameof(TimingCollector), this.ToString(),ParentRequest.Context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the event happened and for how long.
|
||||
/// </summary>
|
||||
internal void AddEvent(string name, DateTime when, TimeSpan duration)
|
||||
{
|
||||
if (this.Events == null)
|
||||
this.Events = new List<TimingEvent>(16);
|
||||
|
||||
// Events can have zero ms durations, this block would set the same duration as the previous one,
|
||||
// and that's clearly wrong.
|
||||
/*if (duration == TimeSpan.Zero)
|
||||
{
|
||||
DateTime prevEventAt = this.Created;
|
||||
if (this.Events.Count > 0)
|
||||
prevEventAt = this.Events[this.Events.Count - 1].Start;
|
||||
duration = when - prevEventAt;
|
||||
}*/
|
||||
|
||||
//if (name != TimingEventNames.Queued)
|
||||
//{
|
||||
// int idx = this.Events.FindIndex(e => e.Name.Equals(name));
|
||||
// if (idx != -1)
|
||||
// HTTPManager.Logger.Warning(nameof(TimingCollector), $"'{name}' already present at idx '{idx}'({this.Events[idx].Start.ToString("hh:mm:ss.fffffff")}) new event time: {when.ToString("hh:mm:ss.fffffff")}! Current timing table: {this.ToString()}", this.ParentRequest.Context);
|
||||
//}
|
||||
|
||||
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 TimingEvent FindNext(TimingEvent relativeTo, string name)
|
||||
{
|
||||
int from = 0;
|
||||
|
||||
if (relativeTo != TimingEvent.Empty)
|
||||
{
|
||||
for (int i = 0; i < this.Events.Count; i++)
|
||||
{
|
||||
var ev = this.Events[i];
|
||||
|
||||
if (relativeTo.Equals(ev))
|
||||
{
|
||||
from = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = from; i < this.Events.Count; i++)
|
||||
{
|
||||
var ev = this.Events[i];
|
||||
|
||||
if (ev.Name == name)
|
||||
return ev;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d71df0a057c2faa46a145bf71a3c2f1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
|
||||
namespace Best.HTTP.Request.Timings
|
||||
{
|
||||
/// <summary>
|
||||
/// Struct to hold information about one timing event recorded for a <see cref="HTTPRequest"/>. Timing events are managed by the <see cref="TimingCollector"/>.
|
||||
/// </summary>
|
||||
public struct TimingEvent : IEquatable<TimingEvent>
|
||||
{
|
||||
public static readonly TimingEvent Empty = new TimingEvent(null, TimeSpan.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Name of the event
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Duration of the event.
|
||||
/// </summary>
|
||||
public readonly TimeSpan Duration;
|
||||
|
||||
/// <summary>
|
||||
/// When the event started.
|
||||
/// </summary>
|
||||
public readonly DateTime Start;
|
||||
|
||||
public TimingEvent(string name, TimeSpan duration)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Duration = duration;
|
||||
this.Start = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public TimingEvent(string name, DateTime when, TimeSpan duration)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Start = when;
|
||||
this.Duration = duration;
|
||||
}
|
||||
|
||||
public TimeSpan CalculateDuration(TimingEvent tEvent)
|
||||
{
|
||||
if (this.Start < tEvent.Start)
|
||||
return tEvent.Start - this.Start;
|
||||
|
||||
return this.Start - tEvent.Start;
|
||||
}
|
||||
|
||||
public bool Equals(TimingEvent other)
|
||||
{
|
||||
return this.Name == other.Name &&
|
||||
this.Duration == other.Duration &&
|
||||
this.Start == other.Start;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is TimingEvent)
|
||||
return this.Equals((TimingEvent)obj);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => (this.Name != null ? this.Name.GetHashCode() : 0) ^ this.Duration.GetHashCode() ^ this.Start.GetHashCode();
|
||||
|
||||
public static bool operator ==(TimingEvent lhs, TimingEvent rhs) => lhs.Equals(rhs);
|
||||
|
||||
public static bool operator !=(TimingEvent lhs, TimingEvent rhs) => !lhs.Equals(rhs);
|
||||
|
||||
public override string ToString() => $"['{this.Name}': {this.Duration} ({this.Start.ToString("hh:mm:ss.fffffff")})]";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0660f6d607bdf3c4595e0544ca50dc5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace Best.HTTP.Request.Timings
|
||||
{
|
||||
public enum TimingEvents
|
||||
{
|
||||
StartNext,
|
||||
Finish
|
||||
}
|
||||
|
||||
public readonly struct TimingEventInfo
|
||||
{
|
||||
public readonly HTTPRequest SourceRequest;
|
||||
public readonly TimingEvents Event;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly DateTime Time;
|
||||
|
||||
public TimingEventInfo(HTTPRequest parentRequest, TimingEvents timingEvent)
|
||||
{
|
||||
this.SourceRequest = parentRequest;
|
||||
this.Event = timingEvent;
|
||||
|
||||
this.Name = null;
|
||||
this.Time = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public TimingEventInfo(HTTPRequest parentRequest, TimingEvents timingEvent, string eventName)
|
||||
{
|
||||
this.SourceRequest = parentRequest;
|
||||
this.Event = timingEvent;
|
||||
|
||||
this.Name = eventName;
|
||||
this.Time = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{Event} \"{Name}\", Time: \"{Time.ToString("hh:mm:ss.fffffff")}\", Source: {SourceRequest.Context.Hash}]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c72c6b44e87158408cc72cb04fc43ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Best.HTTP.Request.Timings
|
||||
{
|
||||
public static class TimingEventNames
|
||||
{
|
||||
public const string Initial = "Initial";
|
||||
public const string Queued = "Queued";
|
||||
//public const string Queued_For_Redirection = "Queued for redirection";
|
||||
public const string ProxyDetection = "Proxy Detection";
|
||||
public const string DNS_Lookup = "DNS Lookup";
|
||||
public const string TCP_Connection = "TCP Connection";
|
||||
public const string Proxy_Negotiation = "Proxy Negotiation";
|
||||
public const string TLS_Negotiation = "TLS Negotiation";
|
||||
public const string Request_Sent = "Request Sent";
|
||||
public const string Waiting_TTFB = "Waiting (TTFB)";
|
||||
public const string Headers = "Headers";
|
||||
//public const string Loading_From_Cache = "Loading from Cache";
|
||||
//public const string Writing_To_Cache = "Writing to Cache";
|
||||
public const string Response_Received = "Response Received";
|
||||
//public const string Queued_For_Disptach = "Queued for Dispatch";
|
||||
public const string Finished = "Finished in";
|
||||
public const string Callback = "Callback";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0efad4dc5671b18459733670a6241006
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user