skywalking 自定义插件展示http请求的jvm 和gc 详情

772 阅读4分钟

我正在参加「掘金·启航计划」

1, 什么是skywalking

Skywalking是一个国产的开源框架,2015年有吴晟个人开源,2017年加入Apache孵化器,国人开源的产品,主要开发人员来自于华为,2019年4月17日Apache董事会批准SkyWalking成为顶级项目,支持Java、.Net、NodeJs等探针,数据存储支持Mysql、Elasticsearch等,跟Pinpoint一样采用字节码注入的方式实现代码的无侵入,探针采集数据粒度粗,但性能表现优秀,且对云原生支持,目前增长势头强劲,社区活跃 Skywalking是分布式系统的应用程序性能监视工具,专为微服务,云原生架构和基于容器(Docker,K8S,Mesos)架构而设计,它是一款优秀的APM(Application Performance Management)工具,包括了分布式追踪,性能指标分析和服务依赖分析等。

2.如何编写skywalking 插件

官方文档 skyapm.github.io/document-cn…

3,准备工作

3.1 有一个skywalking 的环境 服务端。

3.2 新建一个springboot 的maven 工程项目,skywalking 的版本选择8.8

4.编写代码

4.1.pom 文件的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.ducheng</groupId>
    <artifactId>apm-ducheng-1.x-jvm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <skywalking.version>8.8.0</skywalking.version>
        <shade.package>org.apache.skywalking.apm.dependencies</shade.package>
        <shade.net.bytebuddy.source>net.bytebuddy</shade.net.bytebuddy.source>
        <shade.net.bytebuddy.target>${shade.package}.${shade.net.bytebuddy.source}</shade.net.bytebuddy.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-agent-core</artifactId>
            <version>${skywalking.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-util</artifactId>
            <version>${skywalking.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>false</shadedArtifactAttached>
                            <createDependencyReducedPom>true</createDependencyReducedPom>
                            <createSourcesJar>true</createSourcesJar>
                            <shadeSourcesContent>true</shadeSourcesContent>
                            <relocations>
                                <relocation>
                                    <pattern>${shade.net.bytebuddy.source}</pattern>
                                    <shadedPattern>${shade.net.bytebuddy.target}</shadedPattern>
                                </relocation>
                            </relocations>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

这里主要使用的是 bytebuddy 的字节码编程, 核心就是java agent的探针技术。 java agent 可以理解为字节码 或者 class 文件的 的aop。 也是一种类似切片的技术。这里我就不做过多说明。核心也就是jdk1.5 出来的 Instrumentation, 如果我们会写java agent。 那么skywalking 的插件就是大同小异。

4.2 代码实现

这里主要是干两件事情, 1, 找到需要拦截的类, 找到需要拦截的方法。 2, 把 类名称,方法名称 加到插件里面。 在打印gc / jvm 详情。

首先我们知道一个http 请求到后端的话,肯定要经过拦截器。 那我们就从拦截器HandlerInterceptor 下手。

image.png

我们看一下源码他会走处理之后的方法。 并且是先进后出的。 多个拦截器, 先进去的 posthandle 肯定是最后执行的。 那这样我们就知道代码怎么写了。

核心就是两个类

package com.ducheng.define;


import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;

import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.DeclaredInstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;

import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;

import static net.bytebuddy.matcher.ElementMatchers.*;

public class JvmInstrumentation  extends ClassInstanceMethodsEnhancePluginDefine  {

    /**
     *  自定义方法拦截器的实现
     */
    public static final String TRACE_METHOD_INTERCEPTOR = "com.ducheng.JvmStatisInterceptor";

    /**
     * 这是需要拦截的类名称
     */
    public static final String TRACE_CLASS_INTERCEPTOR = "org.springframework.web.servlet";

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[0];
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
                new DeclaredInstanceMethodsInterceptPoint() {
                    @Override
                    public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        // 需要拦截的方法名称
                        return named("postHandle");
                    }

                    @Override
                    public String getMethodsInterceptor() {
                        return TRACE_METHOD_INTERCEPTOR;
                    }

                    @Override
                    public boolean isOverrideArgs() {
                        // 是否覆盖参数,默认不覆盖。
                        return false;
                    }
                }
        };
    }

    @Override
    protected ClassMatch enhanceClass() {
        return NameMatch.byName(TRACE_CLASS_INTERCEPTOR);
    }
}
package com.ducheng;

import org.apache.skywalking.apm.agent.core.context.ContextManager;
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.agent.core.util.MethodUtil;
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JvmStatisInterceptor implements InstanceMethodsAroundInterceptor {

    private static final long MB = 1048576L;


    @Override
    public void beforeMethod(EnhancedInstance enhancedInstance, Method method, Object[] objects, Class<?>[] classes, MethodInterceptResult methodInterceptResult) throws Throwable {

        AbstractSpan span = ContextManager.createLocalSpan("jvm/gc详情");
        /*
         * 可用ComponentsDefine工具类指定Skywalking官方支持的组件
         * 也可自己new OfficialComponent或者Component
         * 不过在Skywalking的控制台上不会被识别,只会显示N/A
         */
        span.setComponent(ComponentsDefine.TOMCAT);
        // 指定该调用的layer,layer是个枚举
        span.setLayer(SpanLayer.CACHE);
    }

    @Override
    public Object afterMethod(EnhancedInstance enhancedInstance, Method method, Object[] objects, Class<?>[] classes, Object o) throws Throwable {
        // 停止span(监控的结束),本质上是清理ThreadLocal对象
        AbstractSpan activeSpan = ContextManager.activeSpan();


        Map<String,String> stringMap = new HashMap<String, String>();

        MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
        MemoryUsage headMemory = memory.getHeapMemoryUsage();

        String heapMessage = String.format("\n初始化堆内存: %s\t 最大内存: %s\t 使用内存: %s\t 当前可使用的内存大小: %s\t 使用率: %s\n",
                headMemory.getInit() / MB + "MB",
                headMemory.getMax() / MB + "MB", headMemory.getUsed() / MB + "MB",
                headMemory.getCommitted() / MB + "MB",
                headMemory.getUsed() * 100 / headMemory.getCommitted() + "%"

        );

        stringMap.put("堆信息",heapMessage);

        MemoryUsage nonheadMemory = memory.getNonHeapMemoryUsage();
        String nonheadMemoryMeaage = String.format("初始化非堆内存: %s\t 最大内存: %s\t 使用内存: %s\t 当前可使用的内存大小: %s\t 使用率: %s\n",
                nonheadMemory.getInit() / MB + "MB",
                nonheadMemory.getMax() / MB + "MB", nonheadMemory.getUsed() / MB + "MB",
                nonheadMemory.getCommitted() / MB + "MB",
                nonheadMemory.getUsed() * 100 / nonheadMemory.getCommitted() + "%"
        );
        stringMap.put("非堆信息",nonheadMemoryMeaage);
        //gc 信息
        List<GarbageCollectorMXBean> garbages = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean garbage : garbages) {
            String info = String.format("内存管理器: %s\t 回收总次数:%s\t 回收总时间单位毫秒:%s\t 内存池名称:%s",
                    garbage.getName(),
                    garbage.getCollectionCount(),
                    garbage.getCollectionTime(),
                    Arrays.deepToString(garbage.getMemoryPoolNames()));

            stringMap.put("当前方法gc"+garbage.getName()+"详情", info);
        }
        activeSpan.log(System.currentTimeMillis(),stringMap);
        ContextManager.stopSpan();
        return o;
    }

    @Override
    public void handleMethodException(EnhancedInstance enhancedInstance, Method method, Object[] objects, Class<?>[] classes, Throwable throwable) {
        AbstractSpan activeSpan = ContextManager.activeSpan();
        // 记录日志
        activeSpan.log(throwable);
        activeSpan.errorOccurred();
    }
}

还有要记住,resource 下面一定要写skywalking-plugin.def,要不打包skywalking 不识别这个插件。其实就是skywalking 的一种spi的实现。

image.png

最后我们打包,apm-ducheng-1.x-jvm-1.0-SNAPSHOT.jar 把打好的jar包 放到skywalking 的plugin 插件目录下。

4.3 代码验证

最后的效果

302071421c93226b5a3264f220f4737.png

完美。