/*
* Copyright 2014 NAVER Corp.
*
* 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 com.navercorp.pinpoint.plugin.okhttp;
import java.security.ProtectionDomain;
import com.navercorp.pinpoint.bootstrap.async.AsyncTraceIdAccessor;
import com.navercorp.pinpoint.bootstrap.instrument.*;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplate;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplateAware;
import com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor;
import com.navercorp.pinpoint.bootstrap.logging.PLogger;
import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
import com.navercorp.pinpoint.bootstrap.plugin.ObjectFactory;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;
import static com.navercorp.pinpoint.common.util.VarArgs.va;
/**
* @author jaehong.kim
*
*/
public class OkHttpPlugin implements ProfilerPlugin, TransformTemplateAware {
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
@Override
public void setup(ProfilerPluginSetupContext context) {
final OkHttpPluginConfig config = new OkHttpPluginConfig(context.getConfig());
logger.debug("[OkHttp] Initialized config={}", config);
logger.debug("[OkHttp] Add Call class.");
addCall();
logger.debug("[OkHttp] Add Dispatcher class.");
addDispatcher();
logger.debug("[OkHttp] Add AsyncCall class.");
addAsyncCall();
addHttpEngine(config);
addRequestBuilder();
}
private void addCall() {
transformTemplate.transform("com.squareup.okhttp.Call", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("execute", "enqueue", "cancel"))) {
method.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.CallMethodInterceptor");
}
return target.toBytecode();
}
});
}
private void addDispatcher() {
transformTemplate.transform("com.squareup.okhttp.Dispatcher", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("execute", "cancel"))) {
logger.debug("[OkHttp] Add Dispatcher.execute | cancel interceptor.");
method.addInterceptor(BasicMethodInterceptor.class.getName(), va(OkHttpConstants.OK_HTTP_CLIENT_INTERNAL));
}
InstrumentMethod enqueueMethod = target.getDeclaredMethod("enqueue", "com.squareup.okhttp.Call$AsyncCall");
if (enqueueMethod != null) {
logger.debug("[OkHttp] Add Dispatcher.enqueue interceptor.");
enqueueMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.DispatcherEnqueueMethodInterceptor");
}
return target.toBytecode();
}
});
}
private void addAsyncCall() {
transformTemplate.transform("com.squareup.okhttp.Call$AsyncCall", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
InstrumentMethod executeMethod = target.getDeclaredMethod("execute");
if (executeMethod != null) {
logger.debug("[OkHttp] Add AsyncCall.execute interceptor.");
executeMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.AsyncCallExecuteMethodInterceptor");
}
return target.toBytecode();
}
});
}
private void addHttpEngine(final OkHttpPluginConfig config) {
transformTemplate.transform("com.squareup.okhttp.internal.http.HttpEngine", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
target.addGetter(OkHttpConstants.USER_REQUEST_GETTER, OkHttpConstants.FIELD_USER_REQUEST);
target.addGetter(OkHttpConstants.USER_RESPONSE_GETTER, OkHttpConstants.FIELD_USER_RESPONSE);
target.addGetter(OkHttpConstants.CONNECTION_GETTER, OkHttpConstants.FIELD_CONNECTION);
InstrumentMethod sendRequestMethod = target.getDeclaredMethod("sendRequest");
if (sendRequestMethod != null) {
logger.debug("[OkHttp] Add HttpEngine.sendRequest interceptor.");
final ObjectFactory objectFactory = ObjectFactory.byConstructor("com.navercorp.pinpoint.plugin.okhttp.OkHttpPluginConfig", instrumentor.getProfilerConfig());
sendRequestMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.HttpEngineSendRequestMethodInterceptor", va(objectFactory));
}
InstrumentMethod connectMethod = target.getDeclaredMethod("connect");
if (connectMethod != null) {
logger.debug("[OkHttp] Add HttpEngine.connect interceptor.");
connectMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.HttpEngineConnectMethodInterceptor");
}
InstrumentMethod readResponseMethod = target.getDeclaredMethod("readResponse");
if (readResponseMethod != null) {
logger.debug("[OkHttp] Add HttpEngine.connect interceptor.");
readResponseMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.HttpEngineReadResponseMethodInterceptor", va(config.isStatusCode()));
}
return target.toBytecode();
}
});
}
private void addRequestBuilder() {
transformTemplate.transform("com.squareup.okhttp.Request$Builder", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
InstrumentMethod buildMethod = target.getDeclaredMethod("build");
if (buildMethod != null) {
logger.debug("[OkHttp] Add Request.Builder.build interceptor.");
if(instrumentor.exist(loader, "com.squareup.okhttp.HttpUrl")) {
// over 2.4.0
target.addGetter(OkHttpConstants.HTTP_URL_GETTER, OkHttpConstants.FIELD_HTTP_URL);
buildMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.RequestBuilderBuildMethodInterceptor");
} else {
// 2.0 ~ 2.3
target.addGetter(OkHttpConstants.URL_GETTER, OkHttpConstants.FIELD_HTTP_URL);
buildMethod.addInterceptor("com.navercorp.pinpoint.plugin.okhttp.interceptor.RequestBuilderBuildMethodBackwardCompatibilityInterceptor");
}
}
return target.toBytecode();
}
});
}
@Override
public void setTransformTemplate(TransformTemplate transformTemplate) {
this.transformTemplate = transformTemplate;
}
}