# SkyWalking源码解析:Probes的加载机制
# 1. 前言
本文基于SkyWalking6.6.0
${APP_HOME}
指代SkyWalking的安装解压路径
SkyWalking的架构主要分为以下4个部分:
- Probes
- Platform backend
- Storage
- UI
本篇我们主要说说Probes的加载机制
# 2. 开始
我们的入口方法处于${APP_HOME}/apm-sniffer/apm-agent/
中
org.apache.skywalking.apm.agent.SkyWalkingAgent.java
public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException, IOException {
final PluginFinder pluginFinder;
try {
SnifferConfigInitializer.initialize(agentArgs); // (1)
pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins()); // (2)
} catch (ConfigNotFoundException ce) {
logger.error(ce, "SkyWalking agent could not find config. Shutting down.");
return;
} catch (AgentPackageNotFoundException ape) {
logger.error(ape, "Locate agent.jar failure. Shutting down.");
return;
} catch (Exception e) {
logger.error(e, "SkyWalking agent initialized failure. Shutting down.");
return;
}
final ByteBuddy byteBuddy = new ByteBuddy() // (3)
.with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy) // (4)
.ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.groovy."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameContains(".reflectasm."))
.or(nameStartsWith("sun.reflect"))
.or(allSkyWalkingAgentExcludeToolkit())
.or(ElementMatchers.<TypeDescription>isSynthetic()));
JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses(); // (5)
try {
agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses); // (6)
} catch (Exception e) {
logger.error(e, "SkyWalking agent inject bootstrap instrumentation failure. Shutting down.");
return;
}
try {
agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses); // (7)
} catch (Exception e) {
logger.error(e, "SkyWalking agent open read edge in JDK 9+ failure. Shutting down.");
return;
}
agentBuilder // (8)
.type(pluginFinder.buildMatch())
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new Listener())
.installOn(instrumentation);
try {
ServiceManager.INSTANCE.boot(); // (9)
} catch (Exception e) {
logger.error(e, "Skywalking agent boot failure.");
}
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { // (10)
@Override public void run() {
ServiceManager.INSTANCE.shutdown();
}
}, "skywalking service shutdown thread"));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
- (1) 初始化Config
- (2) 初始化PluginFinder
- (3) 初始化 ByteBuddy
- (4) 初始化AgentBuilder
- (5) (6) (7) 对JDK9模块化的支持
- (8) 对
agentBuilder
注入它需要的所有信息,待增强类的匹配规则,待增强类的增强规则,监听回调方法,instrumentation
对象等等 - (9)
ServiceManager
和plugin
都可以对宿主应用进行增强。其区别在于,plugin在启动之初对其目标类字节码增强,在运行期间没有自己独占的线程(dynamicPlugin也同理);而ServiceManager
对宿主应用增强则是由单独线程实现的,比如定时任务采集CPU/内存占用等信息上报给Platform backend
,有自己独占的线程 - (10) 在
Runtime.getRuntime()
上注册一个ShutdownHook
,宿主应用结束时,也会去结束ServiceManager
的独占线程,并释放资源
# (1) 初始化Config
org.apache.skywalking.apm.agent.core.conf.SnifferConfigInitializer.java
public static void initialize(String agentOptions) throws ConfigNotFoundException, AgentPackageNotFoundException {
InputStreamReader configFileStream;
try {
configFileStream = loadConfig(); // (1)
Properties properties = new Properties();
properties.load(configFileStream); // (2)
for (String key : properties.stringPropertyNames()) { // (3)
String value = (String)properties.get(key);
//replace the key's value. properties.replace(key,value) in jdk8+
properties.put(key, PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(value, properties));
}
ConfigInitializer.initialize(properties, Config.class); // (4)
} catch (Exception e) {
logger.error(e, "Failed to read the config file, skywalking is going to run in default config.");
}
try {
overrideConfigBySystemProp(); // (5)
} catch (Exception e) {
logger.error(e, "Failed to read the system properties.");
}
if (!StringUtil.isEmpty(agentOptions)) {
try {
agentOptions = agentOptions.trim();
logger.info("Agent options is {}.", agentOptions);
overrideConfigByAgentOptions(agentOptions); // (6)
} catch (Exception e) {
logger.error(e, "Failed to parse the agent options, val is {}.", agentOptions);
}
}
if (StringUtil.isEmpty(Config.Agent.SERVICE_NAME)) { // (7)
throw new ExceptionInInitializerError("`agent.service_name` is missing.");
}
if (StringUtil.isEmpty(Config.Collector.BACKEND_SERVICE)) { // (8)
throw new ExceptionInInitializerError("`collector.backend_service` is missing.");
}
if (Config.Plugin.PEER_MAX_LENGTH <= 3) {
logger.warn("PEER_MAX_LENGTH configuration:{} error, the default value of 200 will be used.", Config.Plugin.PEER_MAX_LENGTH);
Config.Plugin.PEER_MAX_LENGTH = 200;
}
IS_INIT_COMPLETED = true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (1) 这里会先尝试用
skywalking_config
作为key从System.getProperties()
里获取配置文件的path,若无就指定为${APP_HOME}/apm-sniffer/apm-agent/
目录下的config/agent.config
,并返回配置文件的InputStreamReader
- (2) 将配置文件的里的键值对解析并存入
properties
对象,其实它就是Map
的一个实现 - (3) 遍历
properties
,将内部键值对中值为为占位符形式的值解析并替换成真实值并写进properties
。比如agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
就会从properties
中取键为SW_AGENT_NAME
的值,并填充进agent.service_name
再填充进properties
- (4) 将
properties
中的值都初始化进Config
,这个一个static
类,它将会在整个probes加载生命周期内被作为全局配置经常用到 - (5) (6) 这里可以看出配置会被覆盖,由此我们可以看出优先级为:
agentOptions
>systemProperties
>agent.config
,并且这2步都会执行(4)中的ConfigInitializer.initialize(properties, Config.class);
- (7) (8) 配置中
agent.service_name
和collector.backend_service
是必填项
# (2) 初始化PluginFinder
org.apache.skywalking.apm.agent.core.plugin.PluginBootstrap.java
public List<AbstractClassEnhancePluginDefine> loadPlugins() throws AgentPackageNotFoundException {
AgentClassLoader.initDefaultLoader(); // (1)
PluginResourcesResolver resolver = new PluginResourcesResolver();
List<URL> resources = resolver.getResources(); // (2)
if (resources == null || resources.size() == 0) {
logger.info("no plugin files (skywalking-plugin.def) found, continue to start application.");
return new ArrayList<AbstractClassEnhancePluginDefine>();
}
for (URL pluginUrl : resources) { // (3)
try {
PluginCfg.INSTANCE.load(pluginUrl.openStream());
} catch (Throwable t) {
logger.error(t, "plugin file [{}] init failure.", pluginUrl);
}
}
List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList(); // (4)
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>(); // (5)
for (PluginDefine pluginDefine : pluginClassList) {
try {
logger.debug("loading plugin class {}.", pluginDefine.getDefineClass());
AbstractClassEnhancePluginDefine plugin =
(AbstractClassEnhancePluginDefine)Class.forName(pluginDefine.getDefineClass(),
true,
AgentClassLoader.getDefault())
.newInstance();
plugins.add(plugin);
} catch (Throwable t) {
logger.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass());
}
}
plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault())); // (6)
return plugins;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
(1) 初始化
AgentClassLoader
,内部用了Double-Check
方式实现了单例模式,将PluginBootstrap
的默认classLoader
设为AgentClassLoader
的DEFAULT_LOADER
(2) 获取
AgentClassLoader
下所有的skywalking-plugin.def
文件的URL
对象,可以发现每个plugin的src/main/resources
路径下都各自的def文件(3) 遍历
resources
,在PluginCfg
中解析skywalking-plugin.def
文件内容,这里以tomcat的为例,它会将注释忽略,并把有意义的行,等号左边作为name
,右边作为defineClass
存入PluginDefine
对象,最后将所有的PluginDefine
对象存在PluginCfg
中的List<PluginDefine>
对象
- (4) 取出(3)中处理完成的
pluginClassList
- (5) 用
Class#forName
将pluginClassList
中的plugin全部实例化成对象存入plugins
- (6) SkyWalking中plugin本质上就是"对类的增强的描述",默认是静态的,有个
customize-enhance-plugin
能够提供动态plugin,在这一步它会将自己动态生成的所有plugin存入plugins
,后续我们会从开发plugin的角度详细说说动态plugin的加载机制
org.apache.skywalking.apm.agent.core.plugin.PluginFinder.java
public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) { // (1)
for (AbstractClassEnhancePluginDefine plugin : plugins) {
ClassMatch match = plugin.enhanceClass(); // (2)
if (match == null) {
continue;
}
if (match instanceof NameMatch) { // (3)
NameMatch nameMatch = (NameMatch)match;
LinkedList<AbstractClassEnhancePluginDefine> pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
if (pluginDefines == null) {
pluginDefines = new LinkedList<AbstractClassEnhancePluginDefine>();
nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
}
pluginDefines.add(plugin);
} else { // (4)
signatureMatchDefine.add(plugin);
}
if (plugin.isBootstrapInstrumentation()) { // (5)
bootstrapClassMatchDefine.add(plugin);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (1) 在
PluginBootstrap#loadPlugins
执行完后,plugins
就被送入了PluginFinder
的构造方法 - (2) 每个plugin的define路径下都会有几个被配置进
skywalking-plugin.def
文件并被加载的*Instrumentation
类,enhanceClass
方法可以返回这个plugin需要增强的目标class,以及匹配方式,SkyWalking官方提供的Java插件开发手册非常详细地描述了我们需要继承的类以及需要做的事情,即可让一个plugin被SkyWalking正确地加载并执行,这里不做赘述,链接:https://github.com/apache/skywalking/blob/v6.6.0/docs/en/guides/Java-Plugin-Development-Guide.md - (3) (4) 匹配方式有
byName
根据类全名,byClassAnnotationMatch
根据类上注解,byMethodAnnotationMatch
根据方法上注解,byHierarchyMatch
根据实现或继承等4种方式可选,byName
匹配的都会被加入nameMatchDefine
,另外3种方式匹配的都会被存入signatureMatchDefine
- (5)
bootstrapClassMatchDefine
中会存放一些对jdk原生class增强的plugin