-
Notifications
You must be signed in to change notification settings - Fork 25.7k
cat API: added endpoint for Circuit Breakers #136890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
7bfe80a
57b8e42
1d6853a
b49d08d
662e3cb
7d0db66
1468d4d
5cd3b8e
b658493
ff13743
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 136890 | ||
| summary: "Cat API: added endpoint for Circuit Breakers" | ||
| area: Infra/REST API | ||
| type: enhancement | ||
| issues: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| { | ||
| "cat.circuit_breaker": { | ||
| "documentation": { | ||
| "url": "https://www.elastic.co/docs/api/doc/elasticsearch#TODO", | ||
| "description": "Get circuit breakers statistics" | ||
| }, | ||
| "stability": "stable", | ||
| "visibility": "public", | ||
| "headers": { | ||
| "accept": [ | ||
| "text/plain", | ||
| "application/json" | ||
| ] | ||
| }, | ||
| "url": { | ||
| "paths": [ | ||
| { | ||
| "path": "/_cat/circuit_breaker", | ||
| "methods": [ | ||
| "GET" | ||
| ] | ||
| }, | ||
| { | ||
| "path": "/_cat/circuit_breaker/{circuit_breaker_patterns}", | ||
| "methods": [ | ||
| "GET" | ||
| ], | ||
| "parts": { | ||
| "circuit_breaker_patterns": { | ||
| "type": "list", | ||
| "description": "A comma-separated list of regular-expressions to filter the circuit breakers in the output" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| "params": { | ||
| "format": { | ||
| "type": "string", | ||
| "default": "text", | ||
| "description": "a short version of the Accept header, e.g. json, yaml" | ||
| }, | ||
| "time": { | ||
| "type": "enum", | ||
| "description": "The unit in which to display time values", | ||
| "options": [ | ||
| "d", | ||
| "h", | ||
| "m", | ||
| "s", | ||
| "ms", | ||
| "micros", | ||
| "nanos" | ||
| ] | ||
| }, | ||
| "local": { | ||
| "type": "boolean", | ||
| "default": false, | ||
| "description": "Return local information, do not retrieve the state from master node (default: false)" | ||
| }, | ||
| "master_timeout": { | ||
| "type": "time", | ||
| "default": "30s", | ||
| "description": "Explicit operation timeout for connection to master node" | ||
| }, | ||
| "h": { | ||
| "type": "list", | ||
| "description": "Comma-separated list of column names to display" | ||
| }, | ||
| "help": { | ||
| "type": "boolean", | ||
| "description": "Return help information", | ||
| "default": false | ||
| }, | ||
| "s": { | ||
| "type": "list", | ||
| "description": "Comma-separated list of column names or column aliases to sort by" | ||
| }, | ||
| "v": { | ||
| "type": "boolean", | ||
| "description": "Verbose mode. Display column headers", | ||
| "default": false | ||
| }, | ||
| "bytes": { | ||
| "type": "enum", | ||
| "description": "The unit in which to display byte values", | ||
| "options": [ | ||
| "b", | ||
| "kb", | ||
| "mb", | ||
| "gb", | ||
| "tb", | ||
| "pb" | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| --- | ||
| "Test cat circuit_breaker help": | ||
| - requires: | ||
| capabilities: | ||
| - method: GET | ||
| path: /_cat/circuit_breaker | ||
| capabilities: [ cat_circuit_breaker ] | ||
| test_runner_features: capabilities | ||
| reason: Capability required to run test | ||
| - do: | ||
| cat.circuit_breaker: | ||
| help: true | ||
|
|
||
| - match: | ||
| $body: | | ||
| /^ node_id .+ \n | ||
| node_name .+ \n | ||
| breaker .+ \n | ||
| limit .+ \n | ||
| limit_bytes .+ \n | ||
| estimated .+ \n | ||
| estimated_bytes .+ \n | ||
| tripped .+ \n | ||
| overhead .+ \n $/ | ||
|
|
||
| --- | ||
| "Test cat circuit_breaker output": | ||
| - requires: | ||
| capabilities: | ||
| - method: GET | ||
| path: /_cat/circuit_breaker | ||
| capabilities: [ cat_circuit_breaker ] | ||
| test_runner_features: capabilities | ||
| reason: Capability required to run test | ||
| - do: | ||
| cat.circuit_breaker: {} | ||
|
|
||
| - match: | ||
| $body: | | ||
| / #node_id breaker \s+ limit \s+ estimated \s+ tripped \n | ||
| ^ (\S+ \s+ \S+ \s+ \S+ \s+ \S+ \s+ \d+ \n)+ $/ | ||
|
|
||
| - do: | ||
| cat.circuit_breaker: | ||
| v: true | ||
|
|
||
| - match: | ||
| $body: | | ||
| /^ node_id \s+ breaker \s+ limit \s+ estimated \s+ tripped \n | ||
| (\S+ \s+ \S+ \s+ \S+ \s+ \S+ \s+ \d+ \n)+ $/ | ||
|
|
||
| - do: | ||
| cat.circuit_breaker: | ||
| circuit_breaker_patterns: request,fielddata | ||
| h: node_id,breaker,limit,estimated,tripped | ||
| v: true | ||
|
|
||
| - match: | ||
| $body: | | ||
| /^ node_id \s+ breaker \s+ limit \s+ estimated \s+ tripped \n | ||
| (\S+ \s+ (request|fielddata) \s+ \S+ \s+ \S+ \s+ \d+ \n){2,} $/ | ||
|
|
||
| - do: | ||
| cat.circuit_breaker: | ||
| circuit_breaker_patterns: request | ||
| h: node_id,breaker,limit,limit_bytes,estimated,estimated_bytes,tripped,overhead | ||
| v: true | ||
|
|
||
| - match: | ||
| $body: | | ||
| /^ node_id \s+ breaker \s+ limit \s+ limit_bytes \s+ estimated \s+ estimated_bytes \s+ tripped \s+ overhead \n | ||
| (\S+ \s+ request \s+ \S+ \s+ \d+ \s+ \S+ \s+ \d+ \s+ \d+ \s+ \d+\.\d+ \n)+ $/ | ||
|
|
||
| - do: | ||
| cat.circuit_breaker: | ||
| circuit_breaker_patterns: "*" | ||
| h: node_id,breaker,tripped | ||
|
|
||
| - match: | ||
| $body: | | ||
| / #node_id breaker \s+ tripped \n | ||
| ^ (\S+ \s+ \S+ \s+ \d+ \n)+ $/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| package org.elasticsearch.rest.action.cat; | ||
|
|
||
| import org.elasticsearch.action.FailedNodeException; | ||
| 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.NodesStatsRequestParameters; | ||
| import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; | ||
| import org.elasticsearch.client.internal.node.NodeClient; | ||
| import org.elasticsearch.common.Table; | ||
| import org.elasticsearch.common.regex.Regex; | ||
| import org.elasticsearch.common.unit.ByteSizeValue; | ||
| import org.elasticsearch.common.util.set.Sets; | ||
| import org.elasticsearch.indices.breaker.CircuitBreakerStats; | ||
| import org.elasticsearch.rest.RestRequest; | ||
| import org.elasticsearch.rest.RestResponse; | ||
| import org.elasticsearch.rest.Scope; | ||
| import org.elasticsearch.rest.ServerlessScope; | ||
| import org.elasticsearch.rest.action.RestResponseListener; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| import static org.elasticsearch.common.util.set.Sets.addToCopy; | ||
| import static org.elasticsearch.rest.RestRequest.Method.GET; | ||
|
|
||
| @ServerlessScope(Scope.INTERNAL) | ||
| public class RestCatCircuitBreakerAction extends AbstractCatAction { | ||
|
|
||
| private static final Set<String> RESPONSE_PARAMS = addToCopy(AbstractCatAction.RESPONSE_PARAMS, "circuit_breaker_patterns"); | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return "cat_circuitbreaker_action"; | ||
| } | ||
|
|
||
| @Override | ||
| public List<Route> routes() { | ||
| return List.of(new Route(GET, "/_cat/circuit_breaker"), new Route(GET, "/_cat/circuit_breaker/{circuit_breaker_patterns}")); | ||
| } | ||
|
|
||
| @Override | ||
| protected void documentation(StringBuilder sb) { | ||
| sb.append("/_cat/circuit_breaker\n"); | ||
| sb.append("/_cat/circuit_breaker/{circuit_breaker_patterns}\n"); | ||
| } | ||
|
|
||
| @Override | ||
| protected Set<String> responseParams() { | ||
| return RESPONSE_PARAMS; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> supportedCapabilities() { | ||
| return Sets.union(Set.of("cat_circuit_breaker"), super.supportedCapabilities()); | ||
|
||
| } | ||
|
|
||
| @Override | ||
| protected RestChannelConsumer doCatRequest(RestRequest request, NodeClient client) { | ||
| NodesStatsRequest nodesStatsRequest = new NodesStatsRequest(); // Empty nodes array sends request to all nodes. | ||
| nodesStatsRequest.clear().addMetric(NodesStatsRequestParameters.Metric.BREAKER); | ||
|
|
||
| return channel -> client.admin().cluster().nodesStats(nodesStatsRequest, new RestResponseListener<>(channel) { | ||
| @Override | ||
| public RestResponse buildResponse(final NodesStatsResponse nodesStatsResponse) throws Exception { | ||
| RestResponse response = RestTable.buildResponse(buildTable(request, nodesStatsResponse), channel); | ||
| if (nodesStatsResponse.failures().isEmpty() == false) { | ||
| response.addHeader("Warning", "Partial success, missing info from " + nodesStatsResponse.failures().size() + " nodes."); | ||
| } | ||
| return response; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| protected Table getTableWithHeader(RestRequest request) { | ||
| Table table = new Table(); | ||
| table.startHeaders(); | ||
| table.addCell("node_id", "default:true;alias:id;desc:persistent node id"); | ||
| table.addCell("node_name", "default:false;alias:nn;desc:node name"); | ||
| table.addCell("breaker", "default:true;alias:br;desc:breaker name"); | ||
| table.addCell("limit", "default:true;alias:l;desc:limit size"); | ||
| table.addCell("limit_bytes", "default:false;alias:lb;desc:limit size in bytes"); | ||
| table.addCell("estimated", "default:true;alias:e;desc:estimated size"); | ||
| table.addCell("estimated_bytes", "default:false;alias:eb;desc:estimated size in bytes"); | ||
| table.addCell("tripped", "default:true;alias:t;desc:tripped count"); | ||
| table.addCell("overhead", "default:false;alias:o;desc:overhead"); | ||
| table.endHeaders(); | ||
| return table; | ||
| } | ||
|
|
||
| private Table buildTable(RestRequest request, NodesStatsResponse nodesStatsResponse) { | ||
| final Table table = getTableWithHeader(request); | ||
| final String[] circuitBreakers = request.paramAsStringArray("circuit_breaker_patterns", new String[] { "*" }); | ||
|
|
||
| for (final NodeStats nodeStats : nodesStatsResponse.getNodes()) { | ||
| if (nodeStats.getBreaker() == null) { | ||
| continue; | ||
| } | ||
| for (final CircuitBreakerStats circuitBreakerStats : nodeStats.getBreaker().getAllStats()) { | ||
| if (Regex.simpleMatch(circuitBreakers, circuitBreakerStats.getName()) == false) { | ||
| continue; | ||
| } | ||
| table.startRow(); | ||
| table.addCell(nodeStats.getNode().getId()); | ||
| table.addCell(nodeStats.getNode().getName()); | ||
| table.addCell(circuitBreakerStats.getName()); | ||
| table.addCell(ByteSizeValue.ofBytes(circuitBreakerStats.getLimit())); | ||
| table.addCell(circuitBreakerStats.getLimit()); | ||
| table.addCell(ByteSizeValue.ofBytes(circuitBreakerStats.getEstimated())); | ||
| table.addCell(circuitBreakerStats.getEstimated()); | ||
| table.addCell(circuitBreakerStats.getTrippedCount()); | ||
| table.addCell(circuitBreakerStats.getOverhead()); | ||
| table.endRow(); | ||
| } | ||
| } | ||
|
|
||
| for (final FailedNodeException errors : nodesStatsResponse.failures()) { | ||
| table.startRow(); | ||
| table.addCell(errors.nodeId()); | ||
| table.addCell("N/A"); | ||
| table.addCell(errors.getMessage()); | ||
| table.addCell("N/A"); | ||
| table.addCell("N/A"); | ||
| table.addCell("N/A"); | ||
| table.addCell("N/A"); | ||
| table.addCell("N/A"); | ||
| table.addCell("N/A"); | ||
| table.endRow(); | ||
| } | ||
|
|
||
| return table; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, defining and checking for a capability shouldn't be needed here. Existence should do