1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-08 00:16:27 +00:00

added incoming kbps property for sockets, updated logging

This commit is contained in:
Jkorf 2021-09-29 13:28:35 +02:00
parent 89de0da724
commit aa07029c21
8 changed files with 137 additions and 164 deletions

View File

@ -1353,6 +1353,11 @@
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.MaxConcurrentResubscriptionsPerSocket">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxConcurrentResubscriptionsPerSocket"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.IncomingKbps">
<summary>
The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.ISocketClient.UnsubscribeAsync(CryptoExchange.Net.Sockets.UpdateSubscription)">
<summary>
Unsubscribe from a stream
@ -1548,6 +1553,11 @@
The max amount of outgoing messages per second
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.IncomingKbps">
<summary>
The current kilobytes per second of data being received, averaged over the last 3 seconds
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.DataInterpreterBytes">
<summary>
Handler for byte data
@ -1843,6 +1853,14 @@
<param name="error"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult`1.As``1(``0)">
<summary>
Copy the WebCallResult to a new data type
</summary>
<typeparam name="K">The new type</typeparam>
<param name="data">The data of the new type</param>
<returns></returns>
</member>
<member name="T:CryptoExchange.Net.Objects.WebCallResult">
<summary>
The result of a request
@ -3129,6 +3147,11 @@
The max amount of outgoing messages per socket per second
</summary>
</member>
<member name="P:CryptoExchange.Net.SocketClient.IncomingKbps">
<summary>
The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
</summary>
</member>
<member name="M:CryptoExchange.Net.SocketClient.#ctor(System.String,CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
ctor
@ -3454,6 +3477,11 @@
The timespan no data is received on the socket. If no data is received within this time an error is generated
</summary>
</member>
<member name="P:CryptoExchange.Net.Sockets.CryptoExchangeWebSocketClient.IncomingKbps">
<summary>
The current kilobytes per second of data being received, averaged over the last 3 seconds
</summary>
</member>
<member name="E:CryptoExchange.Net.Sockets.CryptoExchangeWebSocketClient.OnClose">
<summary>
Socket closed event
@ -3969,148 +3997,5 @@
<member name="M:CryptoExchange.Net.Sockets.WebsocketFactory.CreateWebsocket(CryptoExchange.Net.Logging.Log,System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.String})">
<inheritdoc />
</member>
<member name="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute">
<summary>
Specifies that <see langword="null"/> is allowed as an input even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.AllowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute">
<summary>
Specifies that <see langword="null"/> is disallowed as an input even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DisallowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute">
<summary>
Specifies that a method that will never return under any circumstance.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute">
<summary>
Specifies that the method will not return if the associated <see cref="T:System.Boolean"/>
parameter is passed the specified value.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.ParameterValue">
<summary>
Gets the condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.#ctor(System.Boolean)">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute"/>
class with the specified parameter value.
</summary>
<param name="parameterValue">
The condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute">
<summary>
Specifies that an output may be <see langword="null"/> even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue"/>,
the parameter may be <see langword="null"/> even if the corresponding type disallows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullAttribute">
<summary>
Specifies that an output is not <see langword="null"/> even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.NotNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute">
<summary>
Specifies that the output will be non-<see langword="null"/> if the
named parameter is non-<see langword="null"/>.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.ParameterName">
<summary>
Gets the associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.#ctor(System.String)">
<summary>
Initializes the attribute with the associated parameter name.
</summary>
<param name="parameterName">
The associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue"/>,
the parameter will not be <see langword="null"/> even if the corresponding type allows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</param>
</member>
</members>
</doc>

View File

@ -49,6 +49,10 @@ namespace CryptoExchange.Net.Interfaces
int? MaxResubscribeTries { get; }
/// <inheritdoc cref="SocketClientOptions.MaxConcurrentResubscriptionsPerSocket"/>
int MaxConcurrentResubscriptionsPerSocket { get; }
/// <summary>
/// The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
/// </summary>
double IncomingKbps { get; }
/// <summary>
/// Unsubscribe from a stream

View File

@ -47,7 +47,11 @@ namespace CryptoExchange.Net.Interfaces
/// <summary>
/// The max amount of outgoing messages per second
/// </summary>
public int? RatelimitPerSecond { get; set; }
int? RatelimitPerSecond { get; set; }
/// <summary>
/// The current kilobytes per second of data being received, averaged over the last 3 seconds
/// </summary>
double IncomingKbps { get; }
/// <summary>
/// Handler for byte data
/// </summary>

View File

@ -120,6 +120,17 @@ namespace CryptoExchange.Net.Objects
{
return new WebCallResult<T>(null, null, default, error);
}
/// <summary>
/// Copy the WebCallResult to a new data type
/// </summary>
/// <typeparam name="K">The new type</typeparam>
/// <param name="data">The data of the new type</param>
/// <returns></returns>
public CallResult<K> As<K>([AllowNull] K data)
{
return new CallResult<K>(data, Error);
}
}
/// <summary>

View File

@ -447,7 +447,8 @@ namespace CryptoExchange.Net.OrderBook
if (asks.First().Key < bids.First().Key)
{
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} detected out of sync order book. Resyncing");
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} detected out of sync order book. First ask: {asks.First().Key}, first bid: {bids.First().Key}. Resyncing");
_stopProcessing = true;
Resubscribe();
return;
}
@ -636,6 +637,7 @@ namespace CryptoExchange.Net.OrderBook
{
// Out of sync
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
_stopProcessing = true;
Resubscribe();
return false;
}

View File

@ -96,6 +96,20 @@ namespace CryptoExchange.Net
/// The max amount of outgoing messages per socket per second
/// </summary>
protected internal int? RateLimitPerSocketPerSecond { get; set; }
/// <summary>
/// The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
/// </summary>
public double IncomingKbps
{
get
{
if (!sockets.Any())
return 0;
return sockets.Sum(s => s.Value.Socket.IncomingKbps);
}
}
#endregion
/// <summary>
@ -193,7 +207,7 @@ namespace CryptoExchange.Net
if (socketConnection.PausedActivity)
{
log.Write(LogLevel.Information, "Socket has been paused, can't subscribe at this moment");
log.Write(LogLevel.Information, $"Socket {socketConnection.Socket.Id} has been paused, can't subscribe at this moment");
return new CallResult<UpdateSubscription>(default, new ServerError("Socket is paused"));
}
@ -284,7 +298,7 @@ namespace CryptoExchange.Net
if (socketConnection.PausedActivity)
{
log.Write(LogLevel.Information, "Socket has been paused, can't send query at this moment");
log.Write(LogLevel.Information, $"Socket {socketConnection.Socket.Id} has been paused, can't send query at this moment");
return new CallResult<T>(default, new ServerError("Socket is paused"));
}
@ -334,7 +348,7 @@ namespace CryptoExchange.Net
var result = await AuthenticateSocketAsync(socket).ConfigureAwait(false);
if (!result)
{
log.Write(LogLevel.Warning, "Socket authentication failed");
log.Write(LogLevel.Warning, $"Socket {socket.Socket.Id} authentication failed");
result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult<bool>(false, result.Error);
}
@ -435,7 +449,7 @@ namespace CryptoExchange.Net
var desResult = Deserialize<T>(messageEvent.JsonData, false);
if (!desResult)
{
log.Write(LogLevel.Warning, $"Failed to deserialize data into type {typeof(T)}: {desResult.Error}");
log.Write(LogLevel.Warning, $"Socket {connection.Socket.Id} Failed to deserialize data into type {typeof(T)}: {desResult.Error}");
return;
}
@ -528,7 +542,7 @@ namespace CryptoExchange.Net
protected virtual IWebsocket CreateSocket(string address)
{
var socket = SocketFactory.CreateWebsocket(log, address);
log.Write(LogLevel.Debug, "Created new socket for " + address);
log.Write(LogLevel.Debug, $"Socket {socket.Id} new socket created for " + address);
if (apiProxy != null)
socket.SetProxy(apiProxy);
@ -566,9 +580,6 @@ namespace CryptoExchange.Net
if (disposing)
break;
if (sockets.Any())
log.Write(LogLevel.Debug, "Sending periodic");
foreach (var socket in sockets.Values)
{
if (disposing)
@ -578,13 +589,15 @@ namespace CryptoExchange.Net
if (obj == null)
continue;
log.Write(LogLevel.Trace, $"Socket {socket.Socket.Id} sending periodic");
try
{
socket.Send(obj);
}
catch (Exception ex)
{
log.Write(LogLevel.Warning, "Periodic send failed: " + ex);
log.Write(LogLevel.Warning, $"Socket {socket.Socket.Id} Periodic send failed: " + ex);
}
}
}

View File

@ -7,6 +7,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Security.Authentication;
@ -36,7 +37,11 @@ namespace CryptoExchange.Net.Sockets
private bool _closing;
private bool _startedSent;
private bool _startedReceive;
private readonly List<DateTime> outgoingMessages;
private readonly List<DateTime> _outgoingMessages;
protected readonly Dictionary<DateTime, int> _receivedMessages;
private DateTime _lastReceivedMessagesUpdate;
protected readonly object _receivedMessagesLock;
/// <summary>
/// Log
@ -126,6 +131,25 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public TimeSpan Timeout { get; set; }
/// <summary>
/// The current kilobytes per second of data being received, averaged over the last 3 seconds
/// </summary>
public double IncomingKbps
{
get
{
UpdateReceivedMessages();
lock (_receivedMessagesLock)
{
if (!_receivedMessages.Any())
return 0;
return Math.Round(_receivedMessages.Values.Sum(v => v) / 1000 / 3d);
}
}
}
/// <summary>
/// Socket closed event
/// </summary>
@ -183,10 +207,12 @@ namespace CryptoExchange.Net.Sockets
this.cookies = cookies;
this.headers = headers;
outgoingMessages = new List<DateTime>();
_outgoingMessages = new List<DateTime>();
_receivedMessages = new Dictionary<DateTime, int>();
_sendEvent = new AutoResetEvent(false);
_sendBuffer = new ConcurrentQueue<byte[]>();
_ctsSource = new CancellationTokenSource();
_receivedMessagesLock = new object();
_socket = CreateSocket();
}
@ -244,7 +270,7 @@ namespace CryptoExchange.Net.Sockets
public virtual void Send(string data)
{
if (_closing)
throw new InvalidOperationException("Can't send data when socket is not connected");
throw new InvalidOperationException($"Socket {Id} Can't send data when socket is not connected");
var bytes = _encoding.GetBytes(data);
log.Write(LogLevel.Trace, $"Socket {Id} Adding {bytes.Length} to sent buffer");
@ -371,14 +397,14 @@ namespace CryptoExchange.Net.Sockets
}
if (start != null)
log.Write(LogLevel.Trace, $"Websocket sent delayed {Math.Round((DateTime.UtcNow - start.Value).TotalMilliseconds)}ms because of rate limit");
outgoingMessages.Add(DateTime.UtcNow);
log.Write(LogLevel.Trace, $"Socket {Id} sent delayed {Math.Round((DateTime.UtcNow - start.Value).TotalMilliseconds)}ms because of rate limit");
}
try
{
await _socket.SendAsync(new ArraySegment<byte>(data, 0, data.Length), WebSocketMessageType.Text, true, _ctsSource.Token).ConfigureAwait(false);
_outgoingMessages.Add(DateTime.UtcNow);
log.Write(LogLevel.Trace, $"Socket {Id} sent {data.Length} bytes");
}
catch (OperationCanceledException)
{
@ -427,6 +453,8 @@ namespace CryptoExchange.Net.Sockets
{
receiveResult = await _socket.ReceiveAsync(buffer, _ctsSource.Token).ConfigureAwait(false);
received += receiveResult.Count;
lock(_receivedMessagesLock)
_receivedMessages.Add(DateTime.UtcNow, receiveResult.Count);
}
catch (OperationCanceledException)
{
@ -462,20 +490,30 @@ namespace CryptoExchange.Net.Sockets
multiPartMessage = true;
if (memoryStream == null)
memoryStream = new MemoryStream();
log.Write(LogLevel.Trace, $"Socket {Id} received {receiveResult.Count} bytes in partial message");
await memoryStream.WriteAsync(buffer.Array, buffer.Offset, receiveResult.Count).ConfigureAwait(false);
}
else
{
if (!multiPartMessage)
{
// Received a complete message and it's not multi part
log.Write(LogLevel.Trace, $"Socket {Id} received {receiveResult.Count} bytes in single message");
HandleMessage(buffer.Array, buffer.Offset, receiveResult.Count, receiveResult.MessageType);
}
else
{
// Received the end of a multipart message, write to memory stream for reassembling
log.Write(LogLevel.Trace, $"Socket {Id} received {receiveResult.Count} bytes in partial message");
await memoryStream!.WriteAsync(buffer.Array, buffer.Offset, receiveResult.Count).ConfigureAwait(false);
}
break;
}
}
lock (_receivedMessagesLock)
UpdateReceivedMessages();
if (receiveResult?.MessageType == WebSocketMessageType.Close)
{
// Received close message
@ -491,6 +529,7 @@ namespace CryptoExchange.Net.Sockets
if (multiPartMessage)
{
// Reassemble complete message from memory stream
log.Write(LogLevel.Trace, $"Socket {Id} reassembled message of {memoryStream!.Length} bytes");
HandleMessage(memoryStream!.ToArray(), 0, (int)memoryStream.Length, receiveResult.MessageType);
memoryStream.Dispose();
}
@ -514,7 +553,9 @@ namespace CryptoExchange.Net.Sockets
try
{
strData = DataInterpreterBytes(data);
var relevantData = new byte[count];
Array.Copy(data, offset, relevantData, 0, count);
strData = DataInterpreterBytes(relevantData);
}
catch(Exception e)
{
@ -619,8 +660,21 @@ namespace CryptoExchange.Net.Sockets
private int MessagesSentLastSecond()
{
var testTime = DateTime.UtcNow;
outgoingMessages.RemoveAll(r => testTime - r > TimeSpan.FromSeconds(1));
return outgoingMessages.Count;
_outgoingMessages.RemoveAll(r => testTime - r > TimeSpan.FromSeconds(1));
return _outgoingMessages.Count;
}
protected void UpdateReceivedMessages()
{
var checkTime = DateTime.UtcNow;
if (checkTime - _lastReceivedMessagesUpdate > TimeSpan.FromSeconds(1))
{
foreach (var msgTime in _receivedMessages.Keys)
if (checkTime - msgTime > TimeSpan.FromSeconds(3))
_receivedMessages.Remove(msgTime);
_lastReceivedMessagesUpdate = checkTime;
}
}
}
}

View File

@ -97,7 +97,7 @@ namespace CryptoExchange.Net.Sockets
if (pausedActivity != value)
{
pausedActivity = value;
log.Write(LogLevel.Debug, "Paused activity: " + value);
log.Write(LogLevel.Debug, $"Socket {Socket.Id} Paused activity: " + value);
if(pausedActivity) ActivityPaused?.Invoke();
else ActivityUnpaused?.Invoke();
}