跟踪源码一步步解密Pinpoint插件加载过程

1,446 阅读4分钟

我们知道pinpoint插件是通过字节码注入的方式工作的,但是插件究竟是如何被加载的呢?官方文档中给出的执行过程描述如何:

ProfilerPlugin works in the order of following steps:

  1. Pinpoint Agent is started when the JVM starts.
  2. Pinpoint Agent loads all plugins under plugin directory.
  3. Pinpoint Agent invokes ProfilerPlugin.setup(ProfilerPluginSetupContext) for each loaded plugin.
  4. In the setup method, the plugin registers a TransformerCallback to all classes that are going to be transformed.
  5. Target application starts.
  6. Every time a class is loaded, Pinpoint Agent looks for the TransformerCallback registered to the class.
  7. If a TransformerCallback is registered, the Agent invokes it’s doInTransform method.
  8. TransformerCallback modifies the target class’ byte code. (e.g. add interceptors, add fields, etc.)
  9. The modified byte code is returned to the JVM, and the class is loaded with the returned byte code.
  10. Application continues running.
  11. When a modified method is invoked, the injected interceptor’s before and after methods are invoked.
  12. The interceptor records the trace data.

The most important points to consider when writing a plugin are 1) figuring out which methods are interesting enough to warrant tracing, and 2) injecting interceptors to actually trace these methods. These interceptors are used to extract, store, and pass trace data around before they are sent off to the Collector. Interceptors may even cooperate with each other, sharing context between them. Plugins may also aid in tracing by adding getters or even custom fields to the target class so that the interceptors may access them during execution. Pinpoint plugin sample shows you how the TransformerCallback modifies classes and what the injected interceptors do to trace a method.

熟悉pinpoint插件机制的同学对以上过程应该很熟悉了。小编今天想带大家学习的是为什么pinpoint的插件会生效?pinpointagent加载机制是什么样的呢?pinpoint agent加载机制是什么样的呢?

首先我们先来回顾下java agent机制。Agent分为两种,一种是在主程序之前运行的Agent(premain),一种是在主程序之后运行的Agent(agentmain),第二种方式可以理解为前者的升级版,jdk1.6以后提供。这两者的区别这里就不过多介绍。

我们来看下pinpoint-bootstrap-2.0.2.jar包中的MANIFEST.MF中是如何定义的:

Manifest-Version: 1.0
Implementation-Title: pinpoint-bootstrap
Implementation-Version: 2.0.2
Built-By: irteam
Can-Redefine-Classes: true
Implementation-Vendor-Id: com.navercorp.pinpoint
Boot-Class-Path: pinpoint-bootstrap-2.0.2.jar
Implementation-Vendor: Naver Corporation
Premain-Class: com.navercorp.pinpoint.bootstrap.PinpointBootStrap
Pinpoint-Version: 2.0.2
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.3
Build-Jdk: 1.8.0_161

可以看到,pinpoint使用的是第一种agent加载模式。其注入方法入口为com.navercorp.pinpoint.bootstrap.PinpointBootStrap#premain

接下来,我们跟进源码进行阅读。

com.navercorp.pinpoint.bootstrap.PinpointBootStrap#premain
	--> com.navercorp.pinpoint.bootstrap.PinpointBootStrap#appendToBootstrapClassLoader # 加载boot目录下的启动依赖类
		--> java.lang.instrument.Instrumentation#appendToBootstrapClassLoaderSearch # 指定jar包将加载类信息
	--> com.navercorp.pinpoint.bootstrap.PinpointBootStrap#loadModuleBootLoader # 是否支持模块加载,这里为false,相对于的ModuleBootLoader为null
	--> com.navercorp.pinpoint.bootstrap.PinpointStarter#start # 真实启动方法

上文代码中的com.navercorp.pinpoint.bootstrap.PinpointStarter#start方法如下:

boolean start() {
  			// 解析启动应用的agentId
        AgentIds agentIds = this.resolveAgentIds();
        if (agentIds == null) {
            return false;
        } else {
            IdValidator idValidator = new IdValidator();
            idValidator.validate(agentIds);
            String agentId = agentIds.getAgentId();
            if (agentId == null) {
                return false;
            } else {
                String applicationName = agentIds.getApplicationName();
                if (applicationName == null) {
                    return false;
                } else {
                    ContainerResolver containerResolver = new ContainerResolver();
                    boolean isContainer = containerResolver.isContainer();

                    try {
                        // 加载pinpoint配置文件
                        Properties properties = this.loadProperties();
                        ProfilerConfig profilerConfig = new DefaultProfilerConfig(properties);
                        this.saveLogFilePath(this.agentDirectory);
                        this.savePinpointVersion();
                        // 获取lib目录下的jar文件列表
                        URL[] urls = this.resolveLib(this.agentDirectory);
                        // 创建ClassLoader,这里返回ParallelClassLoader
                        ClassLoader agentClassLoader = this.createClassLoader("pinpoint.agent", urls, this.parentClassLoader);
                        if (this.moduleBootLoader != null) {
                            this.logger.info("defineAgentModule");
                            this.moduleBootLoader.defineAgentModule(agentClassLoader, urls);
                        }

                        String bootClass = this.getBootClass();
                        AgentBootLoader agentBootLoader = new AgentBootLoader(bootClass, agentClassLoader);
                        this.logger.info(String.format("pinpoint agent [%s] starting...", bootClass));
                        // 获取plugins目录下jar文件列表
                        List<String> pluginJars = this.agentDirectory.getPlugins();
                        AgentOption option = this.createAgentOption(agentId, applicationName, isContainer, profilerConfig, this.instrumentation, pluginJars, this.agentDirectory);
                        // DefaultAgent
                        Agent pinpointAgent = agentBootLoader.boot(option);
                        // 正式启动,下文讲这个方法
                        pinpointAgent.start();
                        // 主要是注册shutdownHook,关闭连接,重置状态,释放资源等
                        pinpointAgent.registerStopHandler();
                        this.logger.info("pinpoint agent started normally.");
                        return true;
                    } catch (Exception var16) {
                        this.logger.warn("pinpoint start failed.", var16);
                        return false;
                    }
                }
            }
        }
    }

继续看com.navercorp.pinpoint.profiler.context.module.DefaultApplicationContext#start方法做了什么事情。

public void start() {
        // 将拦截器注册适配器缓存起来
        this.interceptorRegistryBinder.bind();
        // 死锁监听
        this.deadlockMonitor.start();
        // agent信息上报
        this.agentInfoSender.start();
        // 状态上报
        this.agentStatMonitor.start();
    }

继续深挖DefaultAgent创建过程:

com.navercorp.pinpoint.bootstrap.AgentBootLoader#boot
	--> com.navercorp.pinpoint.profiler.DefaultAgent#DefaultAgent
		--> com.navercorp.pinpoint.profiler.DefaultAgent#newApplicationContext
			--> com.navercorp.pinpoint.profiler.context.module.DefaultApplicationContext#DefaultApplicationContext
				--> com.google.inject.Guice#createInjector

重点是这一行代码:this.injector = Guice.createInjector(Stage.PRODUCTION, new Module[]{applicationContextModule});

这里使用到了google开源的轻量级依赖注入框架guice,它会根据配置或注解加载相关的类,并自动装配依赖树。

我们重点看以下依赖注入代码:

public class ApplicationServerTypeProvider implements Provider<ServiceType> {
    private static final ServiceType DEFAULT_APPLICATION_TYPE;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final ProfilerConfig profilerConfig;
    private final ServiceType configuredApplicationType;
    private final Provider<PluginContextLoadResult> pluginContextLoadResultProvider;

    @Inject
    public ApplicationServerTypeProvider(ProfilerConfig profilerConfig, @ConfiguredApplicationType ServiceType configuredApplicationType, Provider<PluginContextLoadResult> pluginContextLoadResultProvider) {
        this.profilerConfig = (ProfilerConfig)Assert.requireNonNull(profilerConfig, "profilerConfig");
        this.configuredApplicationType = (ServiceType)Assert.requireNonNull(configuredApplicationType, "configuredApplicationType");
        this.pluginContextLoadResultProvider = pluginContextLoadResultProvider;
    }
  
  ......
}

注入ApplicationServerTypeProvider需要3个属性ProfilerConfig,ServiceType和Provider。

其中第3个属性代码如下:

public class PluginContextLoadResultProvider implements Provider<PluginContextLoadResult> {
    private final ProfilerPluginContextLoader profilerPluginContextLoader;
    private final ClassLoader pluginClassLoader;

    @Inject
    public PluginContextLoadResultProvider(ProfilerPluginContextLoader profilerPluginContextLoader, @PluginClassLoader ClassLoader pluginClassLoader) {
        this.profilerPluginContextLoader = (ProfilerPluginContextLoader)Assert.requireNonNull(profilerPluginContextLoader, "profilerPluginContextLoader");
        this.pluginClassLoader = (ClassLoader)Assert.requireNonNull(pluginClassLoader, "pluginClassLoader");
    }

    public PluginContextLoadResult get() {
        return new DefaultPluginContextLoadResult(this.profilerPluginContextLoader, this.pluginClassLoader);
    }
}

这里的com.navercorp.pinpoint.profiler.context.provider.PluginContextLoadResultProvider#get方法会创建DefaultPluginContextLoadResult对象。

public class DefaultPluginContextLoadResult implements PluginContextLoadResult {
  ......
  
	public DefaultPluginContextLoadResult(ProfilerPluginContextLoader profilerPluginContextLoader, ClassLoader pluginClassLoader) {
        Assert.requireNonNull(profilerPluginContextLoader, "profilerPluginConfigurer");
        Assert.requireNonNull(pluginClassLoader, "pluginClassLoader");
        ProfilerPluginLoader profilerPluginLoader = new ProfilerPluginLoader();
    	  // 通过SPI机制查找所有ProfilerPlugin实现类
        List<ProfilerPlugin> profilerPlugins = profilerPluginLoader.load(pluginClassLoader);
        // 这里就开始加载所有的插件了
        this.pluginsSetupResult = profilerPluginContextLoader.load(profilerPlugins); 
    }	
  
  ......
}

public class ProfilerPluginLoader implements PinpointPluginLoader<ProfilerPlugin> {

    // 这个方法是不是非常的熟悉了,这里就是使用java原生SPI机制查找到所有的ProfilerPlugin接口实现类了,也就是从这里,我们自定义的插件被加载了
    @Override
    public List<ProfilerPlugin> load(ClassLoader classLoader) {
        List<ProfilerPlugin> profilerPlugins = new ArrayList<ProfilerPlugin>();
        ServiceLoader<ProfilerPlugin> serviceLoader = ServiceLoader.load(ProfilerPlugin.class, classLoader);
        for (ProfilerPlugin profilerPlugin : serviceLoader) {
            profilerPlugins.add(profilerPlugin);
        }
        return profilerPlugins;
    }
}

继续往下找,最终我们以下代码中可以看到调用插件setup方法了com.navercorp.pinpoint.profiler.plugin.DefaultPluginSetup#setupPlugin

至此,我们终于捋清楚了pinpoint-agent的加载插件的机制,代码虽然有点负载,但总的逻辑还算比较清晰的。

回顾下大致过程如下:

  1. com.navercorp.pinpoint.bootstrap.PinpointBootStrap#premain
  2. com.navercorp.pinpoint.bootstrap.PinpointStarter#start
  3. com.navercorp.pinpoint.bootstrap.AgentBootLoader#boot
  4. com.navercorp.pinpoint.profiler.context.module.DefaultApplicationContext#DefaultApplicationContext
  5. com.google.inject.Guice#createInjector
  6. com.navercorp.pinpoint.profiler.context.provider.ApplicationServerTypeProvider#ApplicationServerTypeProvider
  7. com.navercorp.pinpoint.profiler.context.provider.PluginContextLoadResultProvider#get
  8. com.navercorp.pinpoint.profiler.plugin.DefaultPluginContextLoadResult#DefaultPluginContextLoadResult
  9. com.navercorp.pinpoint.profiler.plugin.DefaultProfilerPluginContextLoader#load
  10. com.navercorp.pinpoint.profiler.plugin.DefaultPluginSetup#setupPlugin

参考文档:

Pinpoint官方文档地址:pinpoint-apm.github.io/pinpoint/ma…

插件定制参考实现:github.com/pinpoint-ap…