/* * Copyright 2016-2017 the original author or authors. * * 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.glowroot.agent.plugin.httpclient; import java.net.URI; import javax.annotation.Nullable; import org.glowroot.agent.plugin.api.Agent; import org.glowroot.agent.plugin.api.AsyncTraceEntry; import org.glowroot.agent.plugin.api.AuxThreadContext; import org.glowroot.agent.plugin.api.MessageSupplier; import org.glowroot.agent.plugin.api.ThreadContext; import org.glowroot.agent.plugin.api.TimerName; import org.glowroot.agent.plugin.api.TraceEntry; import org.glowroot.agent.plugin.api.util.FastThreadLocal; import org.glowroot.agent.plugin.api.weaving.BindParameter; import org.glowroot.agent.plugin.api.weaving.BindReceiver; import org.glowroot.agent.plugin.api.weaving.BindThrowable; import org.glowroot.agent.plugin.api.weaving.BindTraveler; import org.glowroot.agent.plugin.api.weaving.IsEnabled; import org.glowroot.agent.plugin.api.weaving.Mixin; import org.glowroot.agent.plugin.api.weaving.OnBefore; import org.glowroot.agent.plugin.api.weaving.OnReturn; import org.glowroot.agent.plugin.api.weaving.OnThrow; import org.glowroot.agent.plugin.api.weaving.Pointcut; import org.glowroot.agent.plugin.httpclient.ApacheHttpClientAspect.HttpHost; import org.glowroot.agent.plugin.httpclient.ApacheHttpClientAspect.HttpRequest; import org.glowroot.agent.plugin.httpclient.ApacheHttpClientAspect.HttpUriRequest; import org.glowroot.agent.plugin.httpclient.ApacheHttpClientAspect.RequestLine; public class ApacheHttpAsyncClientAspect { private static final FastThreadLocal</*@Nullable*/ AsyncTraceEntry> asyncTraceEntryHolder = new FastThreadLocal</*@Nullable*/ AsyncTraceEntry>(); // the field and method names are verbose to avoid conflict since they will become fields // and methods in all classes that extend org.apache.http.nio.protocol.HttpAsyncResponseConsumer @Mixin("org.apache.http.nio.protocol.HttpAsyncResponseConsumer") public abstract static class HttpAsyncResponseConsumerImpl implements HttpAsyncResponseConsumerMixin { private volatile @Nullable AsyncTraceEntry glowroot$asyncTraceEntry; @Override public @Nullable AsyncTraceEntry glowroot$getAsyncTraceEntry() { return glowroot$asyncTraceEntry; } @Override public void glowroot$setAsyncTraceEntry(@Nullable AsyncTraceEntry asyncTraceEntry) { this.glowroot$asyncTraceEntry = asyncTraceEntry; } } // the field and method names are verbose to avoid conflict since they will become fields // and methods in all classes that extend org.apache.http.concurrent.FutureCallback @Mixin("org.apache.http.concurrent.FutureCallback") public abstract static class FutureCallbackImpl implements FutureCallbackMixin { private volatile @Nullable AuxThreadContext glowroot$auxContext; @Override public @Nullable AuxThreadContext glowroot$getAuxContext() { return glowroot$auxContext; } @Override public void glowroot$setAuxContext(@Nullable AuxThreadContext auxContext) { this.glowroot$auxContext = auxContext; } } // the method names are verbose to avoid conflict since they will become methods in all classes // that extend org.apache.http.nio.protocol.HttpAsyncResponseConsumer public interface HttpAsyncResponseConsumerMixin { @Nullable AsyncTraceEntry glowroot$getAsyncTraceEntry(); void glowroot$setAsyncTraceEntry(@Nullable AsyncTraceEntry asyncTraceEntry); } // the method names are verbose to avoid conflict since they will become methods in all classes // that extend org.apache.http.concurrent.FutureCallback public interface FutureCallbackMixin { @Nullable AuxThreadContext glowroot$getAuxContext(); void glowroot$setAuxContext(@Nullable AuxThreadContext auxContext); } @Pointcut(className = "org.apache.http.nio.client.HttpAsyncClient", methodName = "execute", methodParameterTypes = {"org.apache.http.client.methods.HttpUriRequest", ".."}, nestingGroup = "http-client", timerName = "http client request") public static class ExecuteAdvice { private static final TimerName timerName = Agent.getTimerName(ExecuteAdvice.class); @OnBefore public static @Nullable AsyncTraceEntry onBefore(ThreadContext context, @BindParameter @Nullable HttpUriRequest request) { if (request == null) { return null; } String method = request.getMethod(); if (method == null) { method = ""; } else { method += " "; } URI uriObj = request.getURI(); String uri; if (uriObj == null) { uri = ""; } else { uri = uriObj.toString(); } AsyncTraceEntry asyncTraceEntry = context.startAsyncServiceCallEntry("HTTP", method + Uris.stripQueryString(uri), MessageSupplier.create("http client request: {}{}", method, uri), timerName); asyncTraceEntryHolder.set(asyncTraceEntry); return asyncTraceEntry; } @OnReturn public static void onReturn(@BindTraveler @Nullable AsyncTraceEntry asyncTraceEntry) { if (asyncTraceEntry != null) { asyncTraceEntry.stopSyncTimer(); asyncTraceEntryHolder.set(null); } } @OnThrow public static void onThrow(@BindThrowable Throwable t, @BindTraveler @Nullable AsyncTraceEntry asyncTraceEntry) { if (asyncTraceEntry != null) { asyncTraceEntry.stopSyncTimer(); asyncTraceEntry.endWithError(t); asyncTraceEntryHolder.set(null); } } } @Pointcut(className = "org.apache.http.nio.client.HttpAsyncClient", methodName = "execute", methodParameterTypes = {"org.apache.http.HttpHost", "org.apache.http.HttpRequest", ".."}, nestingGroup = "http-client", timerName = "http client request") public static class ExecuteWithHostAdvice { private static final TimerName timerName = Agent.getTimerName(ExecuteWithHostAdvice.class); @OnBefore public static @Nullable AsyncTraceEntry onBefore(ThreadContext context, @BindParameter @Nullable HttpHost hostObj, @BindParameter @Nullable HttpRequest request) { if (request == null) { return null; } RequestLine requestLine = request.glowroot$getRequestLine(); if (requestLine == null) { return null; } String method = requestLine.getMethod(); if (method == null) { method = ""; } else { method += " "; } String host = hostObj == null ? "" : hostObj.toURI(); String uri = requestLine.getUri(); if (uri == null) { uri = ""; } AsyncTraceEntry asyncTraceEntry = context.startAsyncServiceCallEntry("HTTP", method + Uris.stripQueryString(uri), MessageSupplier.create("http client request: {}{}{}", method, host, uri), timerName); asyncTraceEntryHolder.set(asyncTraceEntry); return asyncTraceEntry; } @OnReturn public static void onReturn(@BindTraveler @Nullable AsyncTraceEntry asyncTraceEntry) { if (asyncTraceEntry != null) { asyncTraceEntry.stopSyncTimer(); asyncTraceEntryHolder.set(null); } } @OnThrow public static void onThrow(@BindThrowable Throwable t, @BindTraveler @Nullable AsyncTraceEntry asyncTraceEntry) { if (asyncTraceEntry != null) { asyncTraceEntry.stopSyncTimer(); asyncTraceEntry.endWithError(t); asyncTraceEntryHolder.set(null); } } } @Pointcut(className = "org.apache.http.nio.client.HttpAsyncClient", methodName = "execute", methodParameterTypes = {"org.apache.http.nio.protocol.HttpAsyncRequestProducer", "org.apache.http.nio.protocol.HttpAsyncResponseConsumer", "org.apache.http.concurrent.FutureCallback"}, nestingGroup = "http-client-producer-consumer") public static class ExecuteWithProducerConsumerAdvice { @OnBefore public static void onBefore(ThreadContext context, @SuppressWarnings("unused") @BindParameter @Nullable Object producer, @BindParameter @Nullable HttpAsyncResponseConsumerMixin consumer, @BindParameter @Nullable FutureCallbackMixin callback) { AsyncTraceEntry asyncTraceEntry = asyncTraceEntryHolder.get(); if (asyncTraceEntry == null) { return; } if (consumer != null) { consumer.glowroot$setAsyncTraceEntry(asyncTraceEntry); } if (callback != null) { callback.glowroot$setAuxContext(context.createAuxThreadContext()); } } } @Pointcut(className = "org.apache.http.nio.client.HttpAsyncClient", methodName = "execute", methodParameterTypes = {"org.apache.http.nio.protocol.HttpAsyncRequestProducer", "org.apache.http.nio.protocol.HttpAsyncResponseConsumer", "org.apache.http.protocol.HttpContext", "org.apache.http.concurrent.FutureCallback"}) public static class ExecuteWithProducerConsumerContextAdvice { @OnBefore public static void onBefore(ThreadContext context, @BindParameter @Nullable Object producer, @BindParameter @Nullable HttpAsyncResponseConsumerMixin consumer, @SuppressWarnings("unused") @BindParameter @Nullable Object httpContext, @BindParameter @Nullable FutureCallbackMixin callback) { ExecuteWithProducerConsumerAdvice.onBefore(context, producer, consumer, callback); } } @Pointcut(className = "org.apache.http.nio.protocol.HttpAsyncResponseConsumer", methodName = "responseCompleted", methodParameterTypes = {"org.apache.http.protocol.HttpContext"}) public static class ResponseCompletedAdvice { @OnBefore public static void onBefore(@BindReceiver HttpAsyncResponseConsumerMixin consumer) { AsyncTraceEntry asyncTraceEntry = consumer.glowroot$getAsyncTraceEntry(); if (asyncTraceEntry != null) { asyncTraceEntry.end(); } } } @Pointcut(className = "org.apache.http.nio.protocol.HttpAsyncResponseConsumer", methodName = "failed", methodParameterTypes = {"java.util.Exception"}) public static class FailedAdvice { @OnBefore public static void onBefore(@BindReceiver HttpAsyncResponseConsumerMixin consumer, @BindParameter @Nullable Exception exception) { AsyncTraceEntry asyncTraceEntry = consumer.glowroot$getAsyncTraceEntry(); if (asyncTraceEntry == null) { return; } if (exception == null) { asyncTraceEntry.endWithError(""); } else { asyncTraceEntry.endWithError(exception); } } } @Pointcut(className = "org.apache.http.concurrent.FutureCallback", methodName = "completed|cancelled|failed", methodParameterTypes = {".."}) public static class CompletedCallbackAdvice { @IsEnabled public static boolean isEnabled() { return true; } @OnBefore public static @Nullable TraceEntry onBefore(@BindReceiver FutureCallbackMixin callback) { AuxThreadContext auxContext = callback.glowroot$getAuxContext(); if (auxContext == null) { return null; } return auxContext.start(); } @OnReturn public static void onReturn(@BindTraveler @Nullable TraceEntry traceEntry) { if (traceEntry != null) { traceEntry.end(); } } @OnThrow public static void onThrow(@BindThrowable Throwable t, @BindTraveler @Nullable TraceEntry traceEntry) { if (traceEntry != null) { traceEntry.endWithError(t); } } } }