|
@@ -51,6 +51,8 @@ import org.elasticsearch.features.NodeFeature;
|
|
|
import org.elasticsearch.http.HttpServerTransport;
|
|
|
import org.elasticsearch.plugins.ActionPlugin;
|
|
|
import org.elasticsearch.plugins.Plugin;
|
|
|
+import org.elasticsearch.plugins.PluginsService;
|
|
|
+import org.elasticsearch.plugins.TelemetryPlugin;
|
|
|
import org.elasticsearch.rest.BaseRestHandler;
|
|
|
import org.elasticsearch.rest.ChunkedRestResponseBodyPart;
|
|
|
import org.elasticsearch.rest.RestController;
|
|
@@ -60,6 +62,8 @@ import org.elasticsearch.rest.RestResponse;
|
|
|
import org.elasticsearch.rest.RestStatus;
|
|
|
import org.elasticsearch.rest.action.EmptyResponseListener;
|
|
|
import org.elasticsearch.rest.action.RestToXContentListener;
|
|
|
+import org.elasticsearch.telemetry.Measurement;
|
|
|
+import org.elasticsearch.telemetry.TestTelemetryPlugin;
|
|
|
import org.elasticsearch.test.ESIntegTestCase;
|
|
|
import org.elasticsearch.transport.netty4.NettyAllocator;
|
|
|
import org.elasticsearch.xcontent.ToXContentObject;
|
|
@@ -91,7 +95,7 @@ public class Netty4PipeliningIT extends ESNetty4IntegTestCase {
|
|
|
@Override
|
|
|
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
|
|
return CollectionUtils.concatLists(
|
|
|
- List.of(CountDown3Plugin.class, ChunkAndFailPlugin.class, KeepPipeliningPlugin.class),
|
|
|
+ List.of(CountDown3Plugin.class, ChunkAndFailPlugin.class, KeepPipeliningPlugin.class, TestTelemetryPlugin.class),
|
|
|
super.nodePlugins()
|
|
|
);
|
|
|
}
|
|
@@ -281,6 +285,90 @@ public class Netty4PipeliningIT extends ESNetty4IntegTestCase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public void testConnectionStatsExposedToTelemetryPlugin() throws Exception {
|
|
|
+ final var targetNode = internalCluster().startNode();
|
|
|
+
|
|
|
+ final var telemetryPlugin = asInstanceOf(
|
|
|
+ TestTelemetryPlugin.class,
|
|
|
+ internalCluster().getInstance(PluginsService.class, targetNode).filterPlugins(TelemetryPlugin.class).findAny().orElseThrow()
|
|
|
+ );
|
|
|
+
|
|
|
+ assertHttpMetrics(telemetryPlugin, 0L, 0L);
|
|
|
+
|
|
|
+ final var releasables = new ArrayList<Releasable>(3);
|
|
|
+ try {
|
|
|
+ final var keepPipeliningRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, KeepPipeliningPlugin.ROUTE);
|
|
|
+ releasables.add(keepPipeliningRequest::release);
|
|
|
+
|
|
|
+ final var responseReceivedLatch = new CountDownLatch(1);
|
|
|
+
|
|
|
+ final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
|
|
|
+ releasables.add(() -> eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS).awaitUninterruptibly());
|
|
|
+ final var clientBootstrap = new Bootstrap().channel(NettyAllocator.getChannelType())
|
|
|
+ .option(ChannelOption.ALLOCATOR, NettyAllocator.getAllocator())
|
|
|
+ .group(eventLoopGroup)
|
|
|
+ .handler(new ChannelInitializer<SocketChannel>() {
|
|
|
+ @Override
|
|
|
+ protected void initChannel(SocketChannel ch) {
|
|
|
+ ch.pipeline().addLast(new HttpClientCodec());
|
|
|
+ ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpResponse>() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void channelRead0(ChannelHandlerContext ctx, HttpResponse msg) {
|
|
|
+ responseReceivedLatch.countDown();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
+ ExceptionsHelper.maybeDieOnAnotherThread(new AssertionError(cause));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ final var httpServerTransport = internalCluster().getInstance(HttpServerTransport.class, targetNode);
|
|
|
+ final var httpServerAddress = randomFrom(httpServerTransport.boundAddress().boundAddresses()).address();
|
|
|
+
|
|
|
+ // Open a channel on which we will pipeline the requests to KeepPipeliningPlugin.ROUTE
|
|
|
+ final var pipeliningChannel = clientBootstrap.connect(httpServerAddress).syncUninterruptibly().channel();
|
|
|
+ releasables.add(() -> pipeliningChannel.close().syncUninterruptibly());
|
|
|
+
|
|
|
+ if (randomBoolean()) {
|
|
|
+ // assertBusy because client-side connect may complete before server-side
|
|
|
+ assertBusy(() -> assertHttpMetrics(telemetryPlugin, 1L, 1L));
|
|
|
+ } else {
|
|
|
+ // Send two pipelined requests so that we start to receive responses
|
|
|
+ pipeliningChannel.writeAndFlush(keepPipeliningRequest.retain());
|
|
|
+ pipeliningChannel.writeAndFlush(keepPipeliningRequest.retain());
|
|
|
+
|
|
|
+ // wait until we've started to receive responses (but we won't have received them all) - server side is definitely open now
|
|
|
+ safeAwait(responseReceivedLatch);
|
|
|
+ assertHttpMetrics(telemetryPlugin, 1L, 1L);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ Collections.reverse(releasables);
|
|
|
+ Releasables.close(releasables);
|
|
|
+ }
|
|
|
+
|
|
|
+ // assertBusy because client-side close may complete before server-side
|
|
|
+ assertBusy(() -> assertHttpMetrics(telemetryPlugin, 1L, 0L));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void assertHttpMetrics(TestTelemetryPlugin telemetryPlugin, long expectedTotal, long expectedCurrent) {
|
|
|
+ try {
|
|
|
+ telemetryPlugin.collect();
|
|
|
+ assertMeasurement(telemetryPlugin.getLongAsyncCounterMeasurement("es.http.connections.total"), expectedTotal);
|
|
|
+ assertMeasurement(telemetryPlugin.getLongGaugeMeasurement("es.http.connections.current"), expectedCurrent);
|
|
|
+ } finally {
|
|
|
+ telemetryPlugin.resetMeter();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void assertMeasurement(List<Measurement> measurements, long expectedValue) {
|
|
|
+ assertThat(measurements, hasSize(1));
|
|
|
+ assertThat(measurements.get(0).getLong(), equalTo(expectedValue));
|
|
|
+ }
|
|
|
+
|
|
|
private void assertOpaqueIdsInOrder(Collection<String> opaqueIds) {
|
|
|
// check if opaque ids are monotonically increasing
|
|
|
int i = 0;
|