/*
* Copyright 2015 Hewlett-Packard Development Company, L.P.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
*/
package com.hp.autonomy.frontend.find.idol.metrics;
import com.autonomy.aci.client.transport.AciParameter;
import com.autonomy.aci.client.transport.AciServerDetails;
import com.hp.autonomy.searchcomponents.idol.annotations.IdolService;
import com.hp.autonomy.searchcomponents.idol.exceptions.AciErrorExceptionAspect;
import com.hp.autonomy.types.requests.idol.actions.params.ActionParams;
import com.hp.autonomy.types.requests.idol.actions.query.params.QueryParams;
import com.hp.autonomy.types.requests.idol.actions.tags.params.GetQueryTagValuesParams;
import com.hp.autonomy.types.requests.idol.actions.user.UserActions;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.hp.autonomy.frontend.find.core.metrics.MetricsConfiguration.*;
/**
* Default implementation of {@link AciErrorExceptionAspect}
*/
@SuppressWarnings("ProhibitedExceptionDeclared")
@Aspect
@Component
@ConditionalOnProperty(FIND_METRICS_ENABLED_PROPERTY_KEY)
class PerformanceMonitoringAspect {
static final String SERVICE_METRIC_NAME_PREFIX = METRIC_NAME_SEPARATOR + "service" + METRIC_NAME_SEPARATOR;
static final String IDOL_REQUEST_METRIC_NAME_PREFIX = METRIC_NAME_SEPARATOR + "idol" + METRIC_NAME_SEPARATOR;
static final char CLASS_METHOD_SEPARATOR = ':';
static final String PARAMETER_SEPARATOR = "&";
static final char NAME_VALUE_SEPARATOR = '=';
private static final Pattern ILLEGAL_COMPONENT_CHARACTERS = Pattern.compile("\\.");
private static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[/\\\\]");
private static final String ILLEGAL_CHARACTER_REPLACEMENT = "_";
private static final String VALUE_PLACEHOLDER = "[any]";
private final GaugeService gaugeService;
private final String metricType;
@Autowired
public PerformanceMonitoringAspect(final GaugeService gaugeService,
@Value(FIND_METRICS_TYPE_PROPERTY) final String metricType) {
this.gaugeService = gaugeService;
this.metricType = metricType;
}
@Around("@within(idolService)")
public Object monitorServiceMethodPerformance(final ProceedingJoinPoint joinPoint, final IdolService idolService) throws Throwable {
final String metricName = metricType
+ SERVICE_METRIC_NAME_PREFIX
+ sanitiseMetricNameComponent(joinPoint.getSignature().getDeclaringTypeName())
+ CLASS_METHOD_SEPARATOR
+ sanitiseMetricNameComponent(joinPoint.getSignature().getName());
return monitorMethodPerformance(joinPoint, sanitiseMetricName(metricName));
}
@Around(value = "execution(* com.autonomy.aci.client.transport.AciHttpClient.executeAction(..)) && args(serverDetails, parameters)",
argNames = "joinPoint,serverDetails,parameters")
public Object monitorIdolRequestPerformance(
final ProceedingJoinPoint joinPoint,
final AciServerDetails serverDetails,
final Collection<? extends AciParameter> parameters) throws Throwable {
final StringBuilder metricNameBuilder = new StringBuilder(metricType)
.append(IDOL_REQUEST_METRIC_NAME_PREFIX)
.append(sanitiseMetricNameComponent(serverDetails.getHost()))
.append(METRIC_NAME_SEPARATOR)
.append(serverDetails.getPort())
.append(METRIC_NAME_SEPARATOR);
final Map<String, String> parameterMap = parameters.stream()
.filter(parameter -> !QueryParams.SecurityInfo.name().equalsIgnoreCase(parameter.getName()) && StringUtils.isNotEmpty(parameter.getValue()))
.collect(Collectors.toMap(AciParameter::getName, AciParameter::getValue));
final Collection<String> parameterKeyValueStrings = parameterMap.entrySet().stream()
.map(e -> e.getKey() + NAME_VALUE_SEPARATOR + sanitiseMetricNameComponent(tweakParameterValueInMetricName(e.getKey(), e.getValue())))
.sorted()
.collect(Collectors.toList());
metricNameBuilder.append(String.join(PARAMETER_SEPARATOR, parameterKeyValueStrings));
return Arrays.stream(UserActions.values()).anyMatch(a ->
a.name().equalsIgnoreCase(parameterMap.get(ActionParams.Action.name())))
? joinPoint.proceed()
: monitorMethodPerformance(joinPoint, sanitiseMetricName(metricNameBuilder.toString()));
}
private Object monitorMethodPerformance(final ProceedingJoinPoint joinPoint, final String metricName) throws Throwable {
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
return joinPoint.proceed();
} finally {
stopWatch.stop();
gaugeService.submit(metricName, stopWatch.getTotalTimeMillis());
}
}
private String sanitiseMetricName(final CharSequence metricNameBuilder) {
return ILLEGAL_CHARACTERS.matcher(metricNameBuilder).replaceAll(ILLEGAL_CHARACTER_REPLACEMENT);
}
private String sanitiseMetricNameComponent(final CharSequence metricNameBuilder) {
return ILLEGAL_COMPONENT_CHARACTERS.matcher(metricNameBuilder).replaceAll(ILLEGAL_CHARACTER_REPLACEMENT);
}
private CharSequence tweakParameterValueInMetricName(final String name, final CharSequence value) {
return QueryParams.Text.name().equalsIgnoreCase(name) && !"*".equals(value) || GetQueryTagValuesParams.Ranges.name().equalsIgnoreCase(name)
? VALUE_PLACEHOLDER
: value;
}
}