Skip to content
6 changes: 6 additions & 0 deletions docs/changelog/96198.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 96198
summary: New HTTP info endpoint
area: Stats
type: feature
issues:
- 95391
2 changes: 2 additions & 0 deletions docs/reference/cluster.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ include::cluster/nodes-reload-secure-settings.asciidoc[]

include::cluster/nodes-stats.asciidoc[]

include::cluster/info-http.asciidoc[]

include::cluster/pending.asciidoc[]

include::cluster/remote-info.asciidoc[]
Expand Down
121 changes: 121 additions & 0 deletions docs/reference/cluster/info-http.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
[[cluster-info-http]]
=== Cluster Info HTTP API
++++
<titleabbrev>Cluster HTTP Info</titleabbrev>
++++

Returns cluster HTTP information.

[[cluster-info-http-api-request]]
==== {api-request-title}

`GET /_info/http` +

[[cluster-info-http-api-prereqs]]
==== {api-prereq-title}

* If the {es} {security-features} are enabled, you must have the `monitor` or
`manage` <<privileges-list-cluster,cluster privilege>> to use this API.


[[cluster-info-http-api-desc]]
==== {api-description-title}

You can use the Cluster Info HTTP API to retrieve http information in a cluster.

[role="child_attributes"]
[[cluster-info-http-api-response-body]]
==== {api-response-body-title}

`cluster_name`::
(string)
Name of the cluster. Based on the <<cluster-name>> setting.


[[cluster-info-http-api-response-body-http]]
`http`::
(object)
Contains http statistics for the cluster.
+
.Properties of `http`
[%collapsible%open]
======
`current_open`::
(integer)
Current number of open HTTP connections for the cluster.

`total_opened`::
(integer)
Total number of HTTP connections opened for the cluster.

`clients`::
(array of objects)
Information on current and recently-closed HTTP client connections.
Clients that have been closed longer than the <<http-settings,http.client_stats.closed_channels.max_age>>
setting will not be represented here.
+
.Properties of `clients`
[%collapsible%open]
=======
`id`::
(integer)
Unique ID for the HTTP client.

`agent`::
(string)
Reported agent for the HTTP client. If unavailable, this property is not
included in the response.

`local_address`::
(string)
Local address for the HTTP connection.

`remote_address`::
(string)
Remote address for the HTTP connection.

`last_uri`::
(string)
The URI of the client's most recent request.

`x_forwarded_for`::
(string)
Value from the client's `x-forwarded-for` HTTP header. If unavailable, this
property is not included in the response.

`x_opaque_id`::
(string)
Value from the client's `x-opaque-id` HTTP header. If unavailable, this property
is not included in the response.

`opened_time_millis`::
(integer)
Time at which the client opened the connection.

`closed_time_millis`::
(integer)
Time at which the client closed the connection if the connection is closed.

`last_request_time_millis`::
(integer)
Time of the most recent request from this client.

`request_count`::
(integer)
Number of requests from this client.

`request_size_bytes`::
(integer)
Cumulative size in bytes of all requests from this client.
=======
======


[[cluster-info-http-api-example]]
==== {api-examples-title}

[source,console]
----
# returns the http info of the cluster
GET /_info/http
----
4 changes: 2 additions & 2 deletions docs/reference/cluster/nodes-stats.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -2114,11 +2114,11 @@ included in the response.

`local_address`::
(string)
Local address for the HTTP client.
Local address for the HTTP connection.

`remote_address`::
(string)
Remote address for the HTTP client.
Remote address for the HTTP connection.

`last_uri`::
(string)
Expand Down
22 changes: 22 additions & 0 deletions rest-api-spec/src/main/resources/rest-api-spec/api/info.http.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"info.http": {
"documentation": {
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/cluster-info-http.html",
"description": "Returns statistical information about http status in the cluster."
},
"stability": "stable",
"visibility": "public",
"headers": {
"accept": ["application/json"]
},
"url": {
"paths": [
{
"path": "/_info/http",
"methods": ["GET"]
}
]
},
"params": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"HTTP Stats":
- skip:
version: " - 8.8.99"
reason: "/_info/http only available from v8.9"

- do:
info.http: {}

- is_true: cluster_name
- gte: { http.current_open: 0 }
- gte: { http.total_opened: 1 }
- is_true: http.clients
- gte: { http.clients.0.id: 1 }
- match: { http.clients.0.agent: "/.*/" }
- match: { http.clients.0.local_address: "/.*/" }
- match: { http.clients.0.remote_address: "/.*/" }
- is_true: http.clients.0.last_uri
- gte: { http.clients.0.opened_time_millis: 1684328268000 } # 2023-05-17
- gte: { http.clients.0.last_request_time_millis: 1684328268000 }
- gte: { http.clients.0.request_count: 1 }
- gte: { http.clients.0.request_size_bytes: 0 }
# values for clients.0.closed_time_millis, clients.0.x_forwarded_for, and clients.0.x_opaque_id are often
# null and cannot be tested here
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@
import org.elasticsearch.rest.action.document.RestMultiTermVectorsAction;
import org.elasticsearch.rest.action.document.RestTermVectorsAction;
import org.elasticsearch.rest.action.document.RestUpdateAction;
import org.elasticsearch.rest.action.info.RestHttpInfoAction;
import org.elasticsearch.rest.action.ingest.RestDeletePipelineAction;
import org.elasticsearch.rest.action.ingest.RestGetPipelineAction;
import org.elasticsearch.rest.action.ingest.RestPutPipelineAction;
Expand Down Expand Up @@ -931,6 +932,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
registerHandler.accept(new RestShardsAction());
registerHandler.accept(new RestMasterAction());
registerHandler.accept(new RestNodesAction());
registerHandler.accept(new RestHttpInfoAction());
registerHandler.accept(new RestTasksAction(nodesInCluster));
registerHandler.accept(new RestIndicesAction());
registerHandler.accept(new RestSegmentsAction());
Expand Down
11 changes: 11 additions & 0 deletions server/src/main/java/org/elasticsearch/http/HttpStats.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

public record HttpStats(long serverOpen, long totalOpen, List<ClientStats> clientStats) implements Writeable, ChunkedToXContent {

public static final HttpStats IDENTITY = new HttpStats(0, 0, List.of());

public HttpStats(long serverOpen, long totalOpened) {
this(serverOpen, totalOpened, List.of());
}
Expand Down Expand Up @@ -50,6 +53,14 @@ public List<ClientStats> getClientStats() {
return this.clientStats;
}

public static HttpStats merge(HttpStats first, HttpStats second) {
return new HttpStats(
first.serverOpen + second.serverOpen,
first.totalOpen + second.totalOpen,
Stream.concat(first.clientStats.stream(), second.clientStats.stream()).toList()
);
}

static final class Fields {
static final String HTTP = "http";
static final String CURRENT_OPEN = "current_open";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.rest.action.info;

import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.client.internal.node.NodeClient;
import org.elasticsearch.common.collect.Iterators;
import org.elasticsearch.common.xcontent.ChunkedToXContent;
import org.elasticsearch.common.xcontent.ChunkedToXContentHelper;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.ChunkedRestResponseBody;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestResponseListener;

import java.io.IOException;

import static org.elasticsearch.xcontent.ToXContent.EMPTY_PARAMS;

public abstract class AbstractInfoAction extends BaseRestHandler {

public abstract NodesStatsRequest buildNodeStatsRequest();

public abstract ChunkedToXContent xContentChunks(NodesStatsResponse nodesStatsResponse);

@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
return channel -> client.admin().cluster().nodesStats(buildNodeStatsRequest(), new RestResponseListener<>(channel) {
@Override
public RestResponse buildResponse(NodesStatsResponse nodesStatsResponse) throws Exception {
return new RestResponse(
RestStatus.OK,
ChunkedRestResponseBody.fromXContent(
outerParams -> Iterators.concat(
ChunkedToXContentHelper.startObject(),
Iterators.single(
(builder, params) -> builder.field("cluster_name", nodesStatsResponse.getClusterName().value())
),
xContentChunks(nodesStatsResponse).toXContentChunked(outerParams),
ChunkedToXContentHelper.endObject()
),
EMPTY_PARAMS,
channel
)
);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.rest.action.info;

import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.common.xcontent.ChunkedToXContent;
import org.elasticsearch.http.HttpStats;
import org.elasticsearch.rest.RestRequest;

import java.util.List;

public class RestHttpInfoAction extends AbstractInfoAction {

public static final NodesStatsRequest NODES_STATS_REQUEST = new NodesStatsRequest().clear()
.addMetric(NodesStatsRequest.Metric.HTTP.metricName());

@Override
public String getName() {
return "http_info_action";
}

@Override
public List<Route> routes() {
return List.of(new Route(RestRequest.Method.GET, "/_info/http"));
}

@Override
public NodesStatsRequest buildNodeStatsRequest() {
return NODES_STATS_REQUEST;
}

@Override
public ChunkedToXContent xContentChunks(NodesStatsResponse nodesStatsResponse) {
return nodesStatsResponse.getNodes().stream().map(NodeStats::getHttp).reduce(HttpStats.IDENTITY, HttpStats::merge);
}
}
Loading