package org.stagemonitor.tracing; import com.codahale.metrics.Timer; import com.uber.jaeger.context.TraceContext; import com.uber.jaeger.context.TracingUtils; import org.stagemonitor.core.Stagemonitor; import org.stagemonitor.core.instrument.WeakConcurrentMap; import org.stagemonitor.tracing.profiler.CallStackElement; import org.stagemonitor.tracing.reporter.ReadbackSpan; import org.stagemonitor.tracing.sampling.PostExecutionInterceptorContext; import org.stagemonitor.tracing.sampling.PreExecutionInterceptorContext; import org.stagemonitor.tracing.utils.SpanUtils; import org.stagemonitor.tracing.wrapper.AbstractSpanEventListener; import org.stagemonitor.tracing.wrapper.SpanEventListener; import org.stagemonitor.tracing.wrapper.SpanEventListenerFactory; import org.stagemonitor.tracing.wrapper.SpanWrapper; import org.stagemonitor.tracing.wrapper.StatelessSpanEventListener; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import io.opentracing.Span; import io.opentracing.tag.Tags; public class SpanContextInformation { private static final WeakConcurrentMap<Span, SpanContextInformation> spanContextMap = new WeakConcurrentMap .WithInlinedExpunction<Span, SpanContextInformation>(); private long overhead1; private SpanContextInformation parent; private Map<String, Object> requestAttributes = new HashMap<String, Object>(); private CallStackElement callTree; private String operationName; private long duration; private boolean externalRequest; private boolean serverRequest; private Timer timerForThisRequest; private Map<String, ExternalRequestStats> externalRequestStats = new HashMap<String, ExternalRequestStats>(); private PostExecutionInterceptorContext postExecutionInterceptorContext; private boolean sampled = true; private PreExecutionInterceptorContext preExecutionInterceptorContext; private String operationType; private ReadbackSpan readbackSpan; public static SpanContextInformation getCurrent() { final TraceContext traceContext = TracingUtils.getTraceContext(); if (traceContext.isEmpty()) { return null; } return forSpan(traceContext.getCurrentSpan()); } /** * Gets or creates the {@link SpanContextInformation} for the provided span. */ public static SpanContextInformation forSpan(Span span) { if (span != null) { if (!spanContextMap.containsKey(span)) { spanContextMap.putIfAbsent(span, new SpanContextInformation()); } return spanContextMap.get(span); } return null; } public String getOperationName() { return operationName; } /** * Adds an attribute to the request which can later be retrieved by {@link #getRequestAttribute(String)} * <p/> * The attributes won't be reported */ public void addRequestAttribute(String key, Object value) { requestAttributes.put(key, value); } public Object getRequestAttribute(String key) { return requestAttributes.get(key); } public CallStackElement getCallTree() { return callTree; } private void setOperationName(String operationName) { this.operationName = operationName; } private void setDuration(long duration) { this.duration = duration; } public long getDurationNanos() { return duration; } private void setExternalRequest(boolean externalRequest) { this.externalRequest = externalRequest; } public boolean isExternalRequest() { return externalRequest; } void setServerRequest(boolean serverRequest) { this.serverRequest = serverRequest; } public boolean isServerRequest() { return serverRequest; } /** * Internal method, should only be called by stagemonitor itself */ public void setCallTree(CallStackElement callTree) { this.callTree = callTree; } public Map<String, Object> getRequestAttributes() { return requestAttributes; } public void setTimerForThisRequest(Timer timerForThisRequest) { this.timerForThisRequest = timerForThisRequest; } public Timer getTimerForThisRequest() { return timerForThisRequest; } public SpanContextInformation getParent() { return parent; } public void setParent(SpanContextInformation parent) { this.parent = parent; } public void addExternalRequest(String requestType, long executionTimeNanos) { final ExternalRequestStats stats = this.externalRequestStats.get(requestType); if (stats == null) { externalRequestStats.put(requestType, new ExternalRequestStats(requestType, executionTimeNanos)); } else { stats.add(executionTimeNanos); } } public Collection<ExternalRequestStats> getExternalRequestStats() { return externalRequestStats.values(); } /** * Internal method, should only be called by stagemonitor itself */ public void setPostExecutionInterceptorContext(PostExecutionInterceptorContext postExecutionInterceptorContext) { this.postExecutionInterceptorContext = postExecutionInterceptorContext; } public PostExecutionInterceptorContext getPostExecutionInterceptorContext() { return postExecutionInterceptorContext; } public boolean isSampled() { return sampled; } /** * Internal method, should only be called by stagemonitor itself */ public void setSampled(boolean sampled) { this.sampled = sampled; } long getOverhead1() { return overhead1; } void setOverhead1(long overhead1) { this.overhead1 = overhead1; } @Override public void finalize() throws Throwable { super.finalize(); if (callTree != null) { callTree.recycle(); } } /** * Internal method, should only be called by stagemonitor itself */ public void setPreExecutionInterceptorContext(PreExecutionInterceptorContext preExecutionInterceptorContext) { this.preExecutionInterceptorContext = preExecutionInterceptorContext; } public PreExecutionInterceptorContext getPreExecutionInterceptorContext() { return preExecutionInterceptorContext; } public String getOperationType() { return operationType; } public void setReadbackSpan(ReadbackSpan readbackSpan) { this.readbackSpan = readbackSpan; } public ReadbackSpan getReadbackSpan() { return readbackSpan; } public static class ExternalRequestStats { private final double MS_IN_NANOS = TimeUnit.MILLISECONDS.toNanos(1); private final String requestType; private int executionCount = 1; private long executionTimeNanos; ExternalRequestStats(String requestType, long executionTimeNanos) { this.requestType = requestType; this.executionTimeNanos = executionTimeNanos; } public double getExecutionTimeMs() { return executionTimeNanos / MS_IN_NANOS; } public long getExecutionTimeNanos() { return executionTimeNanos; } public int getExecutionCount() { return executionCount; } public String getRequestType() { return requestType; } public void add(long executionTimeNanos) { executionCount++; this.executionTimeNanos += executionTimeNanos; } } static class SpanContextSpanEventListener extends AbstractSpanEventListener implements SpanEventListenerFactory { private SpanContextInformation info; private String spanKind; private Number samplingPriority; private String operationType; @Override public void onStart(SpanWrapper spanWrapper) { info = SpanContextInformation.forSpan(spanWrapper); info.setParent(SpanContextInformation.getCurrent()); TracingUtils.getTraceContext().push(spanWrapper); spanContextMap.put(spanWrapper, info); for (Map.Entry<String, String> entry : Stagemonitor.getMeasurementSession().asMap().entrySet()) { spanWrapper.setTag(entry.getKey(), entry.getValue()); } handleTagsSetBeforeSpanStarted(); } private void handleTagsSetBeforeSpanStarted() { if (spanKind != null) { onSetTag(Tags.SPAN_KIND.getKey(), spanKind); } if (samplingPriority != null) { onSetTag(Tags.SAMPLING_PRIORITY.getKey(), samplingPriority); } if (operationType != null) { onSetTag(SpanUtils.OPERATION_TYPE, operationType); } } @Override public String onSetTag(String key, String value) { if (key.equals(Tags.SPAN_KIND.getKey())) { if (info != null) { info.setExternalRequest(Tags.SPAN_KIND_CLIENT.equals(value)); info.setServerRequest(Tags.SPAN_KIND_SERVER.equals(value)); } else { // span.kind was set before the span has been started // store in instance variable and set again if a SpanContextInformation is available spanKind = value; } } else if (SpanUtils.OPERATION_TYPE.equals(key)) { if (info != null) { info.operationType = value; } else { operationType = value; } } return value; } @Override public Number onSetTag(String key, Number value) { if (Tags.SAMPLING_PRIORITY.getKey().equals(key)) { if (info != null) { info.setSampled(value.shortValue() > 0); } else { // sampling.priority was set before the span has been started // store in instance variable and set again if a SpanContextInformation is available samplingPriority = value; } } return value; } @Override public void onFinish(SpanWrapper spanWrapper, String operationName, long durationNanos) { final SpanContextInformation info = SpanContextInformation.forSpan(spanWrapper); info.setOperationName(operationName); info.setDuration(durationNanos); } @Override public SpanEventListener create() { return new SpanContextSpanEventListener(); } } public static class SpanFinalizer extends StatelessSpanEventListener { @Override public void onFinish(SpanWrapper spanWrapper, String operationName, long durationNanos) { TracingUtils.getTraceContext().pop(); spanContextMap.remove(spanWrapper); } } }