/*
* 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.redis;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
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.MethodFilters;
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 static com.navercorp.pinpoint.common.util.VarArgs.va;
/**
*
* @author jaehong.kim
*
*/
public class RedisPlugin implements ProfilerPlugin, TransformTemplateAware {
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
@Override
public void setup(ProfilerPluginSetupContext context) {
final RedisPluginConfig config = new RedisPluginConfig(context.getConfig());
if (logger.isInfoEnabled()) {
logger.info("RedisPlugin config:{}", config);
}
final boolean pipelineEnabled = config.isPipelineEnabled();
// jedis
addJedisClassEditors(config);
addProtocolClassEditor();
if (pipelineEnabled) {
// jedis pipeline
addJedisClientClassEditor();
addJedisPipelineClassEditors(config);
}
}
// Jedis & BinaryJedis
private void addJedisClassEditors(RedisPluginConfig config) {
addJedisExtendedClassEditor(config, "redis.clients.jedis.BinaryJedis", new TransformHandler() {
@Override
public void handle(InstrumentClass target) throws InstrumentException {
target.addField(RedisConstants.END_POINT_ACCESSOR);
}
});
// Jedis extends BinaryJedis
addJedisExtendedClassEditor(config, "redis.clients.jedis.Jedis", null);
}
private void addJedisExtendedClassEditor(final RedisPluginConfig config, final String targetClassName, final TransformHandler handler) {
transformTemplate.transform(targetClassName, new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
if (handler != null) {
handler.handle(target);
}
final InstrumentMethod constructorEditorBuilderArg1 = target.getConstructor("java.lang.String");
if (constructorEditorBuilderArg1 != null) {
constructorEditorBuilderArg1.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisConstructorInterceptor");
}
final InstrumentMethod constructorEditorBuilderArg2 = target.getConstructor("java.lang.String", "int");
if (constructorEditorBuilderArg2 != null) {
constructorEditorBuilderArg2.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisConstructorInterceptor");
}
final InstrumentMethod constructorEditorBuilderArg3 = target.getConstructor("java.lang.String", "int", "int");
if (constructorEditorBuilderArg3 != null) {
constructorEditorBuilderArg3.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisConstructorInterceptor");
}
final InstrumentMethod constructorEditorBuilderArg4 = target.getConstructor("java.net.URI");
if (constructorEditorBuilderArg4 != null) {
constructorEditorBuilderArg4.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisConstructorInterceptor");
}
final InstrumentMethod constructorEditorBuilderArg5 = target.getConstructor("redis.clients.jedis.JedisShardInfo");
if (constructorEditorBuilderArg5 != null) {
constructorEditorBuilderArg5.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisConstructorInterceptor");
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.chain(MethodFilters.name(JedisMethodNames.get()), MethodFilters.modifierNot(MethodFilters.SYNTHETIC)))) {
try {
method.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisMethodInterceptor", va(config.isIo()));
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("Unsupported method " + method, e);
}
}
}
return target.toBytecode();
}
});
}
// Client
private void addJedisClientClassEditor() {
transformTemplate.transform("redis.clients.jedis.Client", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(RedisConstants.END_POINT_ACCESSOR);
final InstrumentMethod constructorEditorBuilderArg1 = target.getConstructor("java.lang.String");
if (constructorEditorBuilderArg1 != null) {
constructorEditorBuilderArg1.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisClientConstructorInterceptor");
}
final InstrumentMethod constructorEditorBuilderArg2 = target.getConstructor("java.lang.String", "int");
if (constructorEditorBuilderArg2 != null) {
constructorEditorBuilderArg2.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisClientConstructorInterceptor");
}
return target.toBytecode();
}
});
}
private void addProtocolClassEditor() {
transformTemplate.transform("redis.clients.jedis.Protocol", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.chain(MethodFilters.name("sendCommand", "read"), MethodFilters.modifierNot(Modifier.PRIVATE)))) {
method.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.ProtocolSendCommandAndReadMethodInterceptor");
}
return target.toBytecode();
}
});
}
// Pipeline
private void addJedisPipelineClassEditors(RedisPluginConfig config) {
addJedisPipelineBaseExtendedClassEditor(config, "redis.clients.jedis.PipelineBase", null);
// MultikeyPipellineBase extends PipelineBase
addJedisPipelineBaseExtendedClassEditor(config, "redis.clients.jedis.MultiKeyPipelineBase", null);
// Pipeline extends PipelineBase
addJedisPipelineBaseExtendedClassEditor(config, "redis.clients.jedis.Pipeline", new TransformHandler() {
@Override
public void handle(InstrumentClass target) throws InstrumentException {
target.addField(RedisConstants.END_POINT_ACCESSOR);
final InstrumentMethod setClientMethodEditorBuilder = target.getDeclaredMethod("setClient", "redis.clients.jedis.Client");
if (setClientMethodEditorBuilder != null) {
setClientMethodEditorBuilder.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisPipelineSetClientMethodInterceptor");
}
final InstrumentMethod constructorEditorBuilder = target.getConstructor("redis.clients.jedis.Client");
if (constructorEditorBuilder != null) {
constructorEditorBuilder.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisPipelineConstructorInterceptor");
}
}
});
}
private void addJedisPipelineBaseExtendedClassEditor(final RedisPluginConfig config, String targetClassName, final TransformHandler handler) {
transformTemplate.transform(targetClassName, new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
if (handler != null) {
handler.handle(target);
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.chain(MethodFilters.name(JedisPipelineMethodNames.get()), MethodFilters.modifierNot(MethodFilters.SYNTHETIC)))) {
try {
method.addInterceptor("com.navercorp.pinpoint.plugin.redis.interceptor.JedisPipelineMethodInterceptor", va(config.isIo()));
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("Unsupported method " + method, e);
}
}
}
return target.toBytecode();
}
});
}
private interface TransformHandler {
void handle(InstrumentClass target) throws InstrumentException;
}
@Override
public void setTransformTemplate(TransformTemplate transformTemplate) {
this.transformTemplate = transformTemplate;
}
}