From 08bef0d825bcd8834efdee874122b8df25307578 Mon Sep 17 00:00:00 2001 From: Greg Marzouka Date: Wed, 13 Aug 2014 15:28:17 -0400 Subject: [PATCH 1/2] Add support for NodesHotThreads Closes #508. --- src/Nest/DSL/NodesHotThreadsDescriptor.cs | 43 +++++++++ src/Nest/DSL/_Descriptors.generated.cs | 8 +- src/Nest/DSL/_Requests.generated.cs | 8 +- .../Responses/NodesHotThreadsResponse.cs | 35 ++++++++ src/Nest/ElasticClient-Nodes.cs | 87 +++++++++++++++++++ src/Nest/ElasticClient.cs | 2 +- src/Nest/IElasticClient.cs | 18 +++- src/Nest/Nest.csproj | 2 + .../Cluster/NodeTests.cs | 18 ++++ .../Nest.Tests.Unit/Nest.Tests.Unit.csproj | 2 +- .../Nodes/NodesHotThreadsRequestTests.cs | 39 +++++++++ 11 files changed, 245 insertions(+), 17 deletions(-) create mode 100644 src/Nest/DSL/NodesHotThreadsDescriptor.cs create mode 100644 src/Nest/Domain/Responses/NodesHotThreadsResponse.cs create mode 100644 src/Tests/Nest.Tests.Unit/ObjectInitializer/Nodes/NodesHotThreadsRequestTests.cs diff --git a/src/Nest/DSL/NodesHotThreadsDescriptor.cs b/src/Nest/DSL/NodesHotThreadsDescriptor.cs new file mode 100644 index 00000000000..3881942bb49 --- /dev/null +++ b/src/Nest/DSL/NodesHotThreadsDescriptor.cs @@ -0,0 +1,43 @@ + +using Elasticsearch.Net; +using Elasticsearch.Net.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Nest +{ + internal static class NodesHotThreadsPathInfo + { + public static void Update(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo, INodesHotThreadsRequest request) + { + pathInfo.HttpMethod = PathInfoHttpMethod.GET; + } + } + + public interface INodesHotThreadsRequest + : INodeIdOptionalPath + { + } + + public partial class NodesHotThreadsRequest + : NodeIdOptionalPathBase, INodesHotThreadsRequest + { + protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) + { + NodesHotThreadsPathInfo.Update(settings, pathInfo, this); + } + } + + public partial class NodesHotThreadsDescriptor + : NodeIdOptionalPathBase, INodesHotThreadsRequest + { + private INodesHotThreadsRequest Self { get { return this; } } + + protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) + { + NodesHotThreadsPathInfo.Update(settings, pathInfo, this.Self); + } + } +} diff --git a/src/Nest/DSL/_Descriptors.generated.cs b/src/Nest/DSL/_Descriptors.generated.cs index eb2600493ac..571af21245b 100644 --- a/src/Nest/DSL/_Descriptors.generated.cs +++ b/src/Nest/DSL/_Descriptors.generated.cs @@ -4405,7 +4405,7 @@ public MultiTermVectorsDescriptor Parent(string parent) ///http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/cluster-nodes-hot-threads.html /// /// - public partial class NodesHotThreadsDescriptor : BaseRequest + public partial class NodesHotThreadsDescriptor { @@ -4441,12 +4441,6 @@ public NodesHotThreadsDescriptor ThreadType(ThreadType thread_type) return this; } - - protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) - { - throw new NotImplementedException(); - } - } diff --git a/src/Nest/DSL/_Requests.generated.cs b/src/Nest/DSL/_Requests.generated.cs index ca788e34d51..2eab5b01bb8 100644 --- a/src/Nest/DSL/_Requests.generated.cs +++ b/src/Nest/DSL/_Requests.generated.cs @@ -3975,7 +3975,7 @@ public string Parent ///http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/cluster-nodes-hot-threads.html /// /// - public partial class NodesHotThreadsRequest : BasePathRequest + public partial class NodesHotThreadsRequest { ///The interval for the second sampling of threads @@ -4009,12 +4009,6 @@ public ThreadType ThreadType set { this.Request.RequestParameters.AddQueryString("type", value); } } - - protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) - { - throw new NotImplementedException(); - } - } diff --git a/src/Nest/Domain/Responses/NodesHotThreadsResponse.cs b/src/Nest/Domain/Responses/NodesHotThreadsResponse.cs new file mode 100644 index 00000000000..5f7f3543936 --- /dev/null +++ b/src/Nest/Domain/Responses/NodesHotThreadsResponse.cs @@ -0,0 +1,35 @@ +using Nest.Resolvers.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Nest +{ + public class HotThreadInformation + { + public HotThreadInformation() + { + this.Threads = new List(); + } + + public string Node { get; set; } + public List Threads { get; set; } + } + + public interface INodesHotThreadsResponse : IResponse + { + List HotThreads { get; } + } + + public class NodesHotThreadsResponse : BaseResponse, INodesHotThreadsResponse + { + public NodesHotThreadsResponse() + { + this.HotThreads = new List(); + } + + public List HotThreads { get; set; } + } +} diff --git a/src/Nest/ElasticClient-Nodes.cs b/src/Nest/ElasticClient-Nodes.cs index 4f6832a04a2..08f9d53fe9d 100644 --- a/src/Nest/ElasticClient-Nodes.cs +++ b/src/Nest/ElasticClient-Nodes.cs @@ -2,9 +2,15 @@ using System.Collections.Generic; using System.Threading.Tasks; using Elasticsearch.Net; +using System.IO; +using System.Linq; namespace Nest { + using NodesHotThreadConverter = Func; + using System.Text.RegularExpressions; + using System.Text; + public partial class ElasticClient { /// @@ -85,5 +91,86 @@ public Task NodesStatsAsync(INodesStatsRequest nodesStatsReq (p, d) => this.RawDispatch.NodesStatsDispatchAsync(p) ); } + + /// + public INodesHotThreadsResponse NodesHotThreads(Func selector = null) + { + selector = selector ?? (s => s); + return this.Dispatch( + selector, + (p, d) => this.RawDispatch.NodesHotThreadsDispatch( + p.DeserializationState(new NodesHotThreadConverter(DeserializeNodesHotThreadResponse))) + ); + } + + /// + public INodesHotThreadsResponse NodesHotThreads(INodesHotThreadsRequest nodesHotThreadsRequest) + { + return this.Dispatch( + nodesHotThreadsRequest, + (p, d) => this.RawDispatch.NodesHotThreadsDispatch( + p.DeserializationState(new NodesHotThreadConverter(DeserializeNodesHotThreadResponse))) + ); + } + + /// + public Task NodesHotThreadsAsync(Func selector = null) + { + selector = selector ?? (s => s); + return this.DispatchAsync( + selector, + (p, d) => this.RawDispatch.NodesHotThreadsDispatchAsync( + p.DeserializationState(new NodesHotThreadConverter(DeserializeNodesHotThreadResponse))) + ); + } + + /// + public Task NodesHotThreadsAsync(INodesHotThreadsRequest nodesHotThreadsRequest) + { + return this.DispatchAsync( + nodesHotThreadsRequest, + (p, d) => this.RawDispatch.NodesHotThreadsDispatchAsync( + p.DeserializationState(new NodesHotThreadConverter(DeserializeNodesHotThreadResponse))) + ); + } + + /// + /// Because the nodes.hot_threads endpoint returns plain text instead of JSON, we have to + /// manually parse the response text into a typed response object. + /// + private NodesHotThreadsResponse DeserializeNodesHotThreadResponse(IElasticsearchResponse response, Stream stream) + { + var typedResponse = new NodesHotThreadsResponse(); + var plainTextResponse = Encoding.UTF8.GetString(response.ResponseRaw); + + // If the response doesn't contain :::, which is the pattern that delimits + // each node section in the response, then the response format isn't recognized. + // Just return an empty response object. This is especially useful when unit + // testing against an in-memory connection where you won't get a real response. + if (!plainTextResponse.Contains(":::")) + return typedResponse; + + var sections = plainTextResponse.Split(new string[] { ":::" }, StringSplitOptions.RemoveEmptyEntries); + + foreach(var section in sections) + { + var sectionLines = section.Split(new string[] { "\n \n" }, StringSplitOptions.None); + + if (sectionLines.Length > 0) + { + var hotThreadInfo = new HotThreadInformation + { + // First line contains the node name between [ ] + Node = sectionLines.First().Split('[')[1].TrimEnd(']'), + // The rest of the lines are hot threads + Threads = sectionLines.Skip(1).Take(sectionLines.Length - 1).ToList() + }; + + typedResponse.HotThreads.Add(hotThreadInfo); + } + } + + return typedResponse; + } } } \ No newline at end of file diff --git a/src/Nest/ElasticClient.cs b/src/Nest/ElasticClient.cs index 9b1ab34077d..de593492a23 100644 --- a/src/Nest/ElasticClient.cs +++ b/src/Nest/ElasticClient.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; +using System.Linq; using System.Threading.Tasks; using Elasticsearch.Net; using Elasticsearch.Net.Connection; diff --git a/src/Nest/IElasticClient.cs b/src/Nest/IElasticClient.cs index 18bfddd11b2..5f5d2112ac1 100644 --- a/src/Nest/IElasticClient.cs +++ b/src/Nest/IElasticClient.cs @@ -612,7 +612,23 @@ Task DeleteMappingAsync(Func, De /// Task NodesStatsAsync(INodesStatsRequest nodesStatsRequest); - + + /// + /// An API allowing to get the current hot threads on each node in the cluster. + /// + /// + /// An optional descriptor to further describe the nodes hot threads operation + INodesHotThreadsResponse NodesHotThreads(Func selector = null); + + /// + INodesHotThreadsResponse NodesHotThreads(INodesHotThreadsRequest nodesHotThreadsRequest); + + /// + Task NodesHotThreadsAsync(Func selector = null); + + /// + Task NodesHotThreadsAsync(INodesHotThreadsRequest nodesHotThreadsRequest); + /// /// Used to check if the index (indices) exists or not. /// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-exists.html diff --git a/src/Nest/Nest.csproj b/src/Nest/Nest.csproj index 4d0b05434e0..1ff7eebbc7a 100644 --- a/src/Nest/Nest.csproj +++ b/src/Nest/Nest.csproj @@ -156,6 +156,7 @@ + @@ -168,6 +169,7 @@ + diff --git a/src/Tests/Nest.Tests.Integration/Cluster/NodeTests.cs b/src/Tests/Nest.Tests.Integration/Cluster/NodeTests.cs index 94897d6aa90..cbbe007581d 100644 --- a/src/Tests/Nest.Tests.Integration/Cluster/NodeTests.cs +++ b/src/Tests/Nest.Tests.Integration/Cluster/NodeTests.cs @@ -1,5 +1,6 @@ using System.Linq; using Elasticsearch.Net; +using FluentAssertions; using NUnit.Framework; namespace Nest.Tests.Integration.Cluster @@ -50,5 +51,22 @@ public void NodeStats() Assert.IsNotNull(node.Transport); Assert.IsNotNull(node.HTTP); } + + [Test] + public void NodesHotThreads() + { + var r = this.Client.NodesHotThreads(n => n + .Interval("20s") + .Snapshots(5) + .Threads(5) + .ThreadType(ThreadType.Cpu) + ); + + r.IsValid.Should().BeTrue(); + r.HotThreads.Count.Should().BeGreaterOrEqualTo(1); + var hotThreadInfo = r.HotThreads.First(); + hotThreadInfo.Node.Should().NotBeNullOrEmpty(); + hotThreadInfo.Threads.Count.ShouldBeEquivalentTo(5); + } } } \ No newline at end of file diff --git a/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj b/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj index 5445e76b87f..2020a37680d 100644 --- a/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj +++ b/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj @@ -181,7 +181,6 @@ Always - @@ -232,6 +231,7 @@ + diff --git a/src/Tests/Nest.Tests.Unit/ObjectInitializer/Nodes/NodesHotThreadsRequestTests.cs b/src/Tests/Nest.Tests.Unit/ObjectInitializer/Nodes/NodesHotThreadsRequestTests.cs new file mode 100644 index 00000000000..e35723f94c0 --- /dev/null +++ b/src/Tests/Nest.Tests.Unit/ObjectInitializer/Nodes/NodesHotThreadsRequestTests.cs @@ -0,0 +1,39 @@ +using Elasticsearch.Net; +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nest.Tests.Unit.ObjectInitializer.Nodes +{ + [TestFixture] + public class NodesHotThreadsRequestTests : BaseJsonTests + { + private readonly IElasticsearchResponse _status; + + public NodesHotThreadsRequestTests() + { + var request = new NodesHotThreadsRequest + { + NodeId = "my-node-id", + ThreadType = ThreadType.Block, + Threads = 10, + Snapshots = 2, + Interval = "5s" + }; + + var response = _client.NodesHotThreads(request); + this._status = response.ConnectionStatus; + } + + [Test] + public void Url() + { + this._status.RequestUrl.Should().EndWith("/nodes/my-node-id/hotthreads?type=block&threads=10&snapshots=2&interval=5s"); + this._status.RequestMethod.Should().Be("GET"); + } + } +} From 6351b57a1ea80a5e2ad07b7e67faefa436c0c48f Mon Sep 17 00:00:00 2001 From: Greg Marzouka Date: Wed, 13 Aug 2014 15:50:05 -0400 Subject: [PATCH 2/2] Smarter format recognition when parsing nodes.hot_threads response --- src/Nest/ElasticClient-Nodes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nest/ElasticClient-Nodes.cs b/src/Nest/ElasticClient-Nodes.cs index 08f9d53fe9d..1dba5e703ed 100644 --- a/src/Nest/ElasticClient-Nodes.cs +++ b/src/Nest/ElasticClient-Nodes.cs @@ -143,11 +143,11 @@ private NodesHotThreadsResponse DeserializeNodesHotThreadResponse(IElasticsearch var typedResponse = new NodesHotThreadsResponse(); var plainTextResponse = Encoding.UTF8.GetString(response.ResponseRaw); - // If the response doesn't contain :::, which is the pattern that delimits + // If the response doesn't start with :::, which is the pattern that delimits // each node section in the response, then the response format isn't recognized. // Just return an empty response object. This is especially useful when unit // testing against an in-memory connection where you won't get a real response. - if (!plainTextResponse.Contains(":::")) + if (!plainTextResponse.StartsWith(":::")) return typedResponse; var sections = plainTextResponse.Split(new string[] { ":::" }, StringSplitOptions.RemoveEmptyEntries);