# 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"));
}
1
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) ServiceManagerplugin都可以对宿主应用进行增强。其区别在于,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;
}
1
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_namecollector.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;

}
1
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设为AgentClassLoaderDEFAULT_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>对象

image-20200802165924158

  • (4) 取出(3)中处理完成的pluginClassList
  • (5) 用Class#forNamepluginClassList中的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);
        }
    }
}
1
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
最近更新: 7/5/2021, 2:03:31 PM