/**
* Copyright 2014 SAP AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spotter.ext.detection.edc.strategies;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.aim.api.measurement.dataset.Dataset;
import org.aim.api.measurement.dataset.DatasetCollection;
import org.aim.api.measurement.dataset.ParameterSelection;
import org.aim.artifacts.probes.StackTraceProbe;
import org.aim.artifacts.records.ResponseTimeRecord;
import org.aim.artifacts.records.SQLQueryRecord;
import org.aim.artifacts.records.StackTraceRecord;
import org.aim.artifacts.records.ThreadTracingRecord;
import org.lpe.common.config.GlobalConfiguration;
import org.lpe.common.util.LpeNumericUtils;
import org.lpe.common.util.LpeStringUtils;
import org.lpe.common.util.NumericPair;
import org.lpe.common.util.NumericPairList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.core.chartbuilder.AnalysisChartBuilder;
import org.spotter.ext.detection.edc.EDCDetectionController;
import org.spotter.ext.detection.edc.EDCExtension;
import org.spotter.ext.detection.edc.IEDCAnalysisStrategy;
import org.spotter.ext.detection.edc.utils.DataAnalyzationUtils;
import org.spotter.ext.detection.edc.utils.MethodCall;
import org.spotter.ext.detection.edc.utils.MethodCallSet;
import org.spotter.shared.configuration.ConfigKeys;
import org.spotter.shared.result.model.SpotterResult;
public class RelativeQueryRTStrategy implements IEDCAnalysisStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(RelativeQueryRTStrategy.class);
private EDCDetectionController controller;
private double perfReqThreshold;
private double perfReqConfidence;
private double perfReqRelativeQueryRT;
private double perfReqRelativeQueryRTDiff;
private long numUsers;
private Dataset hierarchyResponseTimes;
private Dataset singleUserResponseTimes;
private Dataset multiUserResponseTimes;
private Dataset singleUserQueries;
private Dataset multiUserQueries;
private Dataset stackTraceQueries;
private Dataset singleUserThreadTracing;
private Dataset multiUserThreadTracing;
private Dataset stackTraces;
boolean validData = false;
String invalidDataMessage = "";
@Override
public void init(Properties problemDetectionConfiguration, EDCDetectionController controller) {
this.controller = controller;
perfReqThreshold = GlobalConfiguration.getInstance().getPropertyAsDouble(
ConfigKeys.PERFORMANCE_REQUIREMENT_THRESHOLD, ConfigKeys.DEFAULT_PERFORMANCE_REQUIREMENT_THRESHOLD);
perfReqConfidence = GlobalConfiguration.getInstance().getPropertyAsDouble(
ConfigKeys.PERFORMANCE_REQUIREMENT_CONFIDENCE, ConfigKeys.DEFAULT_PERFORMANCE_REQUIREMENT_CONFIDENCE);
String sRelativeQRT = controller.getProblemDetectionConfiguration().getProperty(
EDCExtension.PERF_REQ_RELATIVE_QUERY_RT_KEY,
String.valueOf(EDCExtension.PERF_REQ_RELATIVE_QUERY_RT_DEFAULT));
perfReqRelativeQueryRT = Double.valueOf(sRelativeQRT);
String sRelativeQRTDiff = controller.getProblemDetectionConfiguration().getProperty(
EDCExtension.PERF_REQ_RELATIVE_QUERY_RT_DIFF_KEY,
String.valueOf(EDCExtension.PERF_REQ_RELATIVE_QUERY_RT_DIFF_DEFAULT));
perfReqRelativeQueryRTDiff = Double.valueOf(sRelativeQRTDiff);
numUsers = GlobalConfiguration.getInstance().getPropertyAsLong(ConfigKeys.WORKLOAD_MAXUSERS);
}
@Override
public void setMeasurementData(DatasetCollection data) {
LOGGER.info("Setting measurement data...");
validData = false;
ParameterSelection selectHierarchyExp = new ParameterSelection().select(
EDCDetectionController.KEY_EXPERIMENT_NAME, EDCDetectionController.NAME_HIERARCHY_EXP);
ParameterSelection selectSingleUserExp = new ParameterSelection().select(
EDCDetectionController.KEY_EXPERIMENT_NAME, EDCDetectionController.NAME_SINGLE_USER_EXP);
ParameterSelection selectMultiUserExp = new ParameterSelection().select(
EDCDetectionController.KEY_EXPERIMENT_NAME, EDCDetectionController.NAME_MAIN_EXP);
ParameterSelection selectStackTraceExp = new ParameterSelection().select(
EDCDetectionController.KEY_EXPERIMENT_NAME, EDCDetectionController.NAME_STACK_TRACE_EXP);
LOGGER.debug("Setting response time datasets...");
Dataset rtDataset = data.getDataSet(ResponseTimeRecord.class);
if (rtDataset == null || rtDataset.size() == 0) {
invalidDataMessage = "Instrumentation achieved no response time results for the given scope!";
return;
}
hierarchyResponseTimes = selectHierarchyExp.applyTo(rtDataset);
singleUserResponseTimes = selectSingleUserExp.applyTo(rtDataset);
multiUserResponseTimes = selectMultiUserExp.applyTo(rtDataset);
LOGGER.debug("Response times set.");
LOGGER.debug("Setting SQL query datasets...");
Dataset sqlDataset = data.getDataSet(SQLQueryRecord.class);
if (sqlDataset == null || sqlDataset.size() == 0) {
invalidDataMessage = "Instrumentation achieved no query results for the given scope!";
return;
}
for (SQLQueryRecord record : sqlDataset.getRecords(SQLQueryRecord.class)) {
String sql = record.getQueryString().replace("#sc#", ";");
String generalizedSql = LpeStringUtils.getGeneralizedQuery(sql);
if (generalizedSql == null) {
if (sql.contains("$")) {
int idx_1 = sql.indexOf(",", sql.indexOf("$"));
int idx_2 = sql.indexOf(" ", sql.indexOf("$"));
if (idx_1 < 0 && idx_2 < 0) {
idx_1 = sql.length();
}
idx_1 = idx_1 < 0 ? Integer.MAX_VALUE : idx_1;
idx_2 = idx_2 < 0 ? Integer.MAX_VALUE : idx_2;
int endIndex = Math.min(idx_1, idx_2);
String name = sql.substring(sql.indexOf("$"), endIndex);
generalizedSql = sql.replace(name, "tmp");
} else {
generalizedSql = sql;
}
}
record.setQueryString(generalizedSql);
}
singleUserQueries = selectSingleUserExp.applyTo(sqlDataset);
multiUserQueries = selectMultiUserExp.applyTo(sqlDataset);
stackTraceQueries = selectStackTraceExp.applyTo(sqlDataset);
LOGGER.debug("SQL queries set.");
LOGGER.debug("Setting thread tracing datasets...");
Dataset ttDataset = data.getDataSet(ThreadTracingRecord.class);
if (ttDataset == null || ttDataset.size() == 0) {
invalidDataMessage = "Instrumentation achieved no thread tracing results for the given scope!";
return;
}
singleUserThreadTracing = selectSingleUserExp.applyTo(ttDataset);
multiUserThreadTracing = selectMultiUserExp.applyTo(ttDataset);
LOGGER.debug("Thread tracing set.");
LOGGER.debug("Setting stack trace datasets...");
Dataset stDataset = data.getDataSet(StackTraceRecord.class);
if (stDataset == null || stDataset.size() == 0) {
invalidDataMessage = "Instrumentation achieved no stack trace results for the given scope!";
return;
}
stackTraces = selectStackTraceExp.applyTo(stDataset);
LOGGER.debug("Stack traces set.");
validData = true;
LOGGER.info("Data successfully set.");
}
@Override
public SpotterResult analyze() {
LOGGER.info("Starting analysis...");
SpotterResult result = new SpotterResult();
if (!validData) {
result.setDetected(false);
result.addMessage(invalidDataMessage);
LOGGER.warn("Analysis could not be run due to invalid data: {}", invalidDataMessage);
return result;
}
LOGGER.debug("Deriving servlet hierarchy...");
// Select servlets with requirements violating response times
Set<String> servletNames = DataAnalyzationUtils.extractUniqueMethodNames(hierarchyResponseTimes);
MethodCallSet servletHierarchy = DataAnalyzationUtils.getMethodCallSetOfMethods(servletNames,
multiUserResponseTimes, multiUserThreadTracing);
LOGGER.debug("Servlet hierarchy created.");
LOGGER.debug("Deriving lowest servlet layer...");
MethodCallSet servletQueryHierarchy = servletHierarchy.getSubsetOfLowestLayer();
LOGGER.debug("Lowest layer derived.");
LOGGER.debug("Deriving servlet-query hierarchy...");
DataAnalyzationUtils.addQueriesToMethodCallSet(servletQueryHierarchy, multiUserResponseTimes, multiUserQueries,
multiUserThreadTracing);
LOGGER.debug("Servlet-query hierarchy created.");
LOGGER.debug("Locating critical servlets...");
Set<String> criticalServlets = getCriticalServlets(servletQueryHierarchy);
Set<String> nonCriticalServlets = new TreeSet<>();
nonCriticalServlets.addAll(servletQueryHierarchy.getUniqueMethods());
nonCriticalServlets.removeAll(criticalServlets);
for (String nonCritServlet : nonCriticalServlets) {
servletQueryHierarchy.removeAllCallsWithName(nonCritServlet);
}
LOGGER.debug("Critical servlets located.");
// Select queries with requirements violating relative response time
LOGGER.debug("Locate requirements violating queries...");
Map<String, Double> violatingReqQueriesART = getReqViolatingQueries(servletQueryHierarchy);
for (MethodCall servletCall : servletQueryHierarchy.getMethodCalls()) {
for (MethodCall queryCall : servletCall.getCalledOperations()) {
if (!violatingReqQueriesART.containsKey(queryCall.getOperation())) {
servletCall.removeCall(queryCall);
}
}
if (servletCall.getCalledOperations().size() == 0) {
servletQueryHierarchy.removeCall(servletCall);
}
}
LOGGER.debug("Queries located.");
// Drop queries having a bigger relative response time for one user than
// for many users
LOGGER.debug("Drop false positives (single-user test)...");
MethodCallSet singleUserServletHierarchy = DataAnalyzationUtils.getMethodCallSetOfMethods(criticalServlets,
singleUserResponseTimes, singleUserThreadTracing);
MethodCallSet singleUserServletQueryHierarchy = singleUserServletHierarchy.getSubsetOfLowestLayer();
DataAnalyzationUtils.addQueriesToMethodCallSet(singleUserServletQueryHierarchy, singleUserResponseTimes,
singleUserQueries, singleUserThreadTracing);
filterViolatingReqQueriesBySingleUserTest(violatingReqQueriesART, servletQueryHierarchy,
singleUserServletQueryHierarchy);
for (MethodCall servletCall : servletQueryHierarchy.getMethodCalls()) {
if (servletCall.getCalledOperations().size() == 0) {
servletQueryHierarchy.removeCall(servletCall);
}
}
LOGGER.debug("False positives dropped.");
LOGGER.debug("Generate Spotter result...");
result = generateResult(servletQueryHierarchy, singleUserServletQueryHierarchy, violatingReqQueriesART);
LOGGER.debug("Result generated.");
LOGGER.info("Analysis finished!");
return result;
}
private Set<String> getCriticalServlets(MethodCallSet servletQueryHierarchy) {
Set<String> criticalServlets = new TreeSet<>();
for (String methodName : servletQueryHierarchy.getUniqueMethods()) {
Set<MethodCall> callsOfMethod = servletQueryHierarchy.getCallsOfMethodAtLayer(methodName, 0);
int numberOfReqViolatingCalls = 0;
for (MethodCall call : callsOfMethod) {
if (call.getResponseTime() > perfReqThreshold) {
numberOfReqViolatingCalls++;
}
}
if ((double) numberOfReqViolatingCalls / (double) callsOfMethod.size() > 1.0 - perfReqConfidence) {
criticalServlets.add(methodName);
}
}
return criticalServlets;
}
private Map<String, Double> getReqViolatingQueries(MethodCallSet servletQueryHierarchy) {
Set<String> possiblyCriticalQueries = servletQueryHierarchy.getUniqueMethodsOfLayer(1);
Map<String, Double> violatingReqQueriesART = new TreeMap<>();
for (String query : possiblyCriticalQueries) {
List<Double> relativeRTs = DataAnalyzationUtils.getRelativeQueryResponseTimes(query, servletQueryHierarchy);
if (LpeNumericUtils.average(relativeRTs) > perfReqRelativeQueryRT) {
violatingReqQueriesART.put(query, LpeNumericUtils.average(relativeRTs));
} else {
for (MethodCall queryCall : servletQueryHierarchy.getCallsOfMethodAtLayer(query, 1)) {
servletQueryHierarchy.removeCall(queryCall);
}
}
}
return violatingReqQueriesART;
}
private void filterViolatingReqQueriesBySingleUserTest(Map<String, Double> violatingReqQueriesART,
MethodCallSet servletQueryHierarchy, MethodCallSet singleUserServletQueryHierarchy) {
Set<String> queriesToRemove = new TreeSet<>();
for (String query : violatingReqQueriesART.keySet()) {
List<Double> singleUserRRTs = DataAnalyzationUtils.getRelativeQueryResponseTimes(query,
singleUserServletQueryHierarchy);
double avgRRTDiff = violatingReqQueriesART.get(query) - LpeNumericUtils.average(singleUserRRTs);
if (avgRRTDiff < perfReqRelativeQueryRTDiff) {
queriesToRemove.add(query);
servletQueryHierarchy.removeAllCallsWithName(query);
}
}
for (String query : queriesToRemove) {
violatingReqQueriesART.remove(query);
}
}
private SpotterResult generateResult(MethodCallSet servletQueryHierarchy,
MethodCallSet singleUserServletQueryHierarchy, Map<String, Double> violatingReqQueriesART) {
SpotterResult result = new SpotterResult();
result.setDetected(false);
for (String servletMethod : servletQueryHierarchy.getUniqueMethodsOfLayer(0)) {
StringBuilder messageBuilder = new StringBuilder();
messageBuilder.append("EDC detected in service: ");
messageBuilder.append(servletMethod);
messageBuilder.append("\nQueries are:");
for (String query : violatingReqQueriesART.keySet()) {
String formattedServlet = LpeStringUtils.extractClassName(servletMethod) + "."
+ LpeStringUtils.getSimpleMethodName(servletMethod);
List<String> stackTrace = null;
for (String stackTraceString : DataAnalyzationUtils.getStackTracesOfQuery(query, stackTraceQueries,
stackTraces)) {
if (stackTraceString.contains(formattedServlet)) {
stackTrace = new ArrayList<>();
for (String stackTraceElement : stackTraceString
.split(StackTraceProbe.REGEX_DELIM_STACK_TRACE_ELEMENT)) {
stackTrace.add(stackTraceElement);
}
break;
}
}
if (stackTrace == null) {
continue;
} else {
result.setDetected(true);
}
NumericPairList<Long, Long> multiUserServletRts = DataAnalyzationUtils.getServletResponseTimesOverTime(
servletMethod, multiUserResponseTimes);
NumericPairList<Long, Long> singleUserServletRts = DataAnalyzationUtils
.getServletResponseTimesOverTime(servletMethod, singleUserResponseTimes);
double relativeRT = violatingReqQueriesART.get(query);
List<Double> singleUserRRTs = DataAnalyzationUtils.getRelativeQueryResponseTimes(query,
singleUserServletQueryHierarchy);
double singleUserART = LpeNumericUtils.average(singleUserRRTs);
createTimeSeriesChart(servletMethod, query, numUsers, multiUserServletRts,
DataAnalyzationUtils.getQueryResponseTimesOverTime(query, multiUserResponseTimes,
multiUserQueries), singleUserServletRts,
DataAnalyzationUtils.getQueryResponseTimesOverTime(query, singleUserResponseTimes,
singleUserQueries), result);
createRelativeChart(servletMethod, query, numUsers, relativeRT, singleUserART, result);
DecimalFormat df = new DecimalFormat("#.##");
messageBuilder.append("\n");
messageBuilder.append(query);
messageBuilder.append("\n\tAverage relative response time with ");
messageBuilder.append(numUsers);
messageBuilder.append(" users: ");
messageBuilder.append(df.format(relativeRT * 100));
messageBuilder.append("%");
messageBuilder.append("\n\tAverage relative response time with 1 user: ");
messageBuilder.append(df.format(singleUserART * 100));
messageBuilder.append("%");
messageBuilder.append("\n\tStack trace:");
for (String stackTraceElement : stackTrace) {
messageBuilder.append("\n\t\t");
messageBuilder.append(stackTraceElement);
}
}
if (result.isDetected()) {
result.addMessage(messageBuilder.toString());
}
}
return result;
}
private void createRelativeChart(String servlet, String query, long numMaxUsers, double multiUserQueryRRT,
double singleUserQueryRRT, SpotterResult result) {
NumericPairList<Integer, Double> muRRTSeries = new NumericPairList<>();
for (int i = 0; i < 50; i += 2) {
muRRTSeries.add(new NumericPair<Integer, Double>(i, 100.0));
}
NumericPairList<Integer, Double> muQRRTSeries = new NumericPairList<>();
for (int i = 1; i < 50; i += 2) {
muQRRTSeries.add(new NumericPair<Integer, Double>(i, multiUserQueryRRT * 100.0));
}
NumericPairList<Integer, Double> suRRTSeries = new NumericPairList<>();
for (int i = 50; i <= 100; i += 2) {
suRRTSeries.add(new NumericPair<Integer, Double>(i, 100.0));
}
NumericPairList<Integer, Double> suQRRTSeries = new NumericPairList<>();
for (int i = 51; i <= 100; i += 2) {
suQRRTSeries.add(new NumericPair<Integer, Double>(i, singleUserQueryRRT * 100.0));
}
NumericPairList<Integer, Double> nullSeries = new NumericPairList<>();
nullSeries.add(new NumericPair<Integer, Double>(100, 0.0));
String servletName = LpeStringUtils.shortenOperationName(servlet);
String queryName = query;
if (queryName.length() > 15) {
queryName = queryName.substring(0, 13) + "...";
}
AnalysisChartBuilder chartBuilder = AnalysisChartBuilder.getChartBuilder();
chartBuilder.startChart("Relation between " + servletName + " and " + queryName, "",
"Relative Response Time [%]");
chartBuilder.addScatterSeries(muRRTSeries, "AVG servlet Response Time with " + numMaxUsers + " users");
chartBuilder.addScatterSeries(muQRRTSeries, "AVG query Response Time with " + numMaxUsers + " users");
chartBuilder.addScatterSeries(suRRTSeries, "AVG servlet Response Time with 1 user");
chartBuilder.addScatterSeries(suQRRTSeries, "AVG query Response Time with 1 user");
chartBuilder.addScatterSeries(nullSeries, "0");
controller.getResultManager().storeImageChartResource(chartBuilder, "Relative Response Times", result);
}
private void createTimeSeriesChart(String servlet, String query, long numMaxUsers,
NumericPairList<Long, Long> multiUserServletRTs, NumericPairList<Long, Long> multiUserQueryRTs,
NumericPairList<Long, Long> singleUserServletRTs, NumericPairList<Long, Long> singleUserQueryRTs,
SpotterResult result) {
AnalysisChartBuilder chartBuilder = AnalysisChartBuilder.getChartBuilder();
String servletName = LpeStringUtils.shortenOperationName(servlet);
String queryName = query;
if (queryName.length() > 15) {
queryName = queryName.substring(0, 13) + "...";
}
chartBuilder.startChart("Response Times", "Experiment Time [ms]", "Response Time [ms]");
chartBuilder.addScatterSeries(multiUserServletRTs, servletName + " with " + numMaxUsers + " users");
chartBuilder.addScatterSeries(multiUserQueryRTs, queryName + " with " + numMaxUsers + " users");
chartBuilder.addScatterSeries(singleUserServletRTs, servletName + " with 1 user");
chartBuilder.addScatterSeries(singleUserQueryRTs, queryName + " with 1 user");
chartBuilder.addHorizontalLine(perfReqThreshold, "Performance Requirement");
controller.getResultManager().storeImageChartResource(chartBuilder, "Response Times", result);
}
}