我正在参加「掘金·启航计划」
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 下手。
我们看一下源码他会走处理之后的方法。 并且是先进后出的。 多个拦截器, 先进去的 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的实现。
最后我们打包,apm-ducheng-1.x-jvm-1.0-SNAPSHOT.jar 把打好的jar包 放到skywalking 的plugin 插件目录下。
4.3 代码验证
最后的效果
完美。