-
Notifications
You must be signed in to change notification settings - Fork 94
[Feature branch] Add Neural Stats API #1208
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 19 commits
19480ae
a1f24f0
064465b
e4abeca
9274a71
e1cb163
282db07
7d08433
df81f11
4677199
8cc6698
24760a5
c15bcff
a8c3d0a
9425021
7ba9dcb
fe8a616
56c2d19
d540f28
3da5765
64032f3
f6fc682
c56a110
73e2228
0734a6a
7b532a5
2e0c119
82504ce
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,19 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package org.opensearch.neuralsearch.processor.util; | ||
|
||
import org.opensearch.rest.RestRequest; | ||
|
||
import java.util.Optional; | ||
|
||
public class RestActionUtils { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public static Optional<String[]> splitCommaSeparatedParam(RestRequest request, String paramName) { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Optional.ofNullable(request.param(paramName)).map(s -> s.split(",")); | ||
} | ||
|
||
public static Optional<String> getStringParam(RestRequest request, String paramName) { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Optional.ofNullable(request.param(paramName)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package org.opensearch.neuralsearch.rest; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import lombok.AllArgsConstructor; | ||
import lombok.extern.log4j.Log4j2; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.opensearch.core.rest.RestStatus; | ||
import org.opensearch.neuralsearch.settings.NeuralSearchSettingsAccessor; | ||
import org.opensearch.neuralsearch.stats.NeuralStatsInput; | ||
import org.opensearch.neuralsearch.stats.events.EventStatName; | ||
import org.opensearch.neuralsearch.stats.state.StateStatName; | ||
import org.opensearch.neuralsearch.transport.NeuralStatsAction; | ||
import org.opensearch.neuralsearch.transport.NeuralStatsRequest; | ||
import org.opensearch.rest.BaseRestHandler; | ||
import org.opensearch.rest.BytesRestResponse; | ||
import org.opensearch.rest.RestRequest; | ||
import org.opensearch.rest.action.RestActions; | ||
import org.opensearch.transport.client.node.NodeClient; | ||
|
||
import java.util.Arrays; | ||
import java.util.EnumSet; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import static org.opensearch.neuralsearch.plugin.NeuralSearch.NEURAL_BASE_URI; | ||
import static org.opensearch.neuralsearch.processor.util.RestActionUtils.splitCommaSeparatedParam; | ||
|
||
@Log4j2 | ||
@AllArgsConstructor | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public class RestNeuralStatsHandler extends BaseRestHandler { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static final String NAME = "neural_stats_action"; | ||
public static final String FLATTEN_PARAM = "flat_keys"; | ||
public static final String INCLUDE_METADATA_PARAM = "include_metadata"; | ||
|
||
private static final Set<String> EVENT_STAT_NAMES = EnumSet.allOf(EventStatName.class) | ||
.stream() | ||
.map(EventStatName::getNameString) | ||
.map(String::toLowerCase) | ||
.collect(Collectors.toSet()); | ||
|
||
private static final Set<String> STATE_STAT_NAMES = EnumSet.allOf(StateStatName.class) | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.stream() | ||
.map(StateStatName::getNameString) | ||
.map(String::toLowerCase) | ||
.collect(Collectors.toSet()); | ||
|
||
private NeuralSearchSettingsAccessor settingsAccessor; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this declaration to top There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't static fields typically go before instance fields? |
||
|
||
@Override | ||
public String getName() { | ||
return NAME; | ||
} | ||
|
||
@Override | ||
public List<Route> routes() { | ||
return ImmutableList.of( | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
new Route(RestRequest.Method.GET, NEURAL_BASE_URI + "/{nodeId}/stats/"), | ||
new Route(RestRequest.Method.GET, NEURAL_BASE_URI + "/{nodeId}/stats/{stat}"), | ||
new Route(RestRequest.Method.GET, NEURAL_BASE_URI + "/stats/"), | ||
new Route(RestRequest.Method.GET, NEURAL_BASE_URI + "/stats/{stat}") | ||
); | ||
} | ||
|
||
@Override | ||
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { | ||
if (settingsAccessor.getIsStatsEnabled() == false) { | ||
// Process params, or else will automatically return a 400 instead of a 403 | ||
splitCommaSeparatedParam(request, "nodeId"); | ||
splitCommaSeparatedParam(request, "stat"); | ||
request.paramAsBoolean(FLATTEN_PARAM, false); | ||
request.paramAsBoolean(INCLUDE_METADATA_PARAM, false); | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "Stats endpoint is disabled")); | ||
} | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Read inputs and convert to BaseNodesRequest with correct info configured | ||
NeuralStatsRequest neuralStatsRequest = getRequest(request); | ||
|
||
return channel -> client.execute( | ||
NeuralStatsAction.INSTANCE, | ||
neuralStatsRequest, | ||
new RestActions.NodesResponseRestListener<>(channel) | ||
); | ||
} | ||
|
||
/** | ||
* Creates a NeuralStatsRequest from a RestRequest | ||
* | ||
* @param request Rest request | ||
* @return NeuralStatsRequest | ||
*/ | ||
private NeuralStatsRequest getRequest(RestRequest request) { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// parse the nodes the user wants to query | ||
String[] nodeIdsArr = null; | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
String nodesIdsStr = request.param("nodeId"); | ||
if (StringUtils.isNotEmpty(nodesIdsStr)) { | ||
nodeIdsArr = nodesIdsStr.split(","); | ||
} | ||
|
||
NeuralStatsInput neuralStatsInput = createNeuralStatsInputFromRequestParams(request); | ||
|
||
NeuralStatsRequest neuralStatsRequest = new NeuralStatsRequest(nodeIdsArr, neuralStatsInput); | ||
neuralStatsRequest.timeout(request.param("timeout")); | ||
|
||
return neuralStatsRequest; | ||
} | ||
|
||
NeuralStatsInput createNeuralStatsInputFromRequestParams(RestRequest request) { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
NeuralStatsInput neuralStatsInput = new NeuralStatsInput(); | ||
|
||
// Parse specified nodes | ||
Optional<String[]> nodeIds = splitCommaSeparatedParam(request, "nodeId"); | ||
if (nodeIds.isPresent()) { | ||
neuralStatsInput.getNodeIds().addAll(Arrays.asList(nodeIds.get())); | ||
} | ||
|
||
// Parse query parameters | ||
boolean flatten = request.paramAsBoolean(FLATTEN_PARAM, false); | ||
neuralStatsInput.setFlatten(flatten); | ||
|
||
boolean includeMetadata = request.paramAsBoolean(INCLUDE_METADATA_PARAM, false); | ||
neuralStatsInput.setIncludeMetadata(includeMetadata); | ||
|
||
// Determine which stat names to retrieve based on user parameters | ||
Optional<String[]> stats = splitCommaSeparatedParam(request, "stat"); | ||
boolean retrieveAllStats = true; | ||
|
||
// Add stats to input to retrieve if specified | ||
if (stats.isPresent()) { | ||
for (String stat : stats.get()) { | ||
stat = stat.toLowerCase(Locale.ROOT); | ||
|
||
if (EVENT_STAT_NAMES.contains(stat)) { | ||
retrieveAllStats = false; | ||
neuralStatsInput.getEventStatNames().add(EventStatName.from(stat)); | ||
} else if (STATE_STAT_NAMES.contains(stat)) { | ||
retrieveAllStats = false; | ||
neuralStatsInput.getStateStatNames().add(StateStatName.from(stat)); | ||
} | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
// If no stats are specified, add all stats to retrieve all by default | ||
if (retrieveAllStats) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if stats is specified, if there is not matching stats, do we want to return all stats instead of empty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, currently that's how other stats APIs work, they return all stats if no stats match. I think the reasoning is that invalid stat names are ignored, and when all are ignored then it's treated as a get all. |
||
neuralStatsInput.getEventStatNames().addAll(EnumSet.allOf(EventStatName.class)); | ||
neuralStatsInput.getStateStatNames().addAll(EnumSet.allOf(StateStatName.class)); | ||
} | ||
return neuralStatsInput; | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package org.opensearch.neuralsearch.settings; | ||
|
||
import lombok.Getter; | ||
import org.opensearch.cluster.service.ClusterService; | ||
import org.opensearch.common.settings.Settings; | ||
import org.opensearch.neuralsearch.stats.events.EventStatsManager; | ||
|
||
public class NeuralSearchSettingsAccessor { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static NeuralSearchSettingsAccessor INSTANCE; | ||
private boolean initialized; | ||
|
||
@Getter | ||
private volatile Boolean isStatsEnabled; | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Return instance of the cluster context, must be initialized first for proper usage | ||
* @return instance of cluster context | ||
*/ | ||
public static synchronized NeuralSearchSettingsAccessor instance() { | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (INSTANCE == null) { | ||
INSTANCE = new NeuralSearchSettingsAccessor(); | ||
} | ||
return INSTANCE; | ||
} | ||
|
||
public void initialize(ClusterService clusterService, Settings settings) { | ||
if (initialized) return; | ||
q-andy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
isStatsEnabled = NeuralSearchSettings.NEURAL_STATS_ENABLED.get(settings); | ||
|
||
clusterService.getClusterSettings().addSettingsUpdateConsumer(NeuralSearchSettings.NEURAL_STATS_ENABLED, value -> { | ||
// If stats are being toggled off, clear and reset all stats | ||
if (isStatsEnabled && (value == false)) { | ||
EventStatsManager.instance().reset(); | ||
} | ||
isStatsEnabled = value; | ||
}); | ||
} | ||
|
||
} |
Uh oh!
There was an error while loading. Please reload this page.