package org.stagemonitor.tracing.profiler; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.core.metrics.MetricUtils; import org.stagemonitor.core.util.JsonUtils; import org.stagemonitor.tracing.SpanContextInformation; import org.stagemonitor.tracing.TracingPlugin; import org.stagemonitor.tracing.sampling.PreExecutionInterceptorContext; import org.stagemonitor.tracing.sampling.RateLimitingPreExecutionInterceptor; import org.stagemonitor.tracing.utils.RateLimiter; import org.stagemonitor.tracing.utils.SpanUtils; import org.stagemonitor.tracing.wrapper.SpanWrapper; import org.stagemonitor.tracing.wrapper.StatelessSpanEventListener; import org.stagemonitor.util.StringUtils; import io.opentracing.Span; public class CallTreeSpanEventListener extends StatelessSpanEventListener { private final TracingPlugin tracingPlugin; private RateLimiter rateLimiter; public CallTreeSpanEventListener(TracingPlugin tracingPlugin) { this.tracingPlugin = tracingPlugin; rateLimiter = RateLimitingPreExecutionInterceptor.getRateLimiter(tracingPlugin.getProfilerRateLimitPerMinute()); tracingPlugin.getProfilerRateLimitPerMinuteOption().addChangeListener(new ConfigurationOption.ChangeListener<Double>() { @Override public void onChange(ConfigurationOption<?> configurationOption, Double oldValue, Double newValue) { rateLimiter = RateLimitingPreExecutionInterceptor.getRateLimiter(newValue); } }); } @Override public void onStart(SpanWrapper spanWrapper) { final SpanContextInformation contextInfo = SpanContextInformation.forSpan(spanWrapper); if (contextInfo.isSampled() && contextInfo.getPreExecutionInterceptorContext() != null) { determineIfEnableProfiler(contextInfo); if (contextInfo.getPreExecutionInterceptorContext().isCollectCallTree()) { contextInfo.setCallTree(Profiler.activateProfiling("total")); } } } private void determineIfEnableProfiler(SpanContextInformation spanContext) { final PreExecutionInterceptorContext interceptorContext = spanContext.getPreExecutionInterceptorContext(); if (spanContext.isExternalRequest()) { interceptorContext.shouldNotCollectCallTree("this is a external request (span.kind=client)"); return; } double callTreeRateLimit = tracingPlugin.getProfilerRateLimitPerMinute(); if (!tracingPlugin.isProfilerActive()) { interceptorContext.shouldNotCollectCallTree("stagemonitor.profiler.active=false"); } else if (callTreeRateLimit <= 0) { interceptorContext.shouldNotCollectCallTree("stagemonitor.profiler.sampling.rateLimitPerMinute <= 0"); } else if (RateLimitingPreExecutionInterceptor.isRateExceeded(rateLimiter)) { interceptorContext.shouldNotCollectCallTree("rate limit is reached"); } } @Override public void onFinish(SpanWrapper spanWrapper, String operationName, long durationNanos) { final SpanContextInformation contextInfo = SpanContextInformation.forSpan(spanWrapper); if (contextInfo.getCallTree() != null) { try { Profiler.stop(); if (contextInfo.isSampled()) { determineIfExcludeCallTree(contextInfo); if (isAddCallTreeToSpan(contextInfo, operationName)) { addCallTreeToSpan(contextInfo, spanWrapper, operationName); } } } finally { Profiler.clearMethodCallParent(); } } } private void determineIfExcludeCallTree(SpanContextInformation contextInfo) { final double percentileLimit = tracingPlugin.getExcludeCallTreeFromReportWhenFasterThanXPercentOfRequests(); if (!MetricUtils.isFasterThanXPercentOfAllRequests(contextInfo.getDurationNanos(), percentileLimit, contextInfo.getTimerForThisRequest())) { contextInfo.getPostExecutionInterceptorContext().excludeCallTree("the duration of this request is faster than the percentile limit"); } } private boolean isAddCallTreeToSpan(SpanContextInformation info, String operationName) { return info.getCallTree() != null && info.getPostExecutionInterceptorContext() != null && !info.getPostExecutionInterceptorContext().isExcludeCallTree() && StringUtils.isNotEmpty(operationName); } private void addCallTreeToSpan(SpanContextInformation info, Span span, String operationName) { final CallStackElement callTree = info.getCallTree(); callTree.setSignature(operationName); final double minExecutionTimeMultiplier = tracingPlugin.getMinExecutionTimePercent() / 100; if (minExecutionTimeMultiplier > 0d) { callTree.removeCallsFasterThan((long) (callTree.getExecutionTime() * minExecutionTimeMultiplier)); } span.setTag(SpanUtils.CALL_TREE_JSON, JsonUtils.toJson(callTree)); span.setTag(SpanUtils.CALL_TREE_ASCII, callTree.toString(true)); } }