From eaf5323f02fe34f22e3434492654614b8c952735 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 7 Sep 2020 18:50:18 +1000 Subject: [PATCH 1/2] Enable TCP states and ThreadPool stats This commit adds the ability to enable TCP and ThreadPool statistics to be collected when making a request, exposing these statistics on the IApiCallDetails on the response, as well as including in the debug information. Add documentation explaining how to include these statistics either on a global or per request basis. --- .../connection/configuration-options.asciidoc | 2 +- .../debug-information.asciidoc | 123 +++++++++++++++- .../troubleshooting/debug-mode.asciidoc | 72 +++++++++ docs/troubleshooting.asciidoc | 2 + .../Configuration/ConnectionConfiguration.cs | 22 +-- .../IConnectionConfigurationValues.cs | 10 ++ .../Configuration/RequestConfiguration.cs | 25 ++++ .../Connection/HttpConnection.cs | 35 ++++- .../Connection/HttpWebRequestConnection.cs | 71 ++++++--- .../Responses/ElasticsearchResponse.cs | 26 +++- .../Responses/HttpDetails/ApiCallDetails.cs | 19 ++- .../Responses/HttpDetails/IApiCallDetails.cs | 20 ++- .../Responses/ResponseStatics.cs | 34 +++++ src/Elasticsearch.Net/Stats/TcpStats.cs | 63 ++++++++ .../Stats/ThreadpoolStats.cs | 66 +++++++++ .../Transport/Pipeline/RequestData.cs | 7 +- .../Troubleshooting/DebugInformation.doc.cs | 137 +++++++++++++++++- .../Troubleshooting/DebugMode.doc.cs | 98 +++++++++++++ .../IntegrationDocumentationTestBase.cs | 6 +- tests/Tests/troubleshooting.asciidoc | 1 + 20 files changed, 784 insertions(+), 55 deletions(-) create mode 100644 docs/client-concepts/troubleshooting/debug-mode.asciidoc create mode 100644 src/Elasticsearch.Net/Stats/TcpStats.cs create mode 100644 src/Elasticsearch.Net/Stats/ThreadpoolStats.cs create mode 100644 tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs diff --git a/docs/client-concepts/connection/configuration-options.asciidoc b/docs/client-concepts/connection/configuration-options.asciidoc index d3d39031b6e..e90aa16ba6f 100644 --- a/docs/client-concepts/connection/configuration-options.asciidoc +++ b/docs/client-concepts/connection/configuration-options.asciidoc @@ -136,7 +136,7 @@ Sets the default ping timeout in milliseconds for ping requests, which are used `PrettyJson`:: -Forces all requests to have ?pretty=true querystring parameter appended, causing Elasticsearch to return formatted JSON. Also forces the client to send out formatted JSON. Defaults to `false` +Forces all requests to have ?pretty=true querystring parameter appended, causing Elasticsearch to return formatted JSON. Defaults to `false` `Proxy`:: diff --git a/docs/client-concepts/troubleshooting/debug-information.asciidoc b/docs/client-concepts/troubleshooting/debug-information.asciidoc index 63a8cd74e0f..aeb33a6fbb3 100644 --- a/docs/client-concepts/troubleshooting/debug-information.asciidoc +++ b/docs/client-concepts/troubleshooting/debug-information.asciidoc @@ -31,10 +31,17 @@ response.DebugInformation.Should().Contain("Valid NEST response"); ---- This can be useful in tracking down numerous problems and can also be useful when filing an -{github}/issues[issue] on our github repository. +{github}/issues[issue] on the GitHub repository. + +==== Request and response bytes By default, the request and response bytes are not available within the debug information, but -can be enabled globally on Connection Settings +can be enabled globally on Connection Settings by setting `DisableDirectStreaming`. This +disables direct streaming of + +. the serialized request type to the request stream + +. the response stream to a deserialized response type [source,csharp] ---- @@ -59,8 +66,116 @@ var response = client.Search(s => s .MatchAll() ) ); +---- +<1> disable direct streaming for *this* request only + +Configuring `DisableDirectStreaming` on an individual request takes precedence over +any global configuration. + +There is typically a performance and allocation cost associated with disabling direct streaming +since both the request and response bytes must be buffered in memory, to allow them to be +exposed on the response call details. + +==== TCP statistics + +It can often be useful to see the statistics for active TCP connections, particularly when +trying to diagnose issues with the client. The client can collect the states of active TCP +connections just before making a request, and expose these on the response and in the debug +information. + +Similarly to `DisableDirectStreaming`, TCP statistics can be collected for every request +by configuring on `ConnectionSettings` + +[source,csharp] +---- +var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + +var settings = new ConnectionSettings(connectionPool) + .EnableTcpStats(); <1> + +var client = new ElasticClient(settings); +---- +<1> collect TCP statistics for *all* requests + +or on a _per request_ basis + +[source,csharp] +---- +var response = client.Search(s => s + .RequestConfiguration(r => r + .EnableTcpStats() <1> + ) + .Query(q => q + .MatchAll() + ) +); + +var debugInformation = response.DebugInformation; +---- +<1> collect TCP statistics for *this* request only + +With `EnableTcpStats` set, the states of active TCP connections will now be included +on the response and in the debug information. + +The client includes a `TcpStats` +class to help with retrieving more detail about active TCP connections should it be +required + +[source,csharp] +---- +var tcpStatistics = TcpStats.GetActiveTcpConnections(); <1> +var ipv4Stats = TcpStats.GetTcpStatistics(NetworkInterfaceComponent.IPv4); <2> +var ipv6Stats = TcpStats.GetTcpStatistics(NetworkInterfaceComponent.IPv6); <3> + +var response = client.Search(s => s + .Query(q => q + .MatchAll() + ) +); +---- +<1> Retrieve details about active TCP connections, including local and remote addresses and ports +<2> Retrieve statistics about IPv4 +<3> Retrieve statistics about IPv6 + +==== ThreadPool statistics + +It can often be useful to see the statistics for thread pool threads, particularly when +trying to diagnose issues with the client. The client can collect statistics for both +worker threads and asynchronous I/O threads, and expose these on the response and +in debug information. + +Similar to collecting TCP statistics, ThreadPool statistics can be collected for all requests +by configuring `EnableThreadPoolStats` on `ConnectionSettings` -response.DebugInformation.Should().Contain("\"match_all\":"); +[source,csharp] +---- +var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + +var settings = new ConnectionSettings(connectionPool) + .EnableThreadPoolStats(); <1> + +var client = new ElasticClient(settings); ---- -<1> disable direct streaming for *this* request +<1> collect thread pool statistics for *all* requests + +or on a _per request_ basis + +[source,csharp] +---- +var response = client.Search(s => s + .RequestConfiguration(r => r + .EnableThreadPoolStats() <1> + ) + .Query(q => q + .MatchAll() + ) + ); + +var debugInformation = response.DebugInformation; <2> +---- +<1> collect thread pool statistics for *this* request only +<2> contains thread pool statistics + +With `EnableThreadPoolStats` set, the statistics of thread pool threads will now be included +on the response and in the debug information. diff --git a/docs/client-concepts/troubleshooting/debug-mode.asciidoc b/docs/client-concepts/troubleshooting/debug-mode.asciidoc new file mode 100644 index 00000000000..6ec23147d0b --- /dev/null +++ b/docs/client-concepts/troubleshooting/debug-mode.asciidoc @@ -0,0 +1,72 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/master + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +//// +IMPORTANT NOTE +============== +This file has been generated from https://github.com/elastic/elasticsearch-net/tree/master/src/Tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs. +If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file, +please modify the original csharp file found at the link and submit the PR with that change. Thanks! +//// + +[[debug-mode]] +=== Debug mode + +The <> explains that every response from Elasticsearch.Net +and NEST contains a `DebugInformation` property, and properties on `ConnectionSettings` and +`RequestConfiguration` can control which additional information is included in debug information, +for all requests or on a per request basis, respectively. + +During development, it can be useful to enable the most verbose debug information, to help +identify and troubleshoot problems, or simply ensure that the client is behaving as expected. +The `EnableDebugMode` setting on `ConnectionSettings` is a convenient shorthand for enabling +verbose debug information, configuring a number of settings like + +* disabling direct streaming to capture request and response bytes + +* prettyfying JSON responses from Elasticsearch + +* collecting TCP statistics when a request is made + +* collecting thread pool statistics when a request is made + +* including the Elasticsearch stack trace in the response if there is a an error on the server side + +[source,csharp] +---- +IConnectionPool pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + +var client = new ElasticClient(new ConnectionSettings(pool) + .EnableDebugMode() <1> +); + +var response = client.Search(s => s + .Query(q => q + .MatchAll() + ) +); + +var debugInformation = response.DebugInformation; <2> +---- +<1> configure debug mode +<2> verbose debug information + +In addition to exposing debug information on the response, debug mode will also cause the debug +information to be written to the trace listeners in the `System.Diagnostics.Debug.Listeners` collection +by default, when the request has completed. A delegate can be passed when enabling debug mode to perform +a different action when a request has completed, using <> + +[source,csharp] +---- +var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); +var client = new ElasticClient(new ConnectionSettings(pool) + .EnableDebugMode(apiCallDetails => + { + // do something with the call details + }) +); +---- + diff --git a/docs/troubleshooting.asciidoc b/docs/troubleshooting.asciidoc index d106c92a26b..e92271c8fbe 100644 --- a/docs/troubleshooting.asciidoc +++ b/docs/troubleshooting.asciidoc @@ -61,3 +61,5 @@ include::client-concepts/troubleshooting/audit-trail.asciidoc[] include::client-concepts/troubleshooting/debug-information.asciidoc[] +include::client-concepts/troubleshooting/debug-mode.asciidoc[] + diff --git a/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs b/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs index 8832e159b89..8272c8e414a 100644 --- a/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs +++ b/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs @@ -201,6 +201,8 @@ public abstract class ConnectionConfiguration : IConnectionConfigurationValue private bool _throwExceptions; private bool _transferEncodingChunked; private IMemoryStreamFactory _memoryStreamFactory = RecyclableMemoryStreamFactory.Default; + private bool _enableTcpStats; + private bool _enableThreadPoolStats; private string _userAgent = ConnectionConfiguration.DefaultUserAgent; private Func _statusCodeToResponseSuccess; @@ -280,6 +282,8 @@ protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection co string IConnectionConfigurationValues.UserAgent => _userAgent; Func IConnectionConfigurationValues.StatusCodeToResponseSuccess => _statusCodeToResponseSuccess; bool IConnectionConfigurationValues.TransferEncodingChunked => _transferEncodingChunked; + bool IConnectionConfigurationValues.EnableTcpStats => _enableTcpStats; + bool IConnectionConfigurationValues.EnableThreadPoolStats => _enableThreadPoolStats; void IDisposable.Dispose() => DisposeManagedResources(); @@ -453,7 +457,7 @@ public T Proxy(Uri proxyAddress, string username, SecureString password) => /// /// Forces all requests to have ?pretty=true querystring parameter appended, /// causing Elasticsearch to return formatted JSON. - /// Also forces the client to send out formatted JSON. Defaults to false + /// Defaults to false /// public T PrettyJson(bool b = true) => Assign(b, (a, v) => { @@ -562,16 +566,10 @@ public T EnableDebugMode(Action onRequestCompleted = null) => PrettyJson() .IncludeServerStackTraceOnError() .DisableDirectStreaming() + .EnableTcpStats() + .EnableThreadPoolStats() .Assign(onRequestCompleted, (a, v) => - { - var originalCompletedRequestHandler = _completedRequestHandler; - var debugCompletedRequestHandler = v ?? (d => Debug.WriteLine(d.DebugInformation)); - _completedRequestHandler = d => - { - originalCompletedRequestHandler?.Invoke(d); - debugCompletedRequestHandler.Invoke(d); - }; - }); + _completedRequestHandler += v ?? (d => Debug.WriteLine(d.DebugInformation))); /// /// Register a ServerCertificateValidationCallback, this is called per endpoint until it returns true. @@ -623,6 +621,10 @@ public T SkipDeserializationForStatusCodes(params int[] statusCodes) => /// public T MemoryStreamFactory(IMemoryStreamFactory memoryStreamFactory) => Assign(memoryStreamFactory, (a, v) => a._memoryStreamFactory = v); + public T EnableTcpStats(bool enableTcpStats = true) => Assign(enableTcpStats, (a, v) => a._enableTcpStats = v); + + public T EnableThreadPoolStats(bool enableThreadPoolStats = true) => Assign(enableThreadPoolStats, (a, v) => a._enableThreadPoolStats = v); + protected virtual void DisposeManagedResources() { _connectionPool?.Dispose(); diff --git a/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs b/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs index 3866356f1a1..6529e127042 100644 --- a/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs +++ b/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs @@ -263,5 +263,15 @@ public interface IConnectionConfigurationValues : IDisposable #endif /// TimeSpan DnsRefreshTimeout { get; } + + /// + /// Enable statistics about TCP connections to be collected when making a request + /// + bool EnableTcpStats { get; } + + /// + /// Enable statistics about thread pools to be collected when making a request + /// + bool EnableThreadPoolStats { get; } } } diff --git a/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs b/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs index ecc1983523d..71747b90b87 100644 --- a/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs +++ b/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs @@ -120,6 +120,12 @@ public interface IRequestConfiguration /// Try to send these headers for this single request /// NameValueCollection Headers { get; set; } + + /// + bool? EnableTcpStats { get; set; } + + /// + bool? EnableThreadPoolStats { get; set; } } public class RequestConfiguration : IRequestConfiguration @@ -162,6 +168,10 @@ public class RequestConfiguration : IRequestConfiguration public bool? TransferEncodingChunked { get; set; } /// public NameValueCollection Headers { get; set; } + /// + public bool? EnableTcpStats { get; set; } + /// + public bool? EnableThreadPoolStats { get; set; } } public class RequestConfigurationDescriptor : IRequestConfiguration @@ -209,6 +219,8 @@ public RequestConfigurationDescriptor(IRequestConfiguration config) bool? IRequestConfiguration.ThrowExceptions { get; set; } bool? IRequestConfiguration.TransferEncodingChunked { get; set; } NameValueCollection IRequestConfiguration.Headers { get; set; } + bool? IRequestConfiguration.EnableTcpStats { get; set; } + bool? IRequestConfiguration.EnableThreadPoolStats { get; set; } /// /// Submit the request on behalf in the context of a different shield user @@ -379,5 +391,18 @@ public RequestConfigurationDescriptor GlobalHeaders(NameValueCollection headers) return this; } + /// + public RequestConfigurationDescriptor EnableTcpStats(bool? enableTcpStats = true) + { + Self.EnableTcpStats = enableTcpStats; + return this; + } + + /// + public RequestConfigurationDescriptor EnableThreadPoolStats(bool? enableThreadPoolStats = true) + { + Self.EnableThreadPoolStats = enableThreadPoolStats; + return this; + } } } diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs index 3335daf9fca..bdac9badcbf 100644 --- a/src/Elasticsearch.Net/Connection/HttpConnection.cs +++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.IO.Compression; @@ -13,6 +14,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -65,6 +67,9 @@ public virtual TResponse Request(RequestData requestData) Exception ex = null; string mimeType = null; IDisposable receive = DiagnosticSources.SingletonDisposable; + ReadOnlyDictionary tcpStats = null; + ReadOnlyDictionary threadPoolStats = null; + try { var requestMessage = CreateHttpRequestMessage(requestData); @@ -75,6 +80,12 @@ public virtual TResponse Request(RequestData requestData) using(requestMessage?.Content ?? (IDisposable)Stream.Null) using (var d = DiagnosticSource.Diagnose(DiagnosticSources.HttpConnection.SendAndReceiveHeaders, requestData)) { + if (requestData.TcpStats) + tcpStats = TcpStats.GetStates(); + + if (requestData.ThreadPoolStats) + threadPoolStats = ThreadPoolStats.GetStats(); + responseMessage = client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); statusCode = (int)responseMessage.StatusCode; d.EndState = statusCode; @@ -99,14 +110,19 @@ public virtual TResponse Request(RequestData requestData) ex = e; } using(receive) - using (responseStream = responseStream ?? Stream.Null) + using (responseStream ??= Stream.Null) { var response = ResponseBuilder.ToResponse(requestData, ex, statusCode, warnings, responseStream, mimeType); + + // set TCP and threadpool stats on the response here so that in the event the request fails after the point of + // gathering stats, they are still exposed on the call details. Ideally these would be set inside ResponseBuilder.ToResponse, + // but doing so would be a breaking change in 7.x + response.ApiCall.TcpStats = tcpStats; + response.ApiCall.ThreadPoolStats = threadPoolStats; return response; } } - public virtual async Task RequestAsync(RequestData requestData, CancellationToken cancellationToken) where TResponse : class, IElasticsearchResponse, new() { @@ -118,6 +134,9 @@ public virtual async Task RequestAsync(RequestData request Exception ex = null; string mimeType = null; IDisposable receive = DiagnosticSources.SingletonDisposable; + ReadOnlyDictionary tcpStats = null; + ReadOnlyDictionary threadPoolStats = null; + try { var requestMessage = CreateHttpRequestMessage(requestData); @@ -128,6 +147,12 @@ public virtual async Task RequestAsync(RequestData request using(requestMessage?.Content ?? (IDisposable)Stream.Null) using (var d = DiagnosticSource.Diagnose(DiagnosticSources.HttpConnection.SendAndReceiveHeaders, requestData)) { + if (requestData.TcpStats) + tcpStats = TcpStats.GetStates(); + + if (requestData.ThreadPoolStats) + threadPoolStats = ThreadPoolStats.GetStats(); + responseMessage = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); statusCode = (int)responseMessage.StatusCode; d.EndState = statusCode; @@ -157,6 +182,12 @@ public virtual async Task RequestAsync(RequestData request var response = await ResponseBuilder.ToResponseAsync (requestData, ex, statusCode, warnings, responseStream, mimeType, cancellationToken) .ConfigureAwait(false); + + // set TCP and threadpool stats on the response here so that in the event the request fails after the point of + // gathering stats, they are still exposed on the call details. Ideally these would be set inside ResponseBuilder.ToResponse, + // but doing so would be a breaking change in 7.x + response.ApiCall.TcpStats = tcpStats; + response.ApiCall.ThreadPoolStats = threadPoolStats; return response; } } diff --git a/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs b/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs index 260e9fb877b..e1e036cf7d0 100644 --- a/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs +++ b/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; +using System.Net.NetworkInformation; using System.Net.Security; using System.Text; using System.Threading; @@ -24,8 +26,6 @@ static HttpWebRequestConnection() { //Not available under mono if (!IsMono) HttpWebRequest.DefaultMaximumErrorResponseLength = -1; - - } internal static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null; @@ -38,6 +38,9 @@ public virtual TResponse Request(RequestData requestData) Stream responseStream = null; Exception ex = null; string mimeType = null; + ReadOnlyDictionary tcpStats = null; + ReadOnlyDictionary threadPoolStats = null; + try { var request = CreateHttpWebRequest(requestData); @@ -56,25 +59,39 @@ public virtual TResponse Request(RequestData requestData) } requestData.MadeItToResponse = true; + if (requestData.TcpStats) + tcpStats = TcpStats.GetStates(); + + if (requestData.ThreadPoolStats) + threadPoolStats = ThreadPoolStats.GetStats(); + //http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.getresponsestream.aspx //Either the stream or the response object needs to be closed but not both although it won't //throw any errors if both are closed atleast one of them has to be Closed. //Since we expose the stream we let closing the stream determining when to close the connection - var response = (HttpWebResponse)request.GetResponse(); - HandleResponse(response, out statusCode, out responseStream, out mimeType); + var httpWebResponse = (HttpWebResponse)request.GetResponse(); + HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType); //response.Headers.HasKeys() can return false even if response.Headers.AllKeys has values. - if (response.SupportsHeaders && response.Headers.Count > 0 && response.Headers.AllKeys.Contains("Warning")) - warnings = response.Headers.GetValues("Warning"); + if (httpWebResponse.SupportsHeaders && httpWebResponse.Headers.Count > 0 && httpWebResponse.Headers.AllKeys.Contains("Warning")) + warnings = httpWebResponse.Headers.GetValues("Warning"); } catch (WebException e) { ex = e; - if (e.Response is HttpWebResponse response) - HandleResponse(response, out statusCode, out responseStream, out mimeType); + if (e.Response is HttpWebResponse httpWebResponse) + HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType); } - responseStream = responseStream ?? Stream.Null; - return ResponseBuilder.ToResponse(requestData, ex, statusCode, warnings, responseStream, mimeType); + + responseStream ??= Stream.Null; + var response = ResponseBuilder.ToResponse(requestData, ex, statusCode, warnings, responseStream, mimeType); + + // set TCP and threadpool stats on the response here so that in the event the request fails after the point of + // gathering stats, they are still exposed on the call details. Ideally these would be set inside ResponseBuilder.ToResponse, + // but doing so would be a breaking change in 7.x + response.ApiCall.TcpStats = tcpStats; + response.ApiCall.ThreadPoolStats = threadPoolStats; + return response; } public virtual async Task RequestAsync(RequestData requestData, @@ -88,6 +105,9 @@ CancellationToken cancellationToken Stream responseStream = null; Exception ex = null; string mimeType = null; + ReadOnlyDictionary tcpStats = null; + ReadOnlyDictionary threadPoolStats = null; + try { var data = requestData.PostData; @@ -116,29 +136,42 @@ CancellationToken cancellationToken //throw any errors if both are closed atleast one of them has to be Closed. //Since we expose the stream we let closing the stream determining when to close the connection - var apmGetResponseTask = Task.Factory.FromAsync(request.BeginGetResponse, r => request.EndGetResponse(r), null); + var apmGetResponseTask = Task.Factory.FromAsync(request.BeginGetResponse, r => request.EndGetResponse(r), null); unregisterWaitHandle = RegisterApmTaskTimeout(apmGetResponseTask, request, requestData); - var response = (HttpWebResponse)await apmGetResponseTask.ConfigureAwait(false); - HandleResponse(response, out statusCode, out responseStream, out mimeType); - if (response.SupportsHeaders && response.Headers.HasKeys() && response.Headers.AllKeys.Contains("Warning")) - warnings = response.Headers.GetValues("Warning"); + if (requestData.TcpStats) + tcpStats = TcpStats.GetStates(); + + if (requestData.ThreadPoolStats) + threadPoolStats = ThreadPoolStats.GetStats(); + + var httpWebResponse = (HttpWebResponse)await apmGetResponseTask.ConfigureAwait(false); + HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType); + if (httpWebResponse.SupportsHeaders && httpWebResponse.Headers.HasKeys() && httpWebResponse.Headers.AllKeys.Contains("Warning")) + warnings = httpWebResponse.Headers.GetValues("Warning"); } } catch (WebException e) { ex = e; - if (e.Response is HttpWebResponse response) - HandleResponse(response, out statusCode, out responseStream, out mimeType); + if (e.Response is HttpWebResponse httpWebResponse) + HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType); } finally { unregisterWaitHandle?.Invoke(); } - responseStream = responseStream ?? Stream.Null; - return await ResponseBuilder.ToResponseAsync + responseStream ??= Stream.Null; + var response = await ResponseBuilder.ToResponseAsync (requestData, ex, statusCode, warnings, responseStream, mimeType, cancellationToken) .ConfigureAwait(false); + + // set TCP and threadpool stats on the response here so that in the event the request fails after the point of + // gathering stats, they are still exposed on the call details. Ideally these would be set inside ResponseBuilder.ToResponse, + // but doing so would be a breaking change in 7.x + response.ApiCall.TcpStats = tcpStats; + response.ApiCall.ThreadPoolStats = threadPoolStats; + return response; } void IDisposable.Dispose() => DisposeManagedResources(); diff --git a/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs b/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs index 01514a9b9dd..ce32c5d5dfa 100644 --- a/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs +++ b/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs @@ -1,9 +1,11 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Net.NetworkInformation; namespace Elasticsearch.Net { @@ -15,6 +17,13 @@ public abstract class ElasticsearchResponseBase : IApiCallDetails, IElasticsearc /// public IApiCallDetails ApiCall { get; set; } + /// + public ReadOnlyDictionary TcpStats + { + get => ApiCall.TcpStats; + set => ApiCall.TcpStats = value; + } + /// public string DebugInformation => ApiCall.DebugInformation; /// @@ -26,6 +35,13 @@ public List AuditTrail set => ApiCall.AuditTrail = value; } + /// + public ReadOnlyDictionary ThreadPoolStats + { + get => ApiCall.ThreadPoolStats; + set => ApiCall.ThreadPoolStats = value; + } + /// public IEnumerable DeprecationWarnings => ApiCall.DeprecationWarnings; /// @@ -52,7 +68,7 @@ public List AuditTrail public byte[] RequestBodyInBytes => ApiCall.RequestBodyInBytes; bool IElasticsearchResponse.TryGetServerErrorReason(out string reason) => TryGetServerErrorReason(out reason); - + public virtual bool TryGetServerError(out ServerError serverError) { serverError = null; diff --git a/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs b/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs index d4d25917a02..94e67333031 100644 --- a/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs +++ b/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs @@ -4,21 +4,32 @@ using System; using System.Collections.Generic; -using Elasticsearch.Net.Extensions; +using System.Collections.ObjectModel; +using System.Net.NetworkInformation; +using System.Text; namespace Elasticsearch.Net { public class ApiCallDetails : IApiCallDetails { + private string _debugInformation; + public List AuditTrail { get; set; } + public ReadOnlyDictionary ThreadPoolStats { get; set; } + public ReadOnlyDictionary TcpStats { get; set; } public string DebugInformation { get { - var sb = new System.Text.StringBuilder(); + if (_debugInformation != null) + return _debugInformation; + + var sb = new StringBuilder(); sb.AppendLine(ToString()); - return ResponseStatics.DebugInformationBuilder(this, sb); + _debugInformation = ResponseStatics.DebugInformationBuilder(this, sb); + + return _debugInformation; } } @@ -42,6 +53,6 @@ public string DebugInformation public IConnectionConfigurationValues ConnectionConfiguration { get; set; } public override string ToString() => - $"{(Success ? "S" : "Uns")}uccessful ({HttpStatusCode}) low level call on {EnumExtensions.GetStringValue(HttpMethod)}: {Uri.PathAndQuery}"; + $"{(Success ? "S" : "Uns")}uccessful ({HttpStatusCode}) low level call on {HttpMethod.GetStringValue()}: {Uri.PathAndQuery}"; } } diff --git a/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs b/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs index 811e7f4f9a1..b0aca878cbd 100644 --- a/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs +++ b/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.Net.NetworkInformation; namespace Elasticsearch.Net { @@ -19,9 +21,20 @@ public interface IApiCallDetails /// List AuditTrail { get; set; } - /// - /// A lazy human readable string representation of what happened during this request for both successful and - /// failed requests. + // TODO: Get rid of setter. + /// + /// Thread pool thread statistics collected when making a request + /// + ReadOnlyDictionary ThreadPoolStats { get; set; } + + // TODO: Get rid of setter + /// + /// Active TCP connection statistics collected when making a request + /// + ReadOnlyDictionary TcpStats { get; set; } + + /// + /// A human readable string representation of what happened during this request for both successful and failed requests. /// string DebugInformation { get; } @@ -68,6 +81,7 @@ public interface IApiCallDetails /// The response MIME type string ResponseMimeType { get; } + /// /// The response status code is in the 200 range or is in the allowed list of status codes set on the request. /// diff --git a/src/Elasticsearch.Net/Responses/ResponseStatics.cs b/src/Elasticsearch.Net/Responses/ResponseStatics.cs index e982ddb73e3..07b0eb26f47 100644 --- a/src/Elasticsearch.Net/Responses/ResponseStatics.cs +++ b/src/Elasticsearch.Net/Responses/ResponseStatics.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.NetworkInformation; using System.Text; using Elasticsearch.Net.Extensions; @@ -37,6 +38,39 @@ public static string DebugInformationBuilder(IApiCallDetails r, StringBuilder sb sb.AppendLine($"# Request:{Environment.NewLine}{request}"); sb.AppendLine($"# Response:{Environment.NewLine}{response}"); + if (r.TcpStats != null) + { + sb.AppendLine("# TCP states:"); + foreach (var stat in r.TcpStats) + { + sb.Append(" "); + sb.Append(stat.Key); + sb.Append(": "); + sb.AppendLine($"{stat.Value}"); + } + sb.AppendLine(); + } + + if (r.ThreadPoolStats != null) + { + sb.AppendLine("# ThreadPool statistics:"); + foreach (var stat in r.ThreadPoolStats) + { + sb.Append(" "); + sb.Append(stat.Key); + sb.AppendLine(": "); + sb.Append(" Busy: "); + sb.AppendLine($"{stat.Value.Busy}"); + sb.Append(" Free: "); + sb.AppendLine($"{stat.Value.Free}"); + sb.Append(" Min: "); + sb.AppendLine($"{stat.Value.Min}"); + sb.Append(" Max: "); + sb.AppendLine($"{stat.Value.Max}"); + } + sb.AppendLine(); + } + return sb.ToString(); } diff --git a/src/Elasticsearch.Net/Stats/TcpStats.cs b/src/Elasticsearch.Net/Stats/TcpStats.cs new file mode 100644 index 00000000000..60258bd88ff --- /dev/null +++ b/src/Elasticsearch.Net/Stats/TcpStats.cs @@ -0,0 +1,63 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net.NetworkInformation; + +namespace Elasticsearch.Net +{ + /// + /// Gets statistics about TCP connection + /// + public static class TcpStats + { + private static readonly int StateLength = Enum.GetNames(typeof(TcpState)).Length; + + /// + /// Gets the active TCP connections + /// + /// + public static TcpConnectionInformation[] GetActiveTcpConnections() => + IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections(); + + /// + /// Gets the sum for each state of the active TCP connections + /// + public static ReadOnlyDictionary GetStates() + { + var states = new Dictionary(StateLength); + var connections = GetActiveTcpConnections(); + for (var index = 0; index < connections.Length; index++) + { + var connection = connections[index]; + if (states.TryGetValue(connection.State, out var count)) + states[connection.State] = ++count; + else + states.Add(connection.State, 1); + } + + return new ReadOnlyDictionary(states); + } + + /// + /// Gets the TCP statistics for a given network interface component + /// + public static TcpStatistics GetTcpStatistics(NetworkInterfaceComponent version) + { + var properties = IPGlobalProperties.GetIPGlobalProperties(); + switch (version) + { + case NetworkInterfaceComponent.IPv4: + return properties.GetTcpIPv4Statistics(); + case NetworkInterfaceComponent.IPv6: + return properties.GetTcpIPv6Statistics(); + default: + throw new ArgumentException("version"); + } + } + } +} diff --git a/src/Elasticsearch.Net/Stats/ThreadpoolStats.cs b/src/Elasticsearch.Net/Stats/ThreadpoolStats.cs new file mode 100644 index 00000000000..6a2225f2c60 --- /dev/null +++ b/src/Elasticsearch.Net/Stats/ThreadpoolStats.cs @@ -0,0 +1,66 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; + +namespace Elasticsearch.Net +{ + /// + /// Gets statistics about thread pools + /// + public class ThreadPoolStats + { + private static readonly string WorkerThreads = "Worker"; + private static readonly string CompletionPortThreads = "IOCP"; + + public static ReadOnlyDictionary GetStats() + { + var dictionary = new Dictionary(2); + ThreadPool.GetMaxThreads(out var maxWorkerThreads, out var maxIoThreads); + ThreadPool.GetAvailableThreads(out var freeWorkerThreads, out var freeIoThreads); + ThreadPool.GetMinThreads(out var minWorkerThreads, out var minIoThreads); + var busyIoThreads = maxIoThreads - freeIoThreads; + var busyWorkerThreads = maxWorkerThreads - freeWorkerThreads; + + dictionary.Add(WorkerThreads, new ThreadPoolStatistics(minWorkerThreads, maxWorkerThreads, busyWorkerThreads, freeWorkerThreads)); + dictionary.Add(CompletionPortThreads, new ThreadPoolStatistics(minIoThreads, maxIoThreads, busyIoThreads, freeIoThreads)); + return new ReadOnlyDictionary(dictionary); + } + } + + public class ThreadPoolStatistics + { + public ThreadPoolStatistics(int min, int max, int busy, int free) + { + Min = min; + Max = max; + Busy = busy; + Free = free; + } + + /// The difference between the maximum number of thread pool threads returned by + /// , and the number currently free. + /// + public int Busy { get; } + + /// The difference between the maximum number of thread pool threads returned by + /// , and the number currently active. + /// + public int Free { get; } + + /// + /// The number of requests to the thread pool that can be active concurrently. All requests above that number remain queued until + /// thread pool threads become available. + /// + public int Max { get; } + + /// + /// The minimum number of threads the thread pool creates on demand, as new requests are made, before switching to an algorithm for + /// managing thread creation and destruction. + /// + public int Min { get; } + } +} diff --git a/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs b/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs index 320665f36d1..cd53d7fab95 100644 --- a/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs +++ b/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using System.Security; using System.Security.Cryptography.X509Certificates; +using System.Threading; using Elasticsearch.Net.Extensions; namespace Elasticsearch.Net @@ -89,6 +90,8 @@ IMemoryStreamFactory memoryStreamFactory ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates; UserAgent = global.UserAgent; TransferEncodingChunked = local?.TransferEncodingChunked ?? global.TransferEncodingChunked; + TcpStats = local?.EnableTcpStats ?? global.EnableTcpStats; + ThreadPoolStats = local?.EnableThreadPoolStats ?? global.EnableThreadPoolStats; } private readonly string _path; @@ -133,6 +136,8 @@ IMemoryStreamFactory memoryStreamFactory public bool ThrowExceptions { get; } public string UserAgent { get; } public bool TransferEncodingChunked { get; } + public bool TcpStats { get; } + public bool ThreadPoolStats { get; } public Uri Uri => Node != null ? new Uri(Node.Uri, PathAndQuery) : null; public TimeSpan DnsRefreshTimeout { get; } @@ -142,7 +147,7 @@ IMemoryStreamFactory memoryStreamFactory // TODO This feels like its in the wrong place private string CreatePathWithQueryStrings(string path, IConnectionConfigurationValues global, IRequestParameters request) { - path = path ?? string.Empty; + path ??= string.Empty; if (path.Contains("?")) throw new ArgumentException($"{nameof(path)} can not contain querystring parameters and needs to be already escaped"); diff --git a/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs b/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs index 1b50e84c338..bc99bea99d0 100644 --- a/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs +++ b/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.NetworkInformation; using System.Security; using System.Text; using System.Threading.Tasks; @@ -101,11 +102,16 @@ [U] public void PasswordIsNotExposedInDebugInformationWhenPartOfUrl() } /** * This can be useful in tracking down numerous problems and can also be useful when filing an - * {github}/issues[issue] on our github repository. + * {github}/issues[issue] on the GitHub repository. + * + * ==== Request and response bytes * * By default, the request and response bytes are not available within the debug information, but - * can be enabled globally on Connection Settings + * can be enabled globally on Connection Settings by setting `DisableDirectStreaming`. This + * disables direct streaming of * + * . the serialized request type to the request stream + * . the response stream to a deserialized response type */ public void DisableDirectStreaming() { @@ -127,14 +133,139 @@ [I] public void DisableDirectStreamingPerRequest() var response = client.Search(s => s .RequestConfiguration(r => r - .DisableDirectStreaming() // <1> disable direct streaming for *this* request + .DisableDirectStreaming() // <1> disable direct streaming for *this* request only ) .Query(q => q .MatchAll() ) ); + // hide response.DebugInformation.Should().Contain("\"match_all\":"); } + + /** + * Configuring `DisableDirectStreaming` on an individual request takes precedence over + * any global configuration. + * + * There is typically a performance and allocation cost associated with disabling direct streaming + * since both the request and response bytes must be buffered in memory, to allow them to be + * exposed on the response call details. + * + * ==== TCP statistics + * + * It can often be useful to see the statistics for active TCP connections, particularly when + * trying to diagnose issues with the client. The client can collect the states of active TCP + * connections just before making a request, and expose these on the response and in the debug + * information. + * + * Similarly to `DisableDirectStreaming`, TCP statistics can be collected for every request + * by configuring on `ConnectionSettings` + */ + public void ConnectionSettingsTcpStats() + { + var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + + var settings = new ConnectionSettings(connectionPool) + .EnableTcpStats(); // <1> collect TCP statistics for *all* requests + + var client = new ElasticClient(settings); + } + + /** + * or on a _per request_ basis + */ + [I] public void RequestConfigurationTcpStats() + { + // hide + var client = this.Client; + + var response = client.Search(s => s + .RequestConfiguration(r => r + .EnableTcpStats() // <1> collect TCP statistics for *this* request only + ) + .Query(q => q + .MatchAll() + ) + ); + + var debugInformation = response.DebugInformation; + + // hide + debugInformation.Should().Contain("TCP states:"); + } + + /** + * With `EnableTcpStats` set, the states of active TCP connections will now be included + * on the response and in the debug information. + * + * The client includes a `TcpStats` + * class to help with retrieving more detail about active TCP connections should it be + * required + */ + [I] public void TcpStatistics() + { + // hide + var client = this.Client; + + var tcpStatistics = TcpStats.GetActiveTcpConnections(); // <1> Retrieve details about active TCP connections, including local and remote addresses and ports + var ipv4Stats = TcpStats.GetTcpStatistics(NetworkInterfaceComponent.IPv4); // <2> Retrieve statistics about IPv4 + var ipv6Stats = TcpStats.GetTcpStatistics(NetworkInterfaceComponent.IPv6); // <3> Retrieve statistics about IPv6 + + var response = client.Search(s => s + .Query(q => q + .MatchAll() + ) + ); + } + + /** + * + * ==== ThreadPool statistics + * + * It can often be useful to see the statistics for thread pool threads, particularly when + * trying to diagnose issues with the client. The client can collect statistics for both + * worker threads and asynchronous I/O threads, and expose these on the response and + * in debug information. + * + * Similar to collecting TCP statistics, ThreadPool statistics can be collected for all requests + * by configuring `EnableThreadPoolStats` on `ConnectionSettings` + */ + public void ConnectionSettingsThreadPoolStats() + { + var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + + var settings = new ConnectionSettings(connectionPool) + .EnableThreadPoolStats(); // <1> collect thread pool statistics for *all* requests + + var client = new ElasticClient(settings); + } + + /** + * or on a _per request_ basis + */ + [I] public void RequestConfigurationThreadPoolStats() + { + // hide + var client = this.Client; + + var response = client.Search(s => s + .RequestConfiguration(r => r + .EnableThreadPoolStats() // <1> collect thread pool statistics for *this* request only + ) + .Query(q => q + .MatchAll() + ) + ); + + var debugInformation = response.DebugInformation; // <2> contains thread pool statistics + + // hide + debugInformation.Should().Contain("ThreadPool statistics:"); + } + /** + * With `EnableThreadPoolStats` set, the statistics of thread pool threads will now be included + * on the response and in the debug information. + */ } } diff --git a/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs b/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs new file mode 100644 index 00000000000..3157cf455a5 --- /dev/null +++ b/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs @@ -0,0 +1,98 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Elastic.Elasticsearch.Xunit.Sdk; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elasticsearch.Net; +using FluentAssertions; +using Nest; +using Tests.Core.Client; +using Tests.Core.Client.Settings; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework; +using Tests.Framework.DocumentationTests; +using Xunit; + +namespace Tests.ClientConcepts.Troubleshooting +{ + /** + * === Debug mode + * + * The <> explains that every response from Elasticsearch.Net + * and NEST contains a `DebugInformation` property, and properties on `ConnectionSettings` and + * `RequestConfiguration` can control which additional information is included in debug information, + * for all requests or on a per request basis, respectively. + * + * During development, it can be useful to enable the most verbose debug information, to help + * identify and troubleshoot problems, or simply ensure that the client is behaving as expected. + * The `EnableDebugMode` setting on `ConnectionSettings` is a convenient shorthand for enabling + * verbose debug information, configuring a number of settings like + * + * * disabling direct streaming to capture request and response bytes + * * prettyfying JSON responses from Elasticsearch + * * collecting TCP statistics when a request is made + * * collecting thread pool statistics when a request is made + * * including the Elasticsearch stack trace in the response if there is a an error on the server side + */ + public class DebugMode : IntegrationDocumentationTestBase, IClusterFixture + { + public DebugMode(ReadOnlyCluster cluster) : base(cluster) { } + + [I] public void EnableDebugMode() + { + IConnectionPool pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + + // hide + pool = new StaticConnectionPool(Cluster.NodesUris()); + + var settings = new ConnectionSettings(pool) + .EnableDebugMode(); // <1> configure debug mode + + // hide + settings.DefaultIndex(Client.ConnectionSettings.DefaultIndex); + + var client = new ElasticClient(settings); + + var response = client.Search(s => s + .Query(q => q + .MatchAll() + ) + ); + + var debugInformation = response.DebugInformation; // <2> verbose debug information + + // hide + { + debugInformation.Should().Contain("TCP states:"); + debugInformation.Should().Contain("ThreadPool statistics:"); + } + } + + /** + * In addition to exposing debug information on the response, debug mode will also cause the debug + * information to be written to the trace listeners in the `System.Diagnostics.Debug.Listeners` collection + * by default, when the request has completed. A delegate can be passed when enabling debug mode to perform + * a different action when a request has completed, using <> + */ + public void DebugModeOnRequestCompleted() + { + var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + var client = new ElasticClient(new ConnectionSettings(pool) + .EnableDebugMode(apiCallDetails => + { + // do something with the call details + }) + ); + } + } + +} diff --git a/tests/Tests/Framework/DocumentationTests/IntegrationDocumentationTestBase.cs b/tests/Tests/Framework/DocumentationTests/IntegrationDocumentationTestBase.cs index 3ea2830b26c..c1ae0cf82d5 100644 --- a/tests/Tests/Framework/DocumentationTests/IntegrationDocumentationTestBase.cs +++ b/tests/Tests/Framework/DocumentationTests/IntegrationDocumentationTestBase.cs @@ -18,11 +18,11 @@ public abstract class DocumentationTestBase public abstract class IntegrationDocumentationTestBase { - private readonly ClientTestClusterBase _cluster; + protected readonly ClientTestClusterBase Cluster; - protected IntegrationDocumentationTestBase(ClientTestClusterBase cluster) => _cluster = cluster; + protected IntegrationDocumentationTestBase(ClientTestClusterBase cluster) => Cluster = cluster; - protected IElasticClient Client => _cluster.Client; + protected IElasticClient Client => Cluster.Client; protected static string RandomString() => Guid.NewGuid().ToString("N").Substring(0, 8); } diff --git a/tests/Tests/troubleshooting.asciidoc b/tests/Tests/troubleshooting.asciidoc index f7e4ac0b800..9cf60206695 100644 --- a/tests/Tests/troubleshooting.asciidoc +++ b/tests/Tests/troubleshooting.asciidoc @@ -46,6 +46,7 @@ help get you back on the happy path. include::client-concepts/troubleshooting/audit-trail.asciidoc[] include::client-concepts/troubleshooting/debug-information.asciidoc[] +include::client-concepts/troubleshooting/debug-mode.asciidoc[] From 97cd6ac32c0166e64b11d8ef881ea0db0e887ae2 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Tue, 8 Sep 2020 10:04:25 +1000 Subject: [PATCH 2/2] Move to Diagnostics namespace --- .../troubleshooting/debug-mode.asciidoc | 9 +++++---- .../Connection/HttpWebRequestConnection.cs | 1 + .../{Stats => Diagnostics}/TcpStats.cs | 5 ++--- .../{Stats => Diagnostics}/ThreadpoolStats.cs | 10 ++++++++-- .../Responses/ElasticsearchResponse.cs | 1 + .../Responses/HttpDetails/ApiCallDetails.cs | 1 + .../Responses/HttpDetails/IApiCallDetails.cs | 1 + .../Troubleshooting/DebugInformation.doc.cs | 1 + .../ClientConcepts/Troubleshooting/DebugMode.doc.cs | 2 +- 9 files changed, 21 insertions(+), 10 deletions(-) rename src/Elasticsearch.Net/{Stats => Diagnostics}/TcpStats.cs (95%) rename src/Elasticsearch.Net/{Stats => Diagnostics}/ThreadpoolStats.cs (91%) diff --git a/docs/client-concepts/troubleshooting/debug-mode.asciidoc b/docs/client-concepts/troubleshooting/debug-mode.asciidoc index 6ec23147d0b..0e49a217d56 100644 --- a/docs/client-concepts/troubleshooting/debug-mode.asciidoc +++ b/docs/client-concepts/troubleshooting/debug-mode.asciidoc @@ -39,9 +39,10 @@ verbose debug information, configuring a number of settings like ---- IConnectionPool pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); -var client = new ElasticClient(new ConnectionSettings(pool) - .EnableDebugMode() <1> -); +var settings = new ConnectionSettings(pool) + .EnableDebugMode(); <1> + +var client = new ElasticClient(settings); var response = client.Search(s => s .Query(q => q @@ -65,7 +66,7 @@ var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); var client = new ElasticClient(new ConnectionSettings(pool) .EnableDebugMode(apiCallDetails => { - // do something with the call details + // do something with the call details e.g. send with logging framework }) ); ---- diff --git a/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs b/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs index e1e036cf7d0..fcd8c1e9c00 100644 --- a/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs +++ b/src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs @@ -14,6 +14,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Elasticsearch.Net.Diagnostics; namespace Elasticsearch.Net { diff --git a/src/Elasticsearch.Net/Stats/TcpStats.cs b/src/Elasticsearch.Net/Diagnostics/TcpStats.cs similarity index 95% rename from src/Elasticsearch.Net/Stats/TcpStats.cs rename to src/Elasticsearch.Net/Diagnostics/TcpStats.cs index 60258bd88ff..210223de360 100644 --- a/src/Elasticsearch.Net/Stats/TcpStats.cs +++ b/src/Elasticsearch.Net/Diagnostics/TcpStats.cs @@ -5,13 +5,12 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Net.NetworkInformation; -namespace Elasticsearch.Net +namespace Elasticsearch.Net.Diagnostics { /// - /// Gets statistics about TCP connection + /// Gets statistics about TCP connections /// public static class TcpStats { diff --git a/src/Elasticsearch.Net/Stats/ThreadpoolStats.cs b/src/Elasticsearch.Net/Diagnostics/ThreadpoolStats.cs similarity index 91% rename from src/Elasticsearch.Net/Stats/ThreadpoolStats.cs rename to src/Elasticsearch.Net/Diagnostics/ThreadpoolStats.cs index 6a2225f2c60..774922c2a1b 100644 --- a/src/Elasticsearch.Net/Stats/ThreadpoolStats.cs +++ b/src/Elasticsearch.Net/Diagnostics/ThreadpoolStats.cs @@ -6,16 +6,19 @@ using System.Collections.ObjectModel; using System.Threading; -namespace Elasticsearch.Net +namespace Elasticsearch.Net.Diagnostics { /// - /// Gets statistics about thread pools + /// Retrieves Statistics for thread pools /// public class ThreadPoolStats { private static readonly string WorkerThreads = "Worker"; private static readonly string CompletionPortThreads = "IOCP"; + /// + /// Retrieve thread pool statistics + /// public static ReadOnlyDictionary GetStats() { var dictionary = new Dictionary(2); @@ -31,6 +34,9 @@ public static ReadOnlyDictionary GetStats() } } + /// + /// Statistics for a thread pool + /// public class ThreadPoolStatistics { public ThreadPoolStatistics(int min, int max, int busy, int free) diff --git a/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs b/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs index ce32c5d5dfa..41fc07c0c76 100644 --- a/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs +++ b/src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net.NetworkInformation; +using Elasticsearch.Net.Diagnostics; namespace Elasticsearch.Net { diff --git a/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs b/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs index 94e67333031..4737b20f106 100644 --- a/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs +++ b/src/Elasticsearch.Net/Responses/HttpDetails/ApiCallDetails.cs @@ -7,6 +7,7 @@ using System.Collections.ObjectModel; using System.Net.NetworkInformation; using System.Text; +using Elasticsearch.Net.Diagnostics; namespace Elasticsearch.Net { diff --git a/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs b/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs index b0aca878cbd..c2e68970db1 100644 --- a/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs +++ b/src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs @@ -7,6 +7,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Net.NetworkInformation; +using Elasticsearch.Net.Diagnostics; namespace Elasticsearch.Net { diff --git a/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs b/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs index bc99bea99d0..09f8642d48a 100644 --- a/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs +++ b/tests/Tests/ClientConcepts/Troubleshooting/DebugInformation.doc.cs @@ -12,6 +12,7 @@ using Elastic.Elasticsearch.Xunit.Sdk; using Elastic.Elasticsearch.Xunit.XunitPlumbing; using Elasticsearch.Net; +using Elasticsearch.Net.Diagnostics; using FluentAssertions; using Nest; using Tests.Core.Client; diff --git a/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs b/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs index 3157cf455a5..6a81cf2f7d6 100644 --- a/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs +++ b/tests/Tests/ClientConcepts/Troubleshooting/DebugMode.doc.cs @@ -89,7 +89,7 @@ public void DebugModeOnRequestCompleted() var client = new ElasticClient(new ConnectionSettings(pool) .EnableDebugMode(apiCallDetails => { - // do something with the call details + // do something with the call details e.g. send with logging framework }) ); }