/*
* Copyright 2016 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.vertx;
import com.navercorp.pinpoint.bootstrap.async.AsyncTraceIdAccessor;
import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig;
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.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.common.annotations.InterfaceStability;
import java.security.ProtectionDomain;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
/**
* @author jaehong.kim
*/
@InterfaceStability.Unstable
public class VertxPlugin implements ProfilerPlugin, TransformTemplateAware {
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
@Override
public void setup(ProfilerPluginSetupContext context) {
final VertxConfig config = new VertxConfig(context.getConfig());
if (!config.isEnable() || (!config.isEnableHttpServer() && !config.isEnableHttpClient())) {
return;
}
// for vertx.io 3.x
final VertxDetector vertxDetector = new VertxDetector(config.getBootstrapMains());
context.addApplicationTypeDetector(vertxDetector);
boolean hasHandlers = false;
for (String className : config.getHandlerClassNames()) {
final String classNameTrim = className.trim();
if (classNameTrim.isEmpty()) {
continue;
}
if (logger.isInfoEnabled()) {
logger.info("Adding Vertx Handler {}.", classNameTrim);
}
addHandlerInterceptor(classNameTrim);
hasHandlers = true;
}
if (hasHandlers) {
// runOnContext, executeBlocking
addVertxImpl();
}
if (config.isEnableHttpServer()) {
if (logger.isInfoEnabled()) {
logger.info("Adding Vertx HTTP Server.");
}
addServerConnection();
addHttpServerRequestImpl();
addHttpServerResponseImpl();
}
if (config.isEnableHttpClient()) {
if (logger.isInfoEnabled()) {
logger.info("Adding Vertx HTTP Client.");
}
addHttpClientImpl();
addHttpClientRequestImpl();
addHttpClientStream();
addHttpClientResponseImpl();
}
}
private void addHandlerInterceptor(final String className) {
transformTemplate.transform(className, new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
final InstrumentMethod handleMethod = target.getDeclaredMethod("handle", "java.lang.Object");
if (handleMethod != null) {
handleMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HandlerInterceptor");
}
return target.toBytecode();
}
});
}
private void addVertxImpl() {
transformTemplate.transform("io.vertx.core.impl.VertxImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
final InstrumentMethod runOnContextMethod = target.getDeclaredMethod("runOnContext", "io.vertx.core.Handler");
if (runOnContextMethod != null) {
runOnContextMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.VertxImplRunOnContextInterceptor");
}
final InstrumentMethod executeBlockingMethod = target.getDeclaredMethod("executeBlocking", "io.vertx.core.Handler", "boolean", "io.vertx.core.Handler");
if (executeBlockingMethod != null) {
executeBlockingMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.VertxImplExecuteBlockingInterceptor");
}
return target.toBytecode();
}
});
}
private void addServerConnection() {
transformTemplate.transform("io.vertx.core.http.impl.ServerConnection", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
final InstrumentMethod handleRequestMethod = target.getDeclaredMethod("handleRequest", "io.vertx.core.http.impl.HttpServerRequestImpl", "io.vertx.core.http.impl.HttpServerResponseImpl");
if (handleRequestMethod != null) {
// entry point & set asynchronous of req, res.
handleRequestMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.ServerConnectionHandleRequestInterceptor");
}
return target.toBytecode();
}
});
}
private void addHttpServerRequestImpl() {
transformTemplate.transform("io.vertx.core.http.impl.HttpServerRequestImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
final InstrumentMethod handleExceptionMethod = target.getDeclaredMethod("handleException", "java.lang.Throwable");
if (handleExceptionMethod != null) {
handleExceptionMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HandleExceptionInterceptor");
}
return target.toBytecode();
}
});
}
private void addHttpServerResponseImpl() {
transformTemplate.transform("io.vertx.core.http.impl.HttpServerResponseImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
final InstrumentMethod endMethod = target.getDeclaredMethod("end");
if (endMethod != null) {
endMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpServerResponseImplInterceptor");
}
final InstrumentMethod endBufferMethod = target.getDeclaredMethod("end", "io.vertx.core.buffer.Buffer");
if (endBufferMethod != null) {
endBufferMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpServerResponseImplInterceptor");
}
final InstrumentMethod closeMethod = target.getDeclaredMethod("close");
if (closeMethod != null) {
closeMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpServerResponseImplInterceptor");
}
for (InstrumentMethod sendFileMethod : target.getDeclaredMethods(MethodFilters.name("sendFile"))) {
if (sendFileMethod != null) {
sendFileMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpServerResponseImplInterceptor");
}
}
final InstrumentMethod handleDrainedMethod = target.getDeclaredMethod("handleDrained");
if (handleDrainedMethod != null) {
handleDrainedMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpServerResponseImplInterceptor");
}
final InstrumentMethod handleExceptionMethod = target.getDeclaredMethod("handleException", "java.lang.Throwable");
if (handleExceptionMethod != null) {
handleExceptionMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HandleExceptionInterceptor");
}
final InstrumentMethod handleClosedMethod = target.getDeclaredMethod("handleClosed");
if (handleClosedMethod != null) {
handleClosedMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpServerResponseImplInterceptor");
}
return target.toBytecode();
}
});
}
private void addHttpClientImpl() {
transformTemplate.transform("io.vertx.core.http.impl.HttpClientImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("requestAbs", "request", "get", "getAbs", "getNow", "post", "postAbs", "head", "headAbs", "headNow", "options", "optionsAbs", "optionsNow", "put", "putAbs", "delete", "deleteAbs"))) {
if (method != null) {
method.addScopedInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientImplInterceptor", VertxConstants.HTTP_CLIENT_REQUEST_SCOPE);
}
}
final InstrumentMethod doRequestMethod = target.getDeclaredMethod("doRequest", "io.vertx.core.http.HttpMethod", "java.lang.String", "int", "java.lang.String", "io.vertx.core.MultiMap");
if (doRequestMethod != null) {
doRequestMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientImplDoRequestInterceptor");
}
// connect
final InstrumentMethod getConnectionForRequestMethod = target.getDeclaredMethod("getConnectionForRequest", "int", "java.lang.String", "io.vertx.core.http.impl.Waiter");
if (getConnectionForRequestMethod != null) {
getConnectionForRequestMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientImplGetConnectionForRequest");
}
return target.toBytecode();
}
});
}
private void addHttpClientRequestImpl() {
transformTemplate.transform("io.vertx.core.http.impl.HttpClientRequestImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
// for HttpClientResponseImpl.
final InstrumentMethod doHandleResponseMethod = target.getDeclaredMethod("doHandleResponse", "io.vertx.core.http.impl.HttpClientResponseImpl");
if (doHandleResponseMethod != null) {
doHandleResponseMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientRequestImplDoHandleResponseInterceptor");
}
// for completionHandler, writeHead(), connect().
final InstrumentMethod sendHeadMethod = target.getDeclaredMethod("sendHead", "io.vertx.core.Handler");
if (sendHeadMethod != null) {
sendHeadMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientRequestImplInterceptor");
}
// for stream.writeHeadWithContent().
final InstrumentMethod writeMethod = target.getDeclaredMethod("write", "io.netty.buffer.ByteBuf", "boolean");
if (writeMethod != null) {
writeMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientRequestImplInterceptor");
}
// for stream.writeHead(), stream.writeHeadWithContent(), headersCompletionHandler.
final InstrumentMethod connectedMethod = target.getDeclaredMethod("connected", "io.vertx.core.http.impl.HttpClientStream", "io.vertx.core.Handler");
if (connectedMethod != null) {
connectedMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientRequestImplInterceptor");
}
// handle.
final InstrumentMethod handleDrainedMethod = target.getDeclaredMethod("handleDrained", "java.lang.Throwable");
if (handleDrainedMethod != null) {
handleDrainedMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientRequestImplInterceptor");
}
final InstrumentMethod handleExceptionMethod = target.getDeclaredMethod("handleException", "java.lang.Throwable");
if (handleExceptionMethod != null) {
handleExceptionMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HandleExceptionInterceptor");
}
return target.toBytecode();
}
});
}
private void addHttpClientStream() {
transformTemplate.transform("io.vertx.core.http.impl.ClientConnection", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
// add pinpoint headers.
final InstrumentMethod prepareHeadersMethod = target.getDeclaredMethod("prepareHeaders", "io.netty.handler.codec.http.HttpRequest", "java.lang.String", "boolean");
if (prepareHeadersMethod != null) {
prepareHeadersMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientStreamInterceptor");
}
return target.toBytecode();
}
});
}
private void addHttpClientResponseImpl() {
transformTemplate.transform("io.vertx.core.http.impl.HttpClientResponseImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
final InstrumentMethod handleEndMethod = target.getDeclaredMethod("handleEnd", "io.vertx.core.buffer.Buffer", "io.vertx.core.MultiMap");
if (handleEndMethod != null) {
handleEndMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HttpClientResponseImplInterceptor");
}
final InstrumentMethod handleExceptionMethod = target.getDeclaredMethod("handleException", "java.lang.Throwable");
if (handleExceptionMethod != null) {
handleExceptionMethod.addInterceptor("com.navercorp.pinpoint.plugin.vertx.interceptor.HandleExceptionInterceptor");
}
return target.toBytecode();
}
});
}
@Override
public void setTransformTemplate(TransformTemplate transformTemplate) {
this.transformTemplate = transformTemplate;
}
}