Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
Refactor Interceptors, add CallTreeExcludingInterceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix Barnsteiner committed Feb 7, 2016
1 parent c55fbc3 commit 59ebe9d
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ private <T extends RequestTrace> void trackDbMetrics(String requestName, T reque
}
}

private <T extends RequestTrace> MetricName getTimerMetricName(String requestName) {
public static <T extends RequestTrace> MetricName getTimerMetricName(String requestName) {
return name("response_time_server").tag("request_name", requestName).layer("All").build();
}

Expand Down Expand Up @@ -482,6 +482,15 @@ public void addReporter(RequestTraceReporter requestTraceReporter) {
requestTraceReporters.add(0, requestTraceReporter);
}

public <T extends RequestTraceReporter> T getReporter(Class<T> reporterClass) {
for (RequestTraceReporter requestTraceReporter : requestTraceReporters) {
if (requestTraceReporter.getClass() == reporterClass) {
return (T) requestTraceReporter;
}
}
return null;
}

/**
* Shuts down the internal thread pool
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,13 @@ public class RequestMonitorPlugin extends StagemonitorPlugin {
.defaultValue(Collections.<String>emptySet())
.configurationCategory(REQUEST_MONITOR_PLUGIN)
.build();
private final ConfigurationOption<Integer> onlyReportNRequestsPerMinuteToElasticsearch = ConfigurationOption.integerOption()
private final ConfigurationOption<Double> onlyReportNRequestsPerMinuteToElasticsearch = ConfigurationOption.doubleOption()
.key("stagemonitor.requestmonitor.onlyReportNRequestsPerMinuteToElasticsearch")
.dynamic(true)
.label("Only report N requests per minute to ES")
.description("Limits the rate at which request traces are reported to Elasticsearch. " +
"Set to a value below 1 to deactivate ES reporting and to Integer.MAX_VALUE to always report.")
.defaultValue(Integer.MAX_VALUE)
"Set to a value below 1 to deactivate ES reporting and to 1,000,000 or higher to always report.")
.defaultValue(1000000d)
.configurationCategory(REQUEST_MONITOR_PLUGIN)
.build();
private final ConfigurationOption<Boolean> onlyLogElasticsearchRequestTraceReports = ConfigurationOption.booleanOption()
Expand All @@ -156,6 +156,18 @@ public class RequestMonitorPlugin extends StagemonitorPlugin {
.defaultValue(false)
.configurationCategory(REQUEST_MONITOR_PLUGIN)
.build();
private final ConfigurationOption<Double> excludeCallTreeFromElasticsearchReportWhenFasterThanXPercentOfRequests = ConfigurationOption.doubleOption()
.key("stagemonitor.requestmonitor.elasticsearch.excludeCallTreeFromElasticsearchReportWhenFasterThanXPercentOfRequests")
.dynamic(true)
.label("Exclude the Call Tree from Elasticsearch reports on x% of the fastest requests")
.description("Exclude the Call Tree from Elasticsearch report when the request was faster faster than x " +
"percent of requests with the same request name. This helps to reduce the network and disk overhead " +
"as uninteresting Call Trees (those which are comparatively fast) are excluded." +
"Example: set to 1 to always exclude the Call Tree and to 0 to always include it. " +
"With a setting of 0.85, the Call Tree will only be reported for the slowest 25% of the requests.")
.defaultValue(0d)
.configurationCategory(REQUEST_MONITOR_PLUGIN)
.build();

private static RequestMonitor requestMonitor;

Expand Down Expand Up @@ -249,11 +261,15 @@ public Collection<String> getOnlyReportRequestsWithNameToElasticsearch() {
return onlyReportRequestsWithNameToElasticsearch.getValue();
}

public int getOnlyReportNRequestsPerMinuteToElasticsearch() {
public double getOnlyReportNRequestsPerMinuteToElasticsearch() {
return onlyReportNRequestsPerMinuteToElasticsearch.getValue();
}

public boolean isOnlyLogElasticsearchRequestTraceReports() {
return onlyLogElasticsearchRequestTraceReports.getValue();
}

public double getExcludeCallTreeFromElasticsearchReportWhenFasterThanXPercentOfRequests() {
return excludeCallTreeFromElasticsearchReportWhenFasterThanXPercentOfRequests.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.stagemonitor.requestmonitor.reporter;

import com.codahale.metrics.Timer;
import org.stagemonitor.requestmonitor.RequestMonitorPlugin;

class CallTreeExcludingInterceptor implements ElasticsearchRequestTraceReporterInterceptor {

@Override
public void interceptReport(InterceptContext context) {
if (context.getRequestTrace().getCallStack() == null) {
context.addProperty("containsCallTree", false);
return;
} else {
context.addProperty("containsCallTree", true);
}

final double percentileLimit = context
.getConfig(RequestMonitorPlugin.class)
.getExcludeCallTreeFromElasticsearchReportWhenFasterThanXPercentOfRequests();

if (percentileLimit > 0) {
if (percentileLimit >= 1) {
exclude(context);
} else {
final Timer timer = context.getTimerForThisRequest();
if (timer != null) {
final double percentile = timer.getSnapshot().getValue(percentileLimit);
final long executionTime = context.getRequestTrace().getExecutionTime();
if (executionTime < percentile) {
exclude(context);
}
}
}
}
}

private void exclude(InterceptContext context) {
context.addExcludedProperties("callStack", "callStackJson").addProperty("containsCallTree", false);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.stagemonitor.requestmonitor.reporter;

import java.util.LinkedList;
import java.util.Collection;
import java.util.ServiceLoader;
import java.util.concurrent.CopyOnWriteArrayList;

import com.codahale.metrics.Meter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.core.CorePlugin;
import org.stagemonitor.core.Stagemonitor;
import org.stagemonitor.core.configuration.Configuration;
import org.stagemonitor.core.elasticsearch.ElasticsearchClient;
import org.stagemonitor.core.util.JsonUtils;
Expand All @@ -27,45 +29,56 @@ public class ElasticsearchRequestTraceReporter implements RequestTraceReporter {
private final Meter reportingRate = new Meter();
private final Logger requestTraceLogger;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Iterable<ElasticsearchRequestTraceReporterInterceptor> interceptors;
private final Collection<ElasticsearchRequestTraceReporterInterceptor> interceptors =
new CopyOnWriteArrayList<ElasticsearchRequestTraceReporterInterceptor>();
private final Configuration configuration;

public ElasticsearchRequestTraceReporter(Configuration configuration) {
this(configuration, LoggerFactory.getLogger(ES_REQUEST_TRACE_LOGGER));
}

ElasticsearchRequestTraceReporter(Configuration configuration, Logger requestTraceLogger) {
this.configuration = configuration;
this.corePlugin = configuration.getConfig(CorePlugin.class);
this.requestMonitorPlugin = configuration.getConfig(RequestMonitorPlugin.class);
this.elasticsearchClient = corePlugin.getElasticsearchClient();
this.requestTraceLogger = requestTraceLogger;
this.interceptors = ServiceLoader.load(ElasticsearchRequestTraceReporterInterceptor.class,
ElasticsearchRequestTraceReporter.class.getClassLoader());
for (ElasticsearchRequestTraceReporterInterceptor interceptor : interceptors) {
interceptor.init(configuration);
this.interceptors.add(new RateLimitingInterceptor());
this.interceptors.add(new NameFilteringInterceptor());
this.interceptors.add(new CallTreeExcludingInterceptor());
for (ElasticsearchRequestTraceReporterInterceptor interceptor : ServiceLoader.load(
ElasticsearchRequestTraceReporterInterceptor.class,
ElasticsearchRequestTraceReporter.class.getClassLoader())) {
interceptors.add(interceptor);
}

}

@Override
public <T extends RequestTrace> void reportRequestTrace(T requestTrace) {
final LinkedList<String> excludedProperties = new LinkedList<String>();
InterceptContext context = new InterceptContext(configuration, requestTrace, reportingRate, corePlugin.getMetricRegistry());
for (ElasticsearchRequestTraceReporterInterceptor interceptor : interceptors) {
if (!interceptor.interceptReport(requestTrace, reportingRate, excludedProperties)) {
logger.debug("{} aborted reporting a request trace to Elasticsearch", interceptor);
return;
try {
interceptor.interceptReport(context);
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
doReport(requestTrace, excludedProperties);
if (context.isReport()) {
doReport(requestTrace, context);
}
}

private <T extends RequestTrace> void doReport(T requestTrace, LinkedList<String> excludedProperties) {
private <T extends RequestTrace> void doReport(T requestTrace, InterceptContext context) {
reportingRate.mark();
final String index = "stagemonitor-requests-" + StringUtils.getLogstashStyleDate();
final String type = "requests";
if (!requestMonitorPlugin.isOnlyLogElasticsearchRequestTraceReports()) {
if (excludedProperties.isEmpty()) {
if (context.getExcludedProperties().isEmpty()) {
elasticsearchClient.index(index, type, requestTrace);
} else {
elasticsearchClient.index(index, type, JsonUtils.toObjectNode(requestTrace).remove(excludedProperties));
elasticsearchClient
.index(index, type, JsonUtils.toObjectNode(requestTrace).remove(context.getExcludedProperties()));
}
} else {
requestTraceLogger.info(ElasticsearchClient.getBulkHeader("index", index, type) + JsonUtils.toJson(requestTrace));
Expand All @@ -77,4 +90,19 @@ public <T extends RequestTrace> boolean isActive(T requestTrace) {
return StringUtils.isNotEmpty(corePlugin.getElasticsearchUrl());
}

/**
* Add an {@link ElasticsearchRequestTraceReporterInterceptor} to the interceptor list
*
* @param interceptor the interceptor that should be executed before each report
*/
public static void registerInterceptor(ElasticsearchRequestTraceReporterInterceptor interceptor) {
final ElasticsearchRequestTraceReporter thiz = Stagemonitor
.getConfiguration(RequestMonitorPlugin.class)
.getRequestMonitor()
.getReporter(ElasticsearchRequestTraceReporter.class);
if (thiz != null) {
thiz.interceptors.add(interceptor);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
package org.stagemonitor.requestmonitor.reporter;

import java.util.Collection;

import com.codahale.metrics.Meter;
import org.stagemonitor.core.configuration.Configuration;
import org.stagemonitor.requestmonitor.RequestTrace;

/**
* Allows implementers to customize or omit reporting a request trace to Elasticsearch
* <p/>
* To add an interceptor, call {@link ElasticsearchRequestTraceReporter#registerInterceptor(ElasticsearchRequestTraceReporterInterceptor)}
* or place a file under <code>META-INF/services/org.stagemonitor.requestmonitor.reporter.ElasticsearchRequestTraceReporterInterceptor</code>
* and insert the canonical class name of your implementation.
*/
public interface ElasticsearchRequestTraceReporterInterceptor {

void init(Configuration configuration);

/**
* This method is called before a request trace gets reported to Elasticsearch.
* <p/>
* The implementer of this method can decide whether or not to report the request trace or to exclude certain properties.
*
* @param requestTrace The request trace that is about to be reported
* @param reportingRate The rate at which request traces got reported
* @param excludedProperties Add the names of properties you want to exclude from a report
* @return <code>true</code> if the request trace should be reported, <code>false</code> if reporting should be omitted
* @param context contextual information about the current report that is about to happen
*/
boolean interceptReport(RequestTrace requestTrace, Meter reportingRate, Collection<String> excludedProperties);
void interceptReport(InterceptContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.stagemonitor.requestmonitor.reporter;

import static org.stagemonitor.requestmonitor.RequestMonitor.getTimerMetricName;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;

import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import org.stagemonitor.core.configuration.Configuration;
import org.stagemonitor.core.configuration.ConfigurationOptionProvider;
import org.stagemonitor.core.metrics.metrics2.Metric2Registry;
import org.stagemonitor.requestmonitor.RequestTrace;

public class InterceptContext {

private final Configuration configuration;
private final RequestTrace requestTrace;
private final Meter reportingRate;
private final Metric2Registry metricRegistry;
private boolean mustReport = false;
private boolean report = true;
private final Collection<String> excludedProperties = new LinkedList<String>();

InterceptContext(Configuration configuration, RequestTrace requestTrace, Meter reportingRate, Metric2Registry metricRegistry) {
this.configuration = configuration;
this.requestTrace = requestTrace;
this.reportingRate = reportingRate;
this.metricRegistry = metricRegistry;
}

public InterceptContext mustReport() {
mustReport = true;
report = true;
return this;
}

public InterceptContext shouldNotReport() {
if (!mustReport) {
report = false;
}
return this;
}

public InterceptContext addExcludedProperty(String properties) {
excludedProperties.add(properties);
return this;
}

public InterceptContext addExcludedProperties(String... properties) {
excludedProperties.addAll(Arrays.asList(properties));
return this;
}

public InterceptContext addProperty(String key, Object value) {
requestTrace.addCustomProperty(key, value);
return this;
}

public RequestTrace getRequestTrace() {
return requestTrace;
}

public Meter getReportingRate() {
return reportingRate;
}

public boolean isReport() {
return report;
}

public Collection<String> getExcludedProperties() {
return excludedProperties;
}

public Metric2Registry getMetricRegistry() {
return metricRegistry;
}

/**
* Returns the timer for the current request.
*
* @return the timer for the current request (may be <code>null</code>)
*/
public Timer getTimerForThisRequest() {
return metricRegistry.getTimers().get(getTimerMetricName(requestTrace.getName()));
}

public <T extends ConfigurationOptionProvider> T getConfig(Class<T> configClass) {
return configuration.getConfig(configClass);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@

import java.util.Collection;

import com.codahale.metrics.Meter;
import org.stagemonitor.core.configuration.Configuration;
import org.stagemonitor.requestmonitor.RequestMonitorPlugin;
import org.stagemonitor.requestmonitor.RequestTrace;

public class NameFilteringInterceptor implements ElasticsearchRequestTraceReporterInterceptor {
class NameFilteringInterceptor implements ElasticsearchRequestTraceReporterInterceptor {

private RequestMonitorPlugin requestMonitorPlugin;

@Override
public void init(Configuration configuration) {
requestMonitorPlugin = configuration.getConfig(RequestMonitorPlugin.class);
}
@Override
public boolean interceptReport(RequestTrace requestTrace, Meter reportingRate, Collection<String> excludedProperties) {
final Collection<String> onlyReportRequestsWithName = requestMonitorPlugin.getOnlyReportRequestsWithNameToElasticsearch();
return onlyReportRequestsWithName.isEmpty() || onlyReportRequestsWithName.contains(requestTrace.getName());
public void interceptReport(InterceptContext context) {
final Collection<String> onlyReportRequestsWithName = context.getConfig(RequestMonitorPlugin.class)
.getOnlyReportRequestsWithNameToElasticsearch();
if (!onlyReportRequestsWithName.isEmpty() && !onlyReportRequestsWithName.contains(context.getRequestTrace().getName())) {
context.shouldNotReport();
}
}

}

0 comments on commit 59ebe9d

Please sign in to comment.