我们知道pinpoint插件是通过字节码注入的方式工作的,但是插件究竟是如何被加载的呢?官方文档中给出的执行过程描述如何:
ProfilerPlugin
works in the order of following steps:
- Pinpoint Agent is started when the JVM starts.
- Pinpoint Agent loads all plugins under
plugin
directory. - Pinpoint Agent invokes
ProfilerPlugin.setup(ProfilerPluginSetupContext)
for each loaded plugin. - In the
setup
method, the plugin registers aTransformerCallback
to all classes that are going to be transformed. - Target application starts.
- Every time a class is loaded, Pinpoint Agent looks for the
TransformerCallback
registered to the class. - If a
TransformerCallback
is registered, the Agent invokes it’sdoInTransform
method. TransformerCallback
modifies the target class’ byte code. (e.g. add interceptors, add fields, etc.)- The modified byte code is returned to the JVM, and the class is loaded with the returned byte code.
- Application continues running.
- When a modified method is invoked, the injected interceptor’s
before
andafter
methods are invoked. - 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的插件会生效?
首先我们先来回顾下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的加载插件的机制,代码虽然有点负载,但总的逻辑还算比较清晰的。
回顾下大致过程如下:
- com.navercorp.pinpoint.bootstrap.PinpointBootStrap#premain
- com.navercorp.pinpoint.bootstrap.PinpointStarter#start
- com.navercorp.pinpoint.bootstrap.AgentBootLoader#boot
- com.navercorp.pinpoint.profiler.context.module.DefaultApplicationContext#DefaultApplicationContext
- com.google.inject.Guice#createInjector
- com.navercorp.pinpoint.profiler.context.provider.ApplicationServerTypeProvider#ApplicationServerTypeProvider
- com.navercorp.pinpoint.profiler.context.provider.PluginContextLoadResultProvider#get
- com.navercorp.pinpoint.profiler.plugin.DefaultPluginContextLoadResult#DefaultPluginContextLoadResult
- com.navercorp.pinpoint.profiler.plugin.DefaultProfilerPluginContextLoader#load
- com.navercorp.pinpoint.profiler.plugin.DefaultPluginSetup#setupPlugin
参考文档:
Pinpoint官方文档地址:pinpoint-apm.github.io/pinpoint/ma…
插件定制参考实现:github.com/pinpoint-ap…