/*
* 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.hystrix;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClass;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentException;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentMethod;
import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor;
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.logging.PLogger;
import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;
import com.navercorp.pinpoint.plugin.hystrix.transformer.HystrixCommandTransformer;
import java.security.ProtectionDomain;
/**
* Any Pinpoint profiler plugin must implement ProfilerPlugin interface.
* ProfilerPlugin declares only one method {@link #setup(ProfilerPluginSetupContext)}.
* You should implement the method to do whatever you need to setup your plugin with the passed ProfilerPluginSetupContext object.
*
* @author Jiaqi Feng
* @author HyunGil Jeong
*/
public class HystrixPlugin implements ProfilerPlugin, TransformTemplateAware {
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
@Override
public void setup(ProfilerPluginSetupContext context) {
HystrixPluginConfig config = new HystrixPluginConfig(context.getConfig());
if (config.isTraceHystrix()) {
addTransformers(config.getNumHystrixCommandAnonymousInnerClass());
}
}
private void addTransformers(int numHystrixCommandAnonymousLocalClass) {
transformTemplate.transform("com.netflix.hystrix.HystrixCommand", new HystrixCommandTransformer());
/*
* After com.netflix.hystrix:hystrix-core:1.4.0 the api changed.
* The run() and getFallback() methods of the subclass will be called by HystrixCommand's
* anonymous inner classes.
*
* Safest way (although ugly) is to predefine the anonymous inner class names and check each of them to inject
* their appropriate interceptors as they are loaded.
*
* The anonymous inner classes that should be modified may differ according to hystrix-core version. This is
* simply something that we'll have to keep updating if any changes occur.
* (Any breaking changes can be detected through integration tests.)
*/
// number of anonymous inner classes to look for.
// We start with 3 only because the most recent version requires this many. May be better to make this
// configurable but for now let's just hard-code it.
final int numAnonymousInnerClassesToTest = numHystrixCommandAnonymousLocalClass;
for (int i = 0; i < numAnonymousInnerClassesToTest; ++i) {
String anonymousInnerClassName = "com.netflix.hystrix.HystrixCommand$" + (i + 1);
logger.debug("Registering transformer for {}", anonymousInnerClassName);
transformTemplate.transform(anonymousInnerClassName, 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);
if (target.hasEnclosingMethod("getExecutionObservable")) {
// 1.4.0 ~ 1.5.2 - void call(Subscriber<? super R> s)
InstrumentMethod method = target.getDeclaredMethod("call", "rx.Subscriber");
// 1.5.3+ - Observable<R> call()
if (method == null) {
method = target.getDeclaredMethod("call");
// 1.5.4+ - May be another anonymous class inside getExecutionObservable()
if (!method.getReturnType().equals("rx.Observable")) {
return null;
}
}
if (method != null) {
// Add getter for the enclosing instance
target.addGetter("com.navercorp.pinpoint.plugin.hystrix.field.EnclosingInstanceFieldGetter", "this$0");
method.addInterceptor("com.navercorp.pinpoint.plugin.hystrix.interceptor.ExecutionObservableCallInterceptor");
return target.toBytecode();
} else {
logger.warn("Unknown version of HystrixCommand.getExecutionObservable() detected");
return null;
}
} else if (target.hasEnclosingMethod("getFallbackObservable")) {
// 1.4.0 ~ 1.5.2 - void call(Subscriber<? super R> s)
InstrumentMethod method = target.getDeclaredMethod("call", "rx.Subscriber");
// 1.5.3+ - Observable<R> call()
if (method == null) {
method = target.getDeclaredMethod("call");
}
if (method != null) {
// Add getter for the enclosing instance
target.addGetter("com.navercorp.pinpoint.plugin.hystrix.field.EnclosingInstanceFieldGetter", "this$0");
method.addInterceptor("com.navercorp.pinpoint.plugin.hystrix.interceptor.FallbackObservableCallInterceptor");
return target.toBytecode();
} else {
logger.warn("Unknown version of HystrixCommand.getFallbackObservable detected");
return null;
}
} else {
return null;
}
}
});
}
}
@Override
public void setTransformTemplate(TransformTemplate transformTemplate) {
this.transformTemplate = transformTemplate;
}
}