Skip to content

Commit 52feb00

Browse files
authored
cat API: added endpoint for Circuit Breakers (#136890)
Added CAT Action to display Circuit Breakers stats for all nodes. The API supports pattern matching as a path parameter and the standard query parameters of CAT actions. This change includes spec and yamlRestTest. Addresses #132688
1 parent a61e479 commit 52feb00

File tree

6 files changed

+561
-0
lines changed

6 files changed

+561
-0
lines changed

docs/changelog/136890.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 136890
2+
summary: "Cat API: added endpoint for Circuit Breakers"
3+
area: Infra/REST API
4+
type: enhancement
5+
issues: []
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"cat.circuit_breaker": {
3+
"documentation": {
4+
"url": "https://www.elastic.co/docs/api/doc/elasticsearch#TODO",
5+
"description": "Get circuit breakers statistics"
6+
},
7+
"stability": "stable",
8+
"visibility": "public",
9+
"headers": {
10+
"accept": [
11+
"text/plain",
12+
"application/json"
13+
]
14+
},
15+
"url": {
16+
"paths": [
17+
{
18+
"path": "/_cat/circuit_breaker",
19+
"methods": [
20+
"GET"
21+
]
22+
},
23+
{
24+
"path": "/_cat/circuit_breaker/{circuit_breaker_patterns}",
25+
"methods": [
26+
"GET"
27+
],
28+
"parts": {
29+
"circuit_breaker_patterns": {
30+
"type": "list",
31+
"description": "A comma-separated list of regular-expressions to filter the circuit breakers in the output"
32+
}
33+
}
34+
}
35+
]
36+
},
37+
"params": {
38+
"format": {
39+
"type": "string",
40+
"default": "text",
41+
"description": "a short version of the Accept header, e.g. json, yaml"
42+
},
43+
"time": {
44+
"type": "enum",
45+
"description": "The unit in which to display time values",
46+
"options": [
47+
"d",
48+
"h",
49+
"m",
50+
"s",
51+
"ms",
52+
"micros",
53+
"nanos"
54+
]
55+
},
56+
"local": {
57+
"type": "boolean",
58+
"default": false,
59+
"description": "Return local information, do not retrieve the state from master node (default: false)"
60+
},
61+
"master_timeout": {
62+
"type": "time",
63+
"default": "30s",
64+
"description": "Explicit operation timeout for connection to master node"
65+
},
66+
"h": {
67+
"type": "list",
68+
"description": "Comma-separated list of column names to display"
69+
},
70+
"help": {
71+
"type": "boolean",
72+
"description": "Return help information",
73+
"default": false
74+
},
75+
"s": {
76+
"type": "list",
77+
"description": "Comma-separated list of column names or column aliases to sort by"
78+
},
79+
"v": {
80+
"type": "boolean",
81+
"description": "Verbose mode. Display column headers",
82+
"default": false
83+
},
84+
"bytes": {
85+
"type": "enum",
86+
"description": "The unit in which to display byte values",
87+
"options": [
88+
"b",
89+
"kb",
90+
"mb",
91+
"gb",
92+
"tb",
93+
"pb"
94+
]
95+
}
96+
}
97+
}
98+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
"Test cat circuit_breaker help":
3+
- requires:
4+
capabilities:
5+
- method: GET
6+
path: /_cat/circuit_breaker
7+
test_runner_features: capabilities
8+
reason: Capability required to run test
9+
- do:
10+
cat.circuit_breaker:
11+
help: true
12+
13+
- match:
14+
$body: |
15+
/^ node_id .+ \n
16+
node_name .+ \n
17+
breaker .+ \n
18+
limit .+ \n
19+
limit_bytes .+ \n
20+
estimated .+ \n
21+
estimated_bytes .+ \n
22+
tripped .+ \n
23+
overhead .+ \n $/
24+
25+
---
26+
"Test cat circuit_breaker output":
27+
- requires:
28+
capabilities:
29+
- method: GET
30+
path: /_cat/circuit_breaker
31+
test_runner_features: capabilities
32+
reason: Capability required to run test
33+
- do:
34+
cat.circuit_breaker: {}
35+
36+
- match:
37+
$body: |
38+
/ #node_id breaker \s+ limit \s+ estimated \s+ tripped \n
39+
^ (\S+ \s+ \S+ \s+ \S+ \s+ \S+ \s+ \d+ \n)+ $/
40+
41+
- do:
42+
cat.circuit_breaker:
43+
v: true
44+
45+
- match:
46+
$body: |
47+
/^ node_id \s+ breaker \s+ limit \s+ estimated \s+ tripped \n
48+
(\S+ \s+ \S+ \s+ \S+ \s+ \S+ \s+ \d+ \n)+ $/
49+
50+
- do:
51+
cat.circuit_breaker:
52+
circuit_breaker_patterns: request,fielddata
53+
h: node_id,breaker,limit,estimated,tripped
54+
v: true
55+
56+
- match:
57+
$body: |
58+
/^ node_id \s+ breaker \s+ limit \s+ estimated \s+ tripped \n
59+
(\S+ \s+ (request|fielddata) \s+ \S+ \s+ \S+ \s+ \d+ \n){2,} $/
60+
61+
- do:
62+
cat.circuit_breaker:
63+
circuit_breaker_patterns: request
64+
h: node_id,breaker,limit,limit_bytes,estimated,estimated_bytes,tripped,overhead
65+
v: true
66+
67+
- match:
68+
$body: |
69+
/^ node_id \s+ breaker \s+ limit \s+ limit_bytes \s+ estimated \s+ estimated_bytes \s+ tripped \s+ overhead \n
70+
(\S+ \s+ request \s+ \S+ \s+ \d+ \s+ \S+ \s+ \d+ \s+ \d+ \s+ \d+\.\d+ \n)+ $/
71+
72+
- do:
73+
cat.circuit_breaker:
74+
circuit_breaker_patterns: "*"
75+
h: node_id,breaker,tripped
76+
77+
- match:
78+
$body: |
79+
/ #node_id breaker \s+ tripped \n
80+
^ (\S+ \s+ \S+ \s+ \d+ \n)+ $/

server/src/main/java/org/elasticsearch/action/ActionModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@
376376
import org.elasticsearch.rest.action.cat.RestAliasAction;
377377
import org.elasticsearch.rest.action.cat.RestAllocationAction;
378378
import org.elasticsearch.rest.action.cat.RestCatAction;
379+
import org.elasticsearch.rest.action.cat.RestCatCircuitBreakerAction;
379380
import org.elasticsearch.rest.action.cat.RestCatComponentTemplateAction;
380381
import org.elasticsearch.rest.action.cat.RestCatRecoveryAction;
381382
import org.elasticsearch.rest.action.cat.RestFielddataAction;
@@ -1028,6 +1029,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster, Predicate<
10281029
registerHandler.accept(new org.elasticsearch.rest.action.cat.RestPendingClusterTasksAction());
10291030
registerHandler.accept(new RestAliasAction());
10301031
registerHandler.accept(new RestThreadPoolAction());
1032+
registerHandler.accept(new RestCatCircuitBreakerAction());
10311033
registerHandler.accept(new RestPluginsAction());
10321034
registerHandler.accept(new RestFielddataAction());
10331035
registerHandler.accept(new RestNodeAttrsAction());
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.rest.action.cat;
11+
12+
import org.elasticsearch.action.FailedNodeException;
13+
import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
14+
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
15+
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequestParameters;
16+
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
17+
import org.elasticsearch.client.internal.node.NodeClient;
18+
import org.elasticsearch.common.Table;
19+
import org.elasticsearch.common.regex.Regex;
20+
import org.elasticsearch.common.unit.ByteSizeValue;
21+
import org.elasticsearch.indices.breaker.CircuitBreakerStats;
22+
import org.elasticsearch.rest.RestRequest;
23+
import org.elasticsearch.rest.RestResponse;
24+
import org.elasticsearch.rest.Scope;
25+
import org.elasticsearch.rest.ServerlessScope;
26+
import org.elasticsearch.rest.action.RestResponseListener;
27+
28+
import java.util.List;
29+
import java.util.Set;
30+
31+
import static org.elasticsearch.common.util.set.Sets.addToCopy;
32+
import static org.elasticsearch.rest.RestRequest.Method.GET;
33+
34+
@ServerlessScope(Scope.INTERNAL)
35+
public class RestCatCircuitBreakerAction extends AbstractCatAction {
36+
37+
private static final Set<String> RESPONSE_PARAMS = addToCopy(AbstractCatAction.RESPONSE_PARAMS, "circuit_breaker_patterns");
38+
39+
@Override
40+
public String getName() {
41+
return "cat_circuitbreaker_action";
42+
}
43+
44+
@Override
45+
public List<Route> routes() {
46+
return List.of(new Route(GET, "/_cat/circuit_breaker"), new Route(GET, "/_cat/circuit_breaker/{circuit_breaker_patterns}"));
47+
}
48+
49+
@Override
50+
protected void documentation(StringBuilder sb) {
51+
sb.append("/_cat/circuit_breaker\n");
52+
sb.append("/_cat/circuit_breaker/{circuit_breaker_patterns}\n");
53+
}
54+
55+
@Override
56+
protected Set<String> responseParams() {
57+
return RESPONSE_PARAMS;
58+
}
59+
60+
@Override
61+
protected RestChannelConsumer doCatRequest(RestRequest request, NodeClient client) {
62+
NodesStatsRequest nodesStatsRequest = new NodesStatsRequest(); // Empty nodes array sends request to all nodes.
63+
nodesStatsRequest.clear().addMetric(NodesStatsRequestParameters.Metric.BREAKER);
64+
65+
return channel -> client.admin().cluster().nodesStats(nodesStatsRequest, new RestResponseListener<>(channel) {
66+
@Override
67+
public RestResponse buildResponse(final NodesStatsResponse nodesStatsResponse) throws Exception {
68+
RestResponse response = RestTable.buildResponse(buildTable(request, nodesStatsResponse), channel);
69+
if (nodesStatsResponse.failures().isEmpty() == false) {
70+
response.addHeader("Warning", "Partial success, missing info from " + nodesStatsResponse.failures().size() + " nodes.");
71+
}
72+
return response;
73+
}
74+
});
75+
}
76+
77+
@Override
78+
protected Table getTableWithHeader(RestRequest request) {
79+
Table table = new Table();
80+
table.startHeaders();
81+
table.addCell("node_id", "default:true;alias:id;desc:persistent node id");
82+
table.addCell("node_name", "default:false;alias:nn;desc:node name");
83+
table.addCell("breaker", "default:true;alias:br;desc:breaker name");
84+
table.addCell("limit", "default:true;alias:l;desc:limit size");
85+
table.addCell("limit_bytes", "default:false;alias:lb;desc:limit size in bytes");
86+
table.addCell("estimated", "default:true;alias:e;desc:estimated size");
87+
table.addCell("estimated_bytes", "default:false;alias:eb;desc:estimated size in bytes");
88+
table.addCell("tripped", "default:true;alias:t;desc:tripped count");
89+
table.addCell("overhead", "default:false;alias:o;desc:overhead");
90+
table.endHeaders();
91+
return table;
92+
}
93+
94+
private Table buildTable(RestRequest request, NodesStatsResponse nodesStatsResponse) {
95+
final Table table = getTableWithHeader(request);
96+
final String[] circuitBreakers = request.paramAsStringArray("circuit_breaker_patterns", new String[] { "*" });
97+
98+
for (final NodeStats nodeStats : nodesStatsResponse.getNodes()) {
99+
if (nodeStats.getBreaker() == null) {
100+
continue;
101+
}
102+
for (final CircuitBreakerStats circuitBreakerStats : nodeStats.getBreaker().getAllStats()) {
103+
if (Regex.simpleMatch(circuitBreakers, circuitBreakerStats.getName()) == false) {
104+
continue;
105+
}
106+
table.startRow();
107+
table.addCell(nodeStats.getNode().getId());
108+
table.addCell(nodeStats.getNode().getName());
109+
table.addCell(circuitBreakerStats.getName());
110+
table.addCell(ByteSizeValue.ofBytes(circuitBreakerStats.getLimit()));
111+
table.addCell(circuitBreakerStats.getLimit());
112+
table.addCell(ByteSizeValue.ofBytes(circuitBreakerStats.getEstimated()));
113+
table.addCell(circuitBreakerStats.getEstimated());
114+
table.addCell(circuitBreakerStats.getTrippedCount());
115+
table.addCell(circuitBreakerStats.getOverhead());
116+
table.endRow();
117+
}
118+
}
119+
120+
for (final FailedNodeException errors : nodesStatsResponse.failures()) {
121+
table.startRow();
122+
table.addCell(errors.nodeId());
123+
table.addCell("N/A");
124+
table.addCell(errors.getMessage());
125+
table.addCell("N/A");
126+
table.addCell("N/A");
127+
table.addCell("N/A");
128+
table.addCell("N/A");
129+
table.addCell("N/A");
130+
table.addCell("N/A");
131+
table.endRow();
132+
}
133+
134+
return table;
135+
}
136+
}

0 commit comments

Comments
 (0)