1
0
Эх сурвалжийг харах

Add human-readable HTTP client stats (#134296)

Today the timestamps in the HTTP client stats output are not rendered as
proper human-readable timestamps even when `?human=true` is specified.
This commit adds the missing human-readable variants of these fields.
David Turner 1 сар өмнө
parent
commit
875a35c987

+ 5 - 0
docs/changelog/134296.yaml

@@ -0,0 +1,5 @@
+pr: 134296
+summary: Add human-readable HTTP client stats
+area: Network
+type: enhancement
+issues: []

+ 12 - 5
server/src/main/java/org/elasticsearch/http/HttpStats.java

@@ -15,7 +15,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.xcontent.ChunkedToXContent;
 import org.elasticsearch.xcontent.ToXContent;
-import org.elasticsearch.xcontent.ToXContentFragment;
+import org.elasticsearch.xcontent.ToXContentObject;
 import org.elasticsearch.xcontent.XContentBuilder;
 
 import java.io.IOException;
@@ -89,8 +89,11 @@ public record HttpStats(long serverOpen, long totalOpen, List<ClientStats> clien
         static final String CLIENT_LOCAL_ADDRESS = "local_address";
         static final String CLIENT_REMOTE_ADDRESS = "remote_address";
         static final String CLIENT_LAST_URI = "last_uri";
+        static final String CLIENT_OPENED_TIME = "opened_time";
         static final String CLIENT_OPENED_TIME_MILLIS = "opened_time_millis";
+        static final String CLIENT_CLOSED_TIME = "closed_time";
         static final String CLIENT_CLOSED_TIME_MILLIS = "closed_time_millis";
+        static final String CLIENT_LAST_REQUEST_TIME = "last_request_time";
         static final String CLIENT_LAST_REQUEST_TIME_MILLIS = "last_request_time_millis";
         static final String CLIENT_REQUEST_COUNT = "request_count";
         static final String CLIENT_REQUEST_SIZE_BYTES = "request_size_bytes";
@@ -136,7 +139,7 @@ public record HttpStats(long serverOpen, long totalOpen, List<ClientStats> clien
         long lastRequestTimeMillis,
         long requestCount,
         long requestSizeBytes
-    ) implements Writeable, ToXContentFragment {
+    ) implements Writeable, ToXContentObject {
 
         public static final long NOT_CLOSED = -1L;
 
@@ -179,11 +182,15 @@ public record HttpStats(long serverOpen, long totalOpen, List<ClientStats> clien
             if (opaqueId != null) {
                 builder.field(Fields.CLIENT_OPAQUE_ID, opaqueId);
             }
-            builder.field(Fields.CLIENT_OPENED_TIME_MILLIS, openedTimeMillis);
+            builder.timestampFieldsFromUnixEpochMillis(Fields.CLIENT_OPENED_TIME_MILLIS, Fields.CLIENT_OPENED_TIME, openedTimeMillis);
             if (closedTimeMillis != NOT_CLOSED) {
-                builder.field(Fields.CLIENT_CLOSED_TIME_MILLIS, closedTimeMillis);
+                builder.timestampFieldsFromUnixEpochMillis(Fields.CLIENT_CLOSED_TIME_MILLIS, Fields.CLIENT_CLOSED_TIME, closedTimeMillis);
             }
-            builder.field(Fields.CLIENT_LAST_REQUEST_TIME_MILLIS, lastRequestTimeMillis);
+            builder.timestampFieldsFromUnixEpochMillis(
+                Fields.CLIENT_LAST_REQUEST_TIME_MILLIS,
+                Fields.CLIENT_LAST_REQUEST_TIME,
+                lastRequestTimeMillis
+            );
             builder.field(Fields.CLIENT_REQUEST_COUNT, requestCount);
             builder.field(Fields.CLIENT_REQUEST_SIZE_BYTES, requestSizeBytes);
             builder.endObject();

+ 57 - 0
server/src/test/java/org/elasticsearch/http/HttpClientStatsTrackerTests.java

@@ -9,11 +9,14 @@
 
 package org.elasticsearch.http;
 
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.network.NetworkAddress;
 import org.elasticsearch.common.settings.ClusterSettings;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.Maps;
+import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.node.Node;
 import org.elasticsearch.rest.RestRequest;
@@ -22,8 +25,13 @@ import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.rest.FakeRestRequest;
 import org.elasticsearch.threadpool.DefaultBuiltInExecutorBuilders;
 import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.xcontent.ToXContent;
+import org.elasticsearch.xcontent.XContentFactory;
+import org.elasticsearch.xcontent.XContentType;
 
+import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.time.Instant;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -379,6 +387,55 @@ public class HttpClientStatsTrackerTests extends ESTestCase {
         }
     }
 
+    public void testToXContent() throws IOException {
+        final var clientStats = new HttpStats.ClientStats(
+            randomNonNegativeInt(),
+            randomIdentifier(),
+            randomIdentifier(),
+            randomIdentifier(),
+            randomIdentifier(),
+            randomIdentifier(),
+            randomIdentifier(),
+            randomLongBetween(1, Long.MAX_VALUE),
+            randomLongBetween(1, Long.MAX_VALUE),
+            randomLongBetween(1, Long.MAX_VALUE),
+            randomNonNegativeLong(),
+            randomNonNegativeLong()
+        );
+        final var description = Strings.toString(clientStats, false, true);
+        logger.info("description: {}", description);
+
+        final Map<String, Object> xcontentMap;
+        try (var builder = XContentFactory.jsonBuilder()) {
+            builder.humanReadable(true).value(clientStats, ToXContent.EMPTY_PARAMS);
+            xcontentMap = XContentHelper.convertToMap(BytesReference.bytes(builder), false, XContentType.JSON).v2();
+        }
+
+        assertEquals(description, clientStats.id(), xcontentMap.get("id"));
+        assertEquals(description, clientStats.agent(), xcontentMap.get("agent"));
+        assertEquals(description, clientStats.localAddress(), xcontentMap.get("local_address"));
+        assertEquals(description, clientStats.remoteAddress(), xcontentMap.get("remote_address"));
+        assertEquals(description, clientStats.lastUri(), xcontentMap.get("last_uri"));
+        assertEquals(description, clientStats.forwardedFor(), xcontentMap.get("x_forwarded_for"));
+        assertEquals(description, clientStats.opaqueId(), xcontentMap.get("x_opaque_id"));
+
+        assertEquals(description, clientStats.openedTimeMillis(), xcontentMap.get("opened_time_millis"));
+        assertEquals(description, Instant.ofEpochMilli(clientStats.openedTimeMillis()).toString(), xcontentMap.get("opened_time"));
+
+        assertEquals(description, clientStats.closedTimeMillis(), xcontentMap.get("closed_time_millis"));
+        assertEquals(description, Instant.ofEpochMilli(clientStats.closedTimeMillis()).toString(), xcontentMap.get("closed_time"));
+
+        assertEquals(description, clientStats.lastRequestTimeMillis(), xcontentMap.get("last_request_time_millis"));
+        assertEquals(
+            description,
+            Instant.ofEpochMilli(clientStats.lastRequestTimeMillis()).toString(),
+            xcontentMap.get("last_request_time")
+        );
+
+        assertEquals(description, clientStats.requestCount(), (long) xcontentMap.get("request_count"));
+        assertEquals(description, clientStats.requestSizeBytes(), (long) xcontentMap.get("request_size_bytes"));
+    }
+
     private Map<String, String> getRelevantHeaders(HttpRequest httpRequest) {
         final Map<String, String> headers = Maps.newMapWithExpectedSize(4);
         final String[] relevantHeaderNames = new String[] { "user-agent", "x-elastic-product-origin", "x-forwarded-for", "x-opaque-id" };