Skip to content

Commit 14cee55

Browse files
authored
Add info on each HTTP client to HTTP stats (#64561)
1 parent 3d3ec5c commit 14cee55

File tree

4 files changed

+289
-23
lines changed

4 files changed

+289
-23
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/11_indices_metrics.yml

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,36 @@
225225
- is_false: nodes.$node_id.indices.recovery
226226
- is_true: nodes.$node_id.indices.segments.file_sizes
227227

228+
---
229+
"Metric - segments include_unloaded_segments":
230+
- skip:
231+
features: [arbitrary_key]
232+
version: " - 7.12.99"
233+
reason: "support for include_unloaded_segments only added in 7.13"
234+
- do:
235+
nodes.info: {}
236+
- set:
237+
nodes._arbitrary_key_: node_id
238+
239+
- do:
240+
nodes.stats: { metric: indices, index_metric: segments, include_unloaded_segments: true }
241+
242+
- is_false: nodes.$node_id.indices.docs
243+
- is_false: nodes.$node_id.indices.store
244+
- is_false: nodes.$node_id.indices.indexing
245+
- is_false: nodes.$node_id.indices.get
246+
- is_false: nodes.$node_id.indices.search
247+
- is_false: nodes.$node_id.indices.merges
248+
- is_false: nodes.$node_id.indices.refresh
249+
- is_false: nodes.$node_id.indices.flush
250+
- is_false: nodes.$node_id.indices.warmer
251+
- is_false: nodes.$node_id.indices.query_cache
252+
- is_false: nodes.$node_id.indices.fielddata
253+
- is_false: nodes.$node_id.indices.completion
254+
- is_true: nodes.$node_id.indices.segments
255+
- is_false: nodes.$node_id.indices.translog
256+
- is_false: nodes.$node_id.indices.recovery
257+
228258
---
229259
"Metric - _all include_unloaded_segments":
230260
- skip:
@@ -256,31 +286,31 @@
256286
- is_true: nodes.$node_id.indices.recovery
257287

258288
---
259-
"Metric - segments include_unloaded_segments":
289+
"Metric - http":
260290
- skip:
261291
features: [arbitrary_key]
262-
version: " - 7.12.99"
263-
reason: "support for include_unloaded_segments only added in 7.13"
292+
version: " - 7.99.99"
293+
reason: "change after backporting"
264294
- do:
265295
nodes.info: {}
266296
- set:
267297
nodes._arbitrary_key_: node_id
268298

269299
- do:
270-
nodes.stats: { metric: indices, index_metric: segments, include_unloaded_segments: true }
300+
nodes.stats: { metric: http }
271301

272-
- is_false: nodes.$node_id.indices.docs
273-
- is_false: nodes.$node_id.indices.store
274-
- is_false: nodes.$node_id.indices.indexing
275-
- is_false: nodes.$node_id.indices.get
276-
- is_false: nodes.$node_id.indices.search
277-
- is_false: nodes.$node_id.indices.merges
278-
- is_false: nodes.$node_id.indices.refresh
279-
- is_false: nodes.$node_id.indices.flush
280-
- is_false: nodes.$node_id.indices.warmer
281-
- is_false: nodes.$node_id.indices.query_cache
282-
- is_false: nodes.$node_id.indices.fielddata
283-
- is_false: nodes.$node_id.indices.completion
284-
- is_true: nodes.$node_id.indices.segments
285-
- is_false: nodes.$node_id.indices.translog
286-
- is_false: nodes.$node_id.indices.recovery
302+
- is_true: nodes.$node_id
303+
- gte: { nodes.$node_id.http.current_open: 1 }
304+
- gte: { nodes.$node_id.http.total_opened: 1 }
305+
- is_true: nodes.$node_id.http.clients
306+
- gte: { nodes.$node_id.http.clients.0.id: 1 }
307+
- match: { nodes.$node_id.http.clients.0.agent: "/.*/" }
308+
- match: { nodes.$node_id.http.clients.0.local_address: "/.*/" }
309+
- match: { nodes.$node_id.http.clients.0.remote_address: "/.*/" }
310+
- is_true: nodes.$node_id.http.clients.0.last_uri
311+
- gte: { nodes.$node_id.http.clients.0.opened_time_millis: 1614028911317 }
312+
- gte: { nodes.$node_id.http.clients.0.last_request_time_millis: 1614028911317 }
313+
- gte: { nodes.$node_id.http.clients.0.request_count: 1 }
314+
- gte: { nodes.$node_id.http.clients.0.request_size_bytes: 0 }
315+
# values for clients.0.closed_time_millis, clients.0.x_forwarded_for, and clients.0.x_opaque_id are often
316+
# null and cannot be tested here

server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@
4545
import java.util.Arrays;
4646
import java.util.Collections;
4747
import java.util.List;
48+
import java.util.Map;
4849
import java.util.Set;
4950
import java.util.concurrent.ConcurrentHashMap;
51+
import java.util.concurrent.TimeUnit;
5052
import java.util.concurrent.atomic.AtomicLong;
5153
import java.util.concurrent.atomic.AtomicReference;
5254

@@ -60,6 +62,9 @@ public abstract class AbstractHttpServerTransport extends AbstractLifecycleCompo
6062
private static final Logger logger = LogManager.getLogger(AbstractHttpServerTransport.class);
6163
private static final ActionListener<Void> NO_OP = ActionListener.wrap(() -> {});
6264

65+
private static final long PRUNE_THROTTLE_INTERVAL = TimeUnit.SECONDS.toMillis(60);
66+
private static final long MAX_CLIENT_STATS_AGE = TimeUnit.MINUTES.toMillis(5);
67+
6368
protected final Settings settings;
6469
public final HttpHandlingSettings handlingSettings;
6570
protected final NetworkService networkService;
@@ -78,10 +83,12 @@ public abstract class AbstractHttpServerTransport extends AbstractLifecycleCompo
7883
private final AtomicLong totalChannelsAccepted = new AtomicLong();
7984
private final Set<HttpChannel> httpChannels = Collections.newSetFromMap(new ConcurrentHashMap<>());
8085
private final Set<HttpServerChannel> httpServerChannels = Collections.newSetFromMap(new ConcurrentHashMap<>());
86+
private final Map<Integer, HttpStats.ClientStats> httpChannelStats = new ConcurrentHashMap<>();
8187

8288
private final HttpTracer tracer;
8389

8490
private volatile long slowLogThresholdMs;
91+
protected volatile long lastClientStatsPruneTime;
8592

8693
protected AbstractHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, ThreadPool threadPool,
8794
NamedXContentRegistry xContentRegistry, Dispatcher dispatcher, ClusterSettings clusterSettings) {
@@ -128,7 +135,26 @@ public HttpInfo info() {
128135

129136
@Override
130137
public HttpStats stats() {
131-
return new HttpStats(httpChannels.size(), totalChannelsAccepted.get());
138+
pruneClientStats(false);
139+
return new HttpStats(new ArrayList<>(httpChannelStats.values()), httpChannels.size(), totalChannelsAccepted.get());
140+
}
141+
142+
/**
143+
* Prunes client stats of entries that have been disconnected for more than five minutes.
144+
*
145+
* @param throttled When true, executes the prune process only if more than 60 seconds has elapsed since the last execution.
146+
*/
147+
void pruneClientStats(boolean throttled) {
148+
if (throttled == false || (threadPool.relativeTimeInMillis() - lastClientStatsPruneTime > PRUNE_THROTTLE_INTERVAL)) {
149+
long nowMillis = threadPool.absoluteTimeInMillis();
150+
for (var statsEntry : httpChannelStats.entrySet()) {
151+
long closedTimeMillis = statsEntry.getValue().closedTimeMillis;
152+
if (closedTimeMillis > 0 && (nowMillis - closedTimeMillis > MAX_CLIENT_STATS_AGE)) {
153+
httpChannelStats.remove(statsEntry.getKey());
154+
}
155+
}
156+
lastClientStatsPruneTime = threadPool.relativeTimeInMillis();
157+
}
132158
}
133159

134160
protected void bindServer() {
@@ -291,7 +317,23 @@ protected void serverAcceptedChannel(HttpChannel httpChannel) {
291317
boolean addedOnThisCall = httpChannels.add(httpChannel);
292318
assert addedOnThisCall : "Channel should only be added to http channel set once";
293319
totalChannelsAccepted.incrementAndGet();
294-
httpChannel.addCloseListener(ActionListener.wrap(() -> httpChannels.remove(httpChannel)));
320+
httpChannelStats.put(
321+
HttpStats.ClientStats.getChannelKey(httpChannel),
322+
new HttpStats.ClientStats(threadPool.absoluteTimeInMillis())
323+
);
324+
httpChannel.addCloseListener(ActionListener.wrap(() -> {
325+
try {
326+
httpChannels.remove(httpChannel);
327+
HttpStats.ClientStats clientStats = httpChannelStats.get(HttpStats.ClientStats.getChannelKey(httpChannel));
328+
if (clientStats != null) {
329+
clientStats.closedTimeMillis = threadPool.absoluteTimeInMillis();
330+
}
331+
} catch (Exception e) {
332+
// the listener code about should never throw
333+
logger.trace("error removing HTTP channel listener", e);
334+
}
335+
}));
336+
pruneClientStats(true);
295337
logger.trace(() -> new ParameterizedMessage("Http channel accepted: {}", httpChannel));
296338
}
297339

@@ -302,6 +344,7 @@ protected void serverAcceptedChannel(HttpChannel httpChannel) {
302344
* @param httpChannel that received the http request
303345
*/
304346
public void incomingRequest(final HttpRequest httpRequest, final HttpChannel httpChannel) {
347+
updateClientStats(httpRequest, httpChannel);
305348
final long startTime = threadPool.relativeTimeInMillis();
306349
try {
307350
handleIncomingRequest(httpRequest, httpChannel, httpRequest.getInboundException());
@@ -315,6 +358,41 @@ public void incomingRequest(final HttpRequest httpRequest, final HttpChannel htt
315358
}
316359
}
317360

361+
void updateClientStats(final HttpRequest httpRequest, final HttpChannel httpChannel) {
362+
HttpStats.ClientStats clientStats = httpChannelStats.get(HttpStats.ClientStats.getChannelKey(httpChannel));
363+
if (clientStats != null) {
364+
if (clientStats.agent == null) {
365+
if (hasAtLeastOneHeaderValue(httpRequest, "x-elastic-product-origin")) {
366+
clientStats.agent = httpRequest.getHeaders().get("x-elastic-product-origin").get(0);
367+
} else if (hasAtLeastOneHeaderValue(httpRequest, "User-Agent")) {
368+
clientStats.agent = httpRequest.getHeaders().get("User-Agent").get(0);
369+
}
370+
}
371+
if (clientStats.localAddress == null) {
372+
clientStats.localAddress = NetworkAddress.format(httpChannel.getLocalAddress());
373+
clientStats.remoteAddress = NetworkAddress.format(httpChannel.getRemoteAddress());
374+
}
375+
if (clientStats.forwardedFor == null) {
376+
if (hasAtLeastOneHeaderValue(httpRequest, "x-forwarded-for")) {
377+
clientStats.forwardedFor = httpRequest.getHeaders().get("x-forwarded-for").get(0);
378+
}
379+
}
380+
if (clientStats.opaqueId == null) {
381+
if (hasAtLeastOneHeaderValue(httpRequest, "x-opaque-id")) {
382+
clientStats.opaqueId = httpRequest.getHeaders().get("x-opaque-id").get(0);
383+
}
384+
}
385+
clientStats.lastRequestTimeMillis = threadPool.absoluteTimeInMillis();
386+
clientStats.lastUri = httpRequest.uri();
387+
clientStats.requestCount.increment();
388+
clientStats.requestSizeBytes.add(httpRequest.content().length());
389+
}
390+
}
391+
392+
private static boolean hasAtLeastOneHeaderValue(final HttpRequest request, final String header) {
393+
return request.getHeaders().containsKey(header) && request.getHeaders().get(header).size() > 0;
394+
}
395+
318396
// Visible for testing
319397
void dispatchRequest(final RestRequest restRequest, final RestChannel channel, final Throwable badRequestCause) {
320398
final ThreadContext threadContext = threadPool.getThreadContext();

0 commit comments

Comments
 (0)