Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控、方法执行详情查看、应用状态信息监控等功能。目前公司通过开发pinpoint来满足链路分析、中间件增强、环境隔离等需求,图1为pinpoint的基本结构。
总的来说,pinpoint-agent通过在各种中间件方法上埋点来采集信息,数据上报到pinpoint-collector,由pinpoint-collector接收并持久化到hbase,pinpoint-web负责渲染,在pinpoint-collector和pinpoint-web之间还会维护一条长连接,负责传输实时数据。
本文主要分析pinpoint-agent的启动过程,pinpoint的版本为1.7.2,与目前最新版有一定差异(当前最新版为1.9.0)。
Pinpoint Agent启动过程
- 启动入口为PinpointBootStrap.premain方法
- 设置启动状态,解析启动参数,查找核心jar文件,使用BootstrapClassLoader加载核心jar中的类
- 实例化PinpointStarter,调用start方法
public static void premain(String agentArgs, Instrumentation instrumentation) {
if (agentArgs == null) {
agentArgs = "";
}
logger.info(ProductInfo.NAME + " agentArgs:" + agentArgs);
final boolean success = STATE.start();
if (!success) {
logger.warn("pinpoint-bootstrap already started. skipping agent loading.");
return;
}
Map<String, String> agentArgsMap = argsToMap(agentArgs);
final ClassPathResolver classPathResolver = new AgentDirBaseClassPathResolver();
if (!classPathResolver.verify()) {
logger.warn("Agent Directory Verify fail. skipping agent loading.");
logPinpointAgentLoadFail();
return;
}
BootstrapJarFile bootstrapJarFile = classPathResolver.getBootstrapJarFile();
appendToBootstrapClassLoader(instrumentation, bootstrapJarFile);
PinpointStarter bootStrap = new PinpointStarter(agentArgsMap, bootstrapJarFile, classPathResolver, instrumentation);
if (!bootStrap.start()) {
logPinpointAgentLoadFail();
}
}
- 读取agentId和applicationName,扫描plugin目录下的jar文件
- 加载plugin目录下jar包的TraceMetadataProvider实现类,并执行setup方法,这个方法主要向上下文注册serviceType和AnnotationKey
- 实例化ServiceTypeRegistry和AnnotationKeyRegistry
- 解析并加载pinpoint.config配置,创建AgentClassLoader和AgentOption,实例化DefaultAgent,创建ApplicationContext,使用Guice框架进行依赖注入,大部分类在这个阶段被注册到DI容器
- 调用DefaultAgent的start方法,启动死锁监控线程、agent数据上报线程
- 注册ShutdownHook,JVM准备退出时执行DefaultAgent的stop方法,关闭应用上下文
boolean start() {
final IdValidator idValidator = new IdValidator();
final String agentId = idValidator.getAgentId();
if (agentId == null) {
return false;
}
final String applicationName = idValidator.getApplicationName();
if (applicationName == null) {
return false;
}
URL[] pluginJars = classPathResolver.resolvePlugins();
// TODO using PLogger instead of CommonLogger
CommonLoggerFactory loggerFactory = StdoutCommonLoggerFactory.INSTANCE;
TraceMetadataLoaderService typeLoaderService = new DefaultTraceMetadataLoaderService(pluginJars, loggerFactory);
ServiceTypeRegistryService serviceTypeRegistryService = new DefaultServiceTypeRegistryService(typeLoaderService, loggerFactory);
AnnotationKeyRegistryService annotationKeyRegistryService = new DefaultAnnotationKeyRegistryService(typeLoaderService, loggerFactory);
String configPath = getConfigPath(classPathResolver);
if (configPath == null) {
return false;
}
// set the path of log file as a system property
saveLogFilePath(classPathResolver);
savePinpointVersion();
try {
// Is it right to load the configuration in the bootstrap?
ProfilerConfig profilerConfig = DefaultProfilerConfig.load(configPath);
// this is the library list that must be loaded
List<URL> libUrlList = resolveLib(classPathResolver);
AgentClassLoader agentClassLoader = new AgentClassLoader(libUrlList.toArray(new URL[libUrlList.size()]));
final String bootClass = getBootClass();
agentClassLoader.setBootClass(bootClass);
logger.info("pinpoint agent [" + bootClass + "] starting...");
AgentOption option = createAgentOption(agentId, applicationName, profilerConfig, instrumentation, pluginJars, bootstrapJarFile, serviceTypeRegistryService, annotationKeyRegistryService);
Agent pinpointAgent = agentClassLoader.boot(option);
pinpointAgent.start();
registerShutdownHook(pinpointAgent);
logger.info("pinpoint agent started normally.");
} catch (Exception e) {
// unexpected exception that did not be checked above
logger.warn(ProductInfo.NAME + " start failed.", e);
return false;
}
return true;
}
总结:
简单来说,pinpoint agent的启动过程为:
PinpointBootStrap.premain->
解析bootstrap jar、agent lib、plugin等jar路径->
将pinpoint核心类交给BootstrapClassLoader加载->
new PinpointStarter().start->
通过反射实例化DefaultAgent->new ApplicationContext()->
Guice.createInjector(),使用Guice框架创建并维护pinpoint中的bean,在这个阶段会加载pinpoint中的所有plugin,并执行plugin的初始化过程->
pinpointAgent.start->启动deadlockMonitor、agent数据上报等线程->
添加shutdownHook,jvm退出时关闭agent。
本文简单梳理了pinpoint的启动过程,总得来说比较易懂(对比spring、netty等),如果有时间,后面可能会写如何写一个简单的pinpoint插件来增强使用的框架或中间件,也可能会分析一些常用的中间件pinpoint插件。