use of org.opensearch.ad.util.MultiResponsesDelegateActionListener in project anomaly-detection by opensearch-project.
the class StatsAnomalyDetectorTransportAction method getClusterStats.
/**
* Make async request to get the number of detectors in AnomalyDetector.ANOMALY_DETECTORS_INDEX if necessary
* and, onResponse, gather the cluster statistics
*
* @param client Client
* @param listener MultiResponsesDelegateActionListener to be used once both requests complete
* @param adStatsRequest Request containing stats to be retrieved
*/
private void getClusterStats(Client client, MultiResponsesDelegateActionListener<ADStatsResponse> listener, ADStatsRequest adStatsRequest) {
ADStatsResponse adStatsResponse = new ADStatsResponse();
if ((adStatsRequest.getStatsToBeRetrieved().contains(StatNames.DETECTOR_COUNT.getName()) || adStatsRequest.getStatsToBeRetrieved().contains(StatNames.SINGLE_ENTITY_DETECTOR_COUNT.getName()) || adStatsRequest.getStatsToBeRetrieved().contains(StatNames.MULTI_ENTITY_DETECTOR_COUNT.getName())) && clusterService.state().getRoutingTable().hasIndex(AnomalyDetector.ANOMALY_DETECTORS_INDEX)) {
TermsAggregationBuilder termsAgg = AggregationBuilders.terms(DETECTOR_TYPE_AGG).field(AnomalyDetector.DETECTOR_TYPE_FIELD);
SearchRequest request = new SearchRequest().indices(AnomalyDetector.ANOMALY_DETECTORS_INDEX).source(new SearchSourceBuilder().aggregation(termsAgg).size(0).trackTotalHits(true));
client.search(request, ActionListener.wrap(r -> {
StringTerms aggregation = r.getAggregations().get(DETECTOR_TYPE_AGG);
List<StringTerms.Bucket> buckets = aggregation.getBuckets();
long totalDetectors = r.getHits().getTotalHits().value;
long totalSingleEntityDetectors = 0;
long totalMultiEntityDetectors = 0;
for (StringTerms.Bucket b : buckets) {
if (AnomalyDetectorType.SINGLE_ENTITY.name().equals(b.getKeyAsString()) || AnomalyDetectorType.REALTIME_SINGLE_ENTITY.name().equals(b.getKeyAsString()) || AnomalyDetectorType.HISTORICAL_SINGLE_ENTITY.name().equals(b.getKeyAsString())) {
totalSingleEntityDetectors += b.getDocCount();
}
if (AnomalyDetectorType.MULTI_ENTITY.name().equals(b.getKeyAsString()) || AnomalyDetectorType.REALTIME_MULTI_ENTITY.name().equals(b.getKeyAsString()) || AnomalyDetectorType.HISTORICAL_MULTI_ENTITY.name().equals(b.getKeyAsString())) {
totalMultiEntityDetectors += b.getDocCount();
}
}
if (adStatsRequest.getStatsToBeRetrieved().contains(StatNames.DETECTOR_COUNT.getName())) {
adStats.getStat(StatNames.DETECTOR_COUNT.getName()).setValue(totalDetectors);
}
if (adStatsRequest.getStatsToBeRetrieved().contains(StatNames.SINGLE_ENTITY_DETECTOR_COUNT.getName())) {
adStats.getStat(StatNames.SINGLE_ENTITY_DETECTOR_COUNT.getName()).setValue(totalSingleEntityDetectors);
}
if (adStatsRequest.getStatsToBeRetrieved().contains(StatNames.MULTI_ENTITY_DETECTOR_COUNT.getName())) {
adStats.getStat(StatNames.MULTI_ENTITY_DETECTOR_COUNT.getName()).setValue(totalMultiEntityDetectors);
}
adStatsResponse.setClusterStats(getClusterStatsMap(adStatsRequest));
listener.onResponse(adStatsResponse);
}, e -> listener.onFailure(e)));
} else {
adStatsResponse.setClusterStats(getClusterStatsMap(adStatsRequest));
listener.onResponse(adStatsResponse);
}
}
use of org.opensearch.ad.util.MultiResponsesDelegateActionListener in project anomaly-detection by opensearch-project.
the class AbstractAnomalyDetectorActionHandler method validateAnomalyDetectorFeatures.
/**
* Validate config/syntax, and runtime error of detector features
* @param detectorId detector id
* @param indexingDryRun if false, then will eventually index detector; true, skip indexing detector
* @throws IOException when fail to parse feature aggregation
*/
// TODO: move this method to util class so that it can be re-usable for more use cases
// https://github.com/opensearch-project/anomaly-detection/issues/39
protected void validateAnomalyDetectorFeatures(String detectorId, boolean indexingDryRun) throws IOException {
if (anomalyDetector != null && (anomalyDetector.getFeatureAttributes() == null || anomalyDetector.getFeatureAttributes().isEmpty())) {
checkADNameExists(detectorId, indexingDryRun);
return;
}
// checking configuration/syntax error of detector features
String error = RestHandlerUtils.checkAnomalyDetectorFeaturesSyntax(anomalyDetector, maxAnomalyFeatures);
if (StringUtils.isNotBlank(error)) {
if (indexingDryRun) {
listener.onFailure(new ADValidationException(error, DetectorValidationIssueType.FEATURE_ATTRIBUTES, ValidationAspect.DETECTOR));
return;
}
listener.onFailure(new OpenSearchStatusException(error, RestStatus.BAD_REQUEST));
return;
}
// checking runtime error from feature query
ActionListener<MergeableList<Optional<double[]>>> validateFeatureQueriesListener = ActionListener.wrap(response -> {
checkADNameExists(detectorId, indexingDryRun);
}, exception -> {
listener.onFailure(new ADValidationException(exception.getMessage(), DetectorValidationIssueType.FEATURE_ATTRIBUTES, ValidationAspect.DETECTOR));
});
MultiResponsesDelegateActionListener<MergeableList<Optional<double[]>>> multiFeatureQueriesResponseListener = new MultiResponsesDelegateActionListener<MergeableList<Optional<double[]>>>(validateFeatureQueriesListener, anomalyDetector.getFeatureAttributes().size(), String.format(Locale.ROOT, CommonErrorMessages.VALIDATION_FEATURE_FAILURE, anomalyDetector.getName()), false);
for (Feature feature : anomalyDetector.getFeatureAttributes()) {
SearchSourceBuilder ssb = new SearchSourceBuilder().size(1).query(QueryBuilders.matchAllQuery());
AggregatorFactories.Builder internalAgg = parseAggregators(feature.getAggregation().toString(), xContentRegistry, feature.getId());
ssb.aggregation(internalAgg.getAggregatorFactories().iterator().next());
SearchRequest searchRequest = new SearchRequest().indices(anomalyDetector.getIndices().toArray(new String[0])).source(ssb);
client.search(searchRequest, ActionListener.wrap(response -> {
Optional<double[]> aggFeatureResult = searchFeatureDao.parseResponse(response, Arrays.asList(feature.getId()));
if (aggFeatureResult.isPresent()) {
multiFeatureQueriesResponseListener.onResponse(new MergeableList<Optional<double[]>>(new ArrayList<Optional<double[]>>(Arrays.asList(aggFeatureResult))));
} else {
String errorMessage = CommonErrorMessages.FEATURE_WITH_EMPTY_DATA_MSG + feature.getName();
logger.error(errorMessage);
multiFeatureQueriesResponseListener.onFailure(new OpenSearchStatusException(errorMessage, RestStatus.BAD_REQUEST));
}
}, e -> {
String errorMessage;
if (isExceptionCausedByInvalidQuery(e)) {
errorMessage = CommonErrorMessages.FEATURE_WITH_INVALID_QUERY_MSG + feature.getName();
} else {
errorMessage = CommonErrorMessages.UNKNOWN_SEARCH_QUERY_EXCEPTION_MSG + feature.getName();
}
logger.error(errorMessage, e);
multiFeatureQueriesResponseListener.onFailure(new OpenSearchStatusException(errorMessage, RestStatus.BAD_REQUEST, e));
}));
}
}
use of org.opensearch.ad.util.MultiResponsesDelegateActionListener in project anomaly-detection by opensearch-project.
the class ModelValidationActionHandler method checkFeatureQueryDelegate.
private void checkFeatureQueryDelegate(long latestTime) throws IOException {
ActionListener<MergeableList<double[]>> validateFeatureQueriesListener = ActionListener.wrap(response -> {
windowDelayRecommendation(latestTime);
}, exception -> {
listener.onFailure(new ADValidationException(exception.getMessage(), DetectorValidationIssueType.FEATURE_ATTRIBUTES, ValidationAspect.MODEL));
});
MultiResponsesDelegateActionListener<MergeableList<double[]>> multiFeatureQueriesResponseListener = new MultiResponsesDelegateActionListener<>(validateFeatureQueriesListener, anomalyDetector.getFeatureAttributes().size(), CommonErrorMessages.FEATURE_QUERY_TOO_SPARSE, false);
for (Feature feature : anomalyDetector.getFeatureAttributes()) {
AggregationBuilder aggregation = getBucketAggregation(latestTime, (IntervalTimeConfiguration) anomalyDetector.getDetectionInterval());
BoolQueryBuilder query = QueryBuilders.boolQuery().filter(anomalyDetector.getFilterQuery());
List<String> featureFields = ParseUtils.getFieldNamesForFeature(feature, xContentRegistry);
for (String featureField : featureFields) {
query.filter(QueryBuilders.existsQuery(featureField));
}
SearchSourceBuilder searchSourceBuilder = getSearchSourceBuilder(query, aggregation);
SearchRequest searchRequest = new SearchRequest(anomalyDetector.getIndices().toArray(new String[0])).source(searchSourceBuilder);
client.search(searchRequest, ActionListener.wrap(response -> {
Histogram aggregate = checkBucketResultErrors(response);
if (aggregate == null) {
return;
}
double fullBucketRate = processBucketAggregationResults(aggregate);
if (fullBucketRate < CONFIG_BUCKET_MINIMUM_SUCCESS_RATE) {
multiFeatureQueriesResponseListener.onFailure(new ADValidationException(CommonErrorMessages.FEATURE_QUERY_TOO_SPARSE, DetectorValidationIssueType.FEATURE_ATTRIBUTES, ValidationAspect.MODEL));
} else {
multiFeatureQueriesResponseListener.onResponse(new MergeableList<>(new ArrayList<>(Collections.singletonList(new double[] { fullBucketRate }))));
}
}, e -> {
logger.error(e);
multiFeatureQueriesResponseListener.onFailure(new OpenSearchStatusException(CommonErrorMessages.FEATURE_QUERY_TOO_SPARSE, RestStatus.BAD_REQUEST, e));
}));
}
}
use of org.opensearch.ad.util.MultiResponsesDelegateActionListener in project anomaly-detection by opensearch-project.
the class AnomalyDetectorProfileRunner method profileEntityStats.
private void profileEntityStats(MultiResponsesDelegateActionListener<DetectorProfile> listener, AnomalyDetector detector) {
List<String> categoryField = detector.getCategoryField();
if (!detector.isMultientityDetector() || categoryField.size() > NumericSetting.maxCategoricalFields()) {
listener.onResponse(new DetectorProfile.Builder().build());
} else {
if (categoryField.size() == 1) {
// Run a cardinality aggregation to count the cardinality of single category fields
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
CardinalityAggregationBuilder aggBuilder = new CardinalityAggregationBuilder(CommonName.TOTAL_ENTITIES);
aggBuilder.field(categoryField.get(0));
searchSourceBuilder.aggregation(aggBuilder);
SearchRequest request = new SearchRequest(detector.getIndices().toArray(new String[0]), searchSourceBuilder);
client.search(request, ActionListener.wrap(searchResponse -> {
Map<String, Aggregation> aggMap = searchResponse.getAggregations().asMap();
InternalCardinality totalEntities = (InternalCardinality) aggMap.get(CommonName.TOTAL_ENTITIES);
long value = totalEntities.getValue();
DetectorProfile.Builder profileBuilder = new DetectorProfile.Builder();
DetectorProfile profile = profileBuilder.totalEntities(value).build();
listener.onResponse(profile);
}, searchException -> {
logger.warn(CommonErrorMessages.FAIL_TO_GET_TOTAL_ENTITIES + detector.getDetectorId());
listener.onFailure(searchException);
}));
} else {
// Run a composite query and count the number of buckets to decide cardinality of multiple category fields
AggregationBuilder bucketAggs = AggregationBuilders.composite(CommonName.TOTAL_ENTITIES, detector.getCategoryField().stream().map(f -> new TermsValuesSourceBuilder(f).field(f)).collect(Collectors.toList())).size(maxTotalEntitiesToTrack);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().aggregation(bucketAggs).trackTotalHits(false).size(0);
SearchRequest searchRequest = new SearchRequest().indices(detector.getIndices().toArray(new String[0])).source(searchSourceBuilder);
client.search(searchRequest, ActionListener.wrap(searchResponse -> {
DetectorProfile.Builder profileBuilder = new DetectorProfile.Builder();
Aggregations aggs = searchResponse.getAggregations();
if (aggs == null) {
// This would indicate some bug or some opensearch core changes that we are not aware of (we don't keep up-to-date
// with
// the large amounts of changes there). For example, they may change to if there are results return it; otherwise
// return
// null instead of an empty Aggregations as they currently do.
logger.warn("Unexpected null aggregation.");
listener.onResponse(profileBuilder.totalEntities(0L).build());
return;
}
Aggregation aggrResult = aggs.get(CommonName.TOTAL_ENTITIES);
if (aggrResult == null) {
listener.onFailure(new IllegalArgumentException("Fail to find valid aggregation result"));
return;
}
CompositeAggregation compositeAgg = (CompositeAggregation) aggrResult;
DetectorProfile profile = profileBuilder.totalEntities(Long.valueOf(compositeAgg.getBuckets().size())).build();
listener.onResponse(profile);
}, searchException -> {
logger.warn(CommonErrorMessages.FAIL_TO_GET_TOTAL_ENTITIES + detector.getDetectorId());
listener.onFailure(searchException);
}));
}
}
}
use of org.opensearch.ad.util.MultiResponsesDelegateActionListener in project anomaly-detection by opensearch-project.
the class AnomalyDetectorProfileRunner method prepareProfile.
private void prepareProfile(AnomalyDetector detector, ActionListener<DetectorProfile> listener, Set<DetectorProfileName> profilesToCollect) {
String detectorId = detector.getDetectorId();
GetRequest getRequest = new GetRequest(ANOMALY_DETECTOR_JOB_INDEX, detectorId);
client.get(getRequest, ActionListener.wrap(getResponse -> {
if (getResponse != null && getResponse.isExists()) {
try (XContentParser parser = XContentType.JSON.xContent().createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, getResponse.getSourceAsString())) {
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
AnomalyDetectorJob job = AnomalyDetectorJob.parse(parser);
long enabledTimeMs = job.getEnabledTime().toEpochMilli();
boolean isMultiEntityDetector = detector.isMultientityDetector();
int totalResponsesToWait = 0;
if (profilesToCollect.contains(DetectorProfileName.ERROR)) {
totalResponsesToWait++;
}
// when to consolidate results and return to users
if (isMultiEntityDetector) {
if (profilesToCollect.contains(DetectorProfileName.TOTAL_ENTITIES)) {
totalResponsesToWait++;
}
if (profilesToCollect.contains(DetectorProfileName.COORDINATING_NODE) || profilesToCollect.contains(DetectorProfileName.SHINGLE_SIZE) || profilesToCollect.contains(DetectorProfileName.TOTAL_SIZE_IN_BYTES) || profilesToCollect.contains(DetectorProfileName.MODELS) || profilesToCollect.contains(DetectorProfileName.ACTIVE_ENTITIES) || profilesToCollect.contains(DetectorProfileName.INIT_PROGRESS) || profilesToCollect.contains(DetectorProfileName.STATE)) {
totalResponsesToWait++;
}
if (profilesToCollect.contains(DetectorProfileName.AD_TASK)) {
totalResponsesToWait++;
}
} else {
if (profilesToCollect.contains(DetectorProfileName.STATE) || profilesToCollect.contains(DetectorProfileName.INIT_PROGRESS)) {
totalResponsesToWait++;
}
if (profilesToCollect.contains(DetectorProfileName.COORDINATING_NODE) || profilesToCollect.contains(DetectorProfileName.SHINGLE_SIZE) || profilesToCollect.contains(DetectorProfileName.TOTAL_SIZE_IN_BYTES) || profilesToCollect.contains(DetectorProfileName.MODELS)) {
totalResponsesToWait++;
}
if (profilesToCollect.contains(DetectorProfileName.AD_TASK)) {
totalResponsesToWait++;
}
}
MultiResponsesDelegateActionListener<DetectorProfile> delegateListener = new MultiResponsesDelegateActionListener<DetectorProfile>(listener, totalResponsesToWait, CommonErrorMessages.FAIL_FETCH_ERR_MSG + detectorId, false);
if (profilesToCollect.contains(DetectorProfileName.ERROR)) {
adTaskManager.getAndExecuteOnLatestDetectorLevelTask(detectorId, ADTaskType.REALTIME_TASK_TYPES, adTask -> {
DetectorProfile.Builder profileBuilder = new DetectorProfile.Builder();
if (adTask.isPresent()) {
long lastUpdateTimeMs = adTask.get().getLastUpdateTime().toEpochMilli();
// is enabled.
if (lastUpdateTimeMs > enabledTimeMs && adTask.get().getError() != null) {
profileBuilder.error(adTask.get().getError());
}
delegateListener.onResponse(profileBuilder.build());
} else {
// detector state for this detector does not exist
delegateListener.onResponse(profileBuilder.build());
}
}, transportService, false, delegateListener);
}
// when to consolidate results and return to users
if (isMultiEntityDetector) {
if (profilesToCollect.contains(DetectorProfileName.TOTAL_ENTITIES)) {
profileEntityStats(delegateListener, detector);
}
if (profilesToCollect.contains(DetectorProfileName.COORDINATING_NODE) || profilesToCollect.contains(DetectorProfileName.SHINGLE_SIZE) || profilesToCollect.contains(DetectorProfileName.TOTAL_SIZE_IN_BYTES) || profilesToCollect.contains(DetectorProfileName.MODELS) || profilesToCollect.contains(DetectorProfileName.ACTIVE_ENTITIES) || profilesToCollect.contains(DetectorProfileName.INIT_PROGRESS) || profilesToCollect.contains(DetectorProfileName.STATE)) {
profileModels(detector, profilesToCollect, job, true, delegateListener);
}
if (profilesToCollect.contains(DetectorProfileName.AD_TASK)) {
adTaskManager.getLatestHistoricalTaskProfile(detectorId, transportService, null, delegateListener);
}
} else {
if (profilesToCollect.contains(DetectorProfileName.STATE) || profilesToCollect.contains(DetectorProfileName.INIT_PROGRESS)) {
profileStateRelated(detector, delegateListener, job.isEnabled(), profilesToCollect);
}
if (profilesToCollect.contains(DetectorProfileName.COORDINATING_NODE) || profilesToCollect.contains(DetectorProfileName.SHINGLE_SIZE) || profilesToCollect.contains(DetectorProfileName.TOTAL_SIZE_IN_BYTES) || profilesToCollect.contains(DetectorProfileName.MODELS)) {
profileModels(detector, profilesToCollect, job, false, delegateListener);
}
if (profilesToCollect.contains(DetectorProfileName.AD_TASK)) {
adTaskManager.getLatestHistoricalTaskProfile(detectorId, transportService, null, delegateListener);
}
}
} catch (Exception e) {
logger.error(CommonErrorMessages.FAIL_TO_GET_PROFILE_MSG, e);
listener.onFailure(e);
}
} else {
onGetDetectorForPrepare(detectorId, listener, profilesToCollect);
}
}, exception -> {
if (ExceptionUtil.isIndexNotAvailable(exception)) {
logger.info(exception.getMessage());
onGetDetectorForPrepare(detectorId, listener, profilesToCollect);
} else {
logger.error(CommonErrorMessages.FAIL_TO_GET_PROFILE_MSG + detectorId);
listener.onFailure(exception);
}
}));
}
Aggregations