SkyWalking分布式系统应用程序性能监控工具-中
其他功能
性能剖析
在系统性能监控方法上,Skywalking 提出了代码级性能剖析这种在线诊断方法。这种方法基于一个高级语言编程模型共性,即使再复杂的系统,再复杂的业务逻辑,都是基于线程去进行执行的,而且多数逻辑是在单个线程状态下执行的;代码级性能剖析就是利用方法栈快照,并对方法执行情况进行分析和汇总;并结合有限的分布式追踪 span 上下文,对代码执行速度进行估算。有如下优势:
- 精确的问题定位,直接到代码方法和代码行
 - 无需反复的增删埋点,大大减少了人力开发成本
 - 不用承担过多埋点对目标系统和监控系统的压力和性能风险
 - 按需使用,平时对系统无消耗,使用时的消耗稳定可能
 
SkyWalking的跟踪或者说性能剖析,选择某个服务

根据选择端点的名称及相应的规则建立任务,后续再调用任务列表的端口会自动记录剖析剖析当前端口数据并生成剖析结果

为了更好演示在库存微服务的创建订单方法中增加一个睡眠3秒,然后重新启动订单微服务

再次多次访问创建订单接口 http://localhost:4070/order/create/1000/1001/2 ,需要连续执行多次请求,因为存在采样设置。如果执行次数少,可能不会出现采样数据,每个服务,相同时间只能添加一个任务,添加的任务不能更改,也不能删除,只能等待过期后自动删除。
日志
在库存和订单微服务中引入依赖
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>8.11.0</version>
</dependency>
在库存和订单微服务中,增加分布式链路追踪ID在logback.xml加入如下配置,[%tid]
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>
gRPC reporter上报日志在logback.xml加入如下配置:
    <appender name="grpc-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>

访问订单接口 http://localhost:4070/order/create/1000/1001/2,查看订单和库存微服务的日志中已带有 TID

也通过GRPC上传到SkyWalking后端,通过Log页面可以查看日志信息

可以通过TID查询对应日志详细信息

告警
在config/alarm-settings.yml ,已经默认若干项告警,我们简单修改告警信息内容,增加一串标识"Itxs Alarm"例如,配置webhooks
rules:
  # Rule unique name, must be ended with `_rule`.
  service_instance_resp_time_rule:
    metrics-name: service_instance_resp_time
    op: ">"
    threshold: 1000
    period: 10
    count: 2
    silence-period: 5
    message: Itxs Alarm esponse time of service instance {name} is more than 1000ms in 2 minutes of last 10 minutes
  endpoint_relation_resp_time_rule:
    metrics-name: endpoint_relation_resp_time
    threshold: 1000
    op: ">"
    period: 10
    count: 2
    message: Itxs Alarm esponse time of endpoint relation {name} is more than 1000ms in 2 minutes of last 10 minutes
webhooks:
  - http://192.168.4.210:8080/alarm/
新建一个webhooks接口服务端,创建AlarmMessage实体类
package com.aotain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AlarmMessage implements Serializable {
    private String scopeId;
    private String scope;
    private String name;
    private String id0;
    private String id1;
    private String ruleName;
    //告警的消息
    private String alarmMessage;
    //告警的产生时间
    private Long startTime;
}
创建一个控制器AlarmController,提供/alarm接口,这里简单就显示信息,后续可以根据实际调用微信、钉钉告警之类。
package com.aotain.controller;
import com.aotain.entity.AlarmMessage;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AlarmController {
    @PostMapping("/alarm")
    public String alarm(@RequestBody List<AlarmMessage> alarmMessageList) throws Exception {
        System.out.println(alarmMessageList);
        return "ok";
    }
}
这里使用ApiFox多线程访问http://localhost:4070/order/create/1000/1001/2

查看告警,已经为我们修改后告警信息,含有Itxs Alarm的前缀字符串

查看事件

查看webhooks调用接口,已经收到SkyWalking调用过来的数据,这里后续可扩展为实际的告警方式处理。


SkyWalking原理
SkyWalking Agent原理
无侵入实现原理
上面使用Skywalking并没有修改程序中任何一行 Java 代码,这里便是使用到了 Java Agent 技术,如果平常基于增删改查业务逻辑那就基本不会使用到Java Agent,但我们平时用过的不少工具如热部署工具JRebel,SpringBoot的热部署插件,各种线上诊断工具(btrace, greys),阿里开源的arthas都是基于java Agent来实现的。在JDK1.5以后就有java Agent,使用agent技术构建一个独立于应用程序的代理程序(即Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能,典型的优势就是无代码侵入。Agent大体可分为两种:
- 在主程序之前运行的Agent。
 - 在主程序之后运行的Agent(前者的升级版,1.6以后提供)。
 
主程序之前运行的Agent
premain为主程序之前运行的Agent,在实际使用过程中,javaagent是java命令的一个参数。通过java 命令启动我们的应用程序的时候,可通过参数 -javaagent 指定一个 jar 包(也就是我们的代理agent),能够实现在我们应用程序的主程序运行之前来执行我们指定jar包中的特定方法,在该方法中我们能够实现动态增强Class等相关功能,并且该 jar包有2个要求:
- 这个 jar 包的 META-INF/MANIFEST.MF 文件必须指定 Premain-Class 项,该选项指定的是一个类的全路径。
 - Premain-Class 指定的那个类必须实现 premain() 方法。
 
从字面上理解,Premain-Class 就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行-javaagent所指定 jar 包内 Premain-Class 这个类的 premain 方法 。
-javaagent所在包java.lang.instrument,是rt.jar 中定义的一个包,有两个重要的类:

java.lang.instrument包提供了一些工具帮助开发人员在 Java 程序运行时动态修改系统中的 Class 类型。其中使用该软件包的一个关键组件就是 Javaagent,从本质上来讲,Java Agent 是一个遵循一组严格约定的常规 Java 类,就如上面说到 javaagent命令要求指定的类中必须要有premain()方法,并且对premain方法的签名也有要求,签名必须满足以下两种格式:
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
JVM 会优先加载 带 Instrumentation 签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。
创建PreAgentDemo的maven项目,编写一个agent程序com.itxs.agent.PreAgentDemo,完成premain方法的签名,这里先做一个简单的日志输出。
package com.itxs.agent;
import java.lang.instrument.Instrumentation;
public class PreAgentDemo {
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("PreAgentDemo run");
        System.out.println("PreAgentDemo receive params agentArgs=" + agentArgs);
    }
}
maven项目pom文件增加如下坐标
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive> <!--自动添加META-INF/MANIFEST.MF -->
                        <manifest>
                            <!-- 添加 mplementation-*和Specification-*配置项-->
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        </manifest>
                        <manifestEntries>
                            <!--指定premain方法所在的类-->
                            <Premain-Class>com.itxs.agent.PreAgentDemo</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
对PreAgentDemo项目进行打包,得到 PreAgentDemo-1.0.jar,放在G:\other下,查看jar包中的MANIFEST.MF文件

Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
Premain-Class :包含 premain 方法的类(类的全路径名)
接着创建一个test-demo项目,编写一个简单测试类App,运行JVM参数添加
-javaagent:G:\other\PreAgentDemo-1.0.jar=param1=value1,param2=value2,param3=value3

上运行结果可以看到在测试程序main函数启动前先输出premain方法打印的日志。实际开发中大部分类加载都会通过该方法。当然,遗漏的主要是系统类,因为很多系统类先于 agent 执行,而用户类的加载肯定是会被拦截的。也就是说,这个方法是在 main 方法启动前拦截大部分类的加载活动,既然可以拦截类的加载,那么就可以去做重写类这样的操作,结合第三方的字节码编译工具,比如ASM,bytebuddy,javassist,cglib等等来改写实现类。
Instrumentation 中的核心 API 方法:
- addTransformer()/removeTransformer() 方法:注册/注销一个 ClassFileTransformer 类的实例,该 Transformer 会在类加载的时候被调用,可用于修改类定义(修改类的字节码)。
 - redefineClasses() 方法:该方法针对的是已经加载的类,它会对传入的类进行重新定义。
 - getAllLoadedClasses()方法:返回当前 JVM 已加载的所有类。
 - getInitiatedClasses() 方法:返回当前 JVM 已经初始化的类。
 - getObjectSize()方法:获取参数指定的对象的大小。
 
主程序之后运行的Agent
agentmain,可以在 main 函数开始运行之后再运行。跟premain函数一样, 开发者可以编写一个含有agentmain函数的 Java 类。
public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)
同样需要在MANIFEST.MF文件里面设置“Agent-Class”来指定包含 agentmain 函数的类的全路径。在前面工程基础上增加com.itxs.agent.AgentDemo文件,也是简单打印日志。
package com.itxs.agent;
import java.lang.instrument.Instrumentation;
public class AgentDemo {
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("AgentDemo run");
    }
}

在pom.xml中添加配置如下
<Agent-Class>com.itxs.agent.AgentDemo</Agent-Class>

重新打包 PreAgentDemo-1.0.jar并覆盖到G:\other下,在测试类App修改如下代码
package com.itxs;
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
public class App
{
    public static void main( String[] args ) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
        System.out.println( "itxs app main run!" );
        //获取当前系统中所有 运行中的 虚拟机
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vm : list) {
            if (vm.displayName().endsWith("com.itxs.App")) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vm.id());
                virtualMachine.loadAgent("G:/other/PreAgentDemo-1.0.jar");
                virtualMachine.detach();
            }
        }
    }
}
list()方法会去寻找当前系统中所有运行着的JVM进程,你可以打印vmd.displayName()看到当前系统都有哪些JVM进程在运行。因为main函数执行起来的时候进程名为当前类名,所以通过这种方式可以去找到当前的进程id。在windows中安装的jdk无法找到,如遇到这种情况手动将你jdk安装目录下:lib目录中的tools.jar添加进当前工程的Libraries中。
agent要在主程序运行后加载,我们不可能在主程序中编写加载的代码,只能另写程序,那么另写程序如何与主程序进行通信?这里用到的机制就是attach机制,它可以将JVM A连接至JVM B,并发送指令给JVM B执行。
字节码操作(增强)
Byte Buddy概述
Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,并且不需要编译器的帮助。与Java类库附带的代码生成实用程序不同,Byte Buddy允许创建任意类,并且不局限于为创建运行时代理实现接口。此外,Byte Buddy提供了一个方便的API,可以手动更改类,可以使用Java代理,也可以在构建期间更改类。
- 无需理解字节码指令,即可使用较为简单的API就能很容易操作字节码,控制类和方法。
 - 已支持Java 11,轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
 - 比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。
 
反射机制可以知道调用的方法或字段,但反射性能很差,反射能绕开类型安全检查,不安全,比如权限暴力破解;java编程语言代码生成库也有多种:
- Java Proxy:是 JDK 自带的一个代理工具,它允许为实现了一系列接口的类生成代理类。Java Proxy 要求目标类必须实现接口是一个非常大限制,例如在某些场景中目标类没有实现任何接口且无法修改目标类的代码实现,Java Proxy 就无法对其进行扩展和增强了。
 - CGLIB 诞生于 Java 初期,但不幸的是没有跟上 Java 平台的发展。虽然 CGLIB 本身是一个相当强大的库,但也变得越来越复杂,导致许多用户放弃了CGLIB 。
 - Javassist:其使用对 Java 开发者来说是非常友好的,它使用Java 源代码字符串和 Javassist 提供的一些简单 API ,共同拼凑出用户想要的 Java 类,Javassist 自带一个编译器,拼凑好的 Java 类在程序运行时会被编译成为字节码并加载到 JVM 中。Javassist 库简单易用,而且使用 Java 语法构建类与平时写 Java 代码类似,但是 Javassist 编译器在性能上比不了 Javac 编译器,而且在动态组合字符串以实现比较复杂的逻辑时容易出错。
 - Byte Buddy:提供了一种非常灵活且强大的领域特定语言,通过编写简单的 Java 代码即可创建自定义的运行时类。与此同时,Byte Buddy 还具有非常开放的定制性,能够应付不同复杂度的需求。
 
上面所有代码生成技术中推荐使用Byte Buddy,因为Byte Buddy代码生成可的性能最高;Byte Buddy 的主要侧重点在于生成更快速的代码,如下图

ByteBuddy API
Class<?> dynamicType = new ByteBuddy()
  // 生成 Object的子类
  .subclass(Object.class)
   // 生成类的名称
  .name("com.itxs.type")
  // 拦截其中的toString()方法
  .method(ElementMatchers.named("toString"))
  // 让toString()方法返回固定值
  .intercept(FixedValue.value("Hello World!"))
  .make()
  // 加载新类型,默认WRAPPER策略,也即是ClassLoadingStrategy.Default.WRAPPER可以不写
  .load(getClass().getClassLoader(),ClassLoadingStrategy.Default.WRAPPER)
  .getLoaded();
Byte Buddy 动态增强代码总有如下三种方式:
- subclass:对应 ByteBuddy.subclass() 方法。这种方式比较好理解,就是为目标类(即被增强的类)生成一个子类,在子类方法中插入动态代码。
 - rebasing:对应 ByteBuddy.rebasing() 方法。当使用 rebasing 方式增强一个类时,Byte Buddy 保存目标类中所有方法的实现,也就是说,当 Byte Buddy 遇到冲突的字段或方法时,会将原来的字段或方法实现复制到具有兼容签名的重新命名的私有方法中,而不会抛弃这些字段和方法实现。从而达到不丢失实现的目的。这些重命名的方法可以继续通过重命名后的名称进行调用。
 - redefinition:对应 ByteBuddy.redefine() 方法。当重定义一个类时,Byte Buddy 可以对一个已有的类添加属性和方法,或者删除已经存在的方法实现。如果使用其他的方法实现替换已经存在的方法实现,则原来存在的方法实现就会消失。
 
上面三种增强代码后得到的是 DynamicType.Unloaded 对象,表示的是一个未加载的类型,可以使用 ClassLoadingStrategy加载此类型;Byte Buddy 提供了几种类加载策略,这些加载策略定义在 ClassLoadingStrategy.Default 中:
- WRAPPER 策略:创建一个新的 ClassLoader 来加载动态生成的类型。
 - CHILD_FIRST 策略:创建一个子类优先加载的 ClassLoader,即打破了双亲委派模型。
 - INJECTION 策略:使用反射将动态生成的类型直接注入到当前 ClassLoader 中。
 
method() 方法可以通过传入的 ElementMatchers 参数匹配多个需要修改的方法,这里的ElementMatchers.named("toString") 即为按照方法名匹配 toString() 方法。如果同时存在多个重载方法,则可以使用 ElementMatchers 其他 API 描述方法的签名,如下所示
// 指定方法名称
ElementMatchers.named("toString")
    // 指定方法的返回值
    .and(ElementMatchers.returns(String.class))
    // 指定方法参数
    .and(ElementMatchers.takesArguments(0));
intercept() 方法,通过 method()方法拦截到的所有方法会由 Intercept() 方法指定的 Implementation 对象决定如何增强;这里的 FixValue.value() 会将方法的实现修改为固定值,上例中就是固定返回 “Hello World!” 字符串。Byte Buddy 中可以设置多个 method() 和 Intercept() 方法进行拦截和修改,Byte Buddy 会按照栈的顺序来进行拦截。
ByteBuddy 普通类代理示例
在test-demo项目中添加ByteBuddy的依赖
    <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy</artifactId>
      <version>1.12.12</version>
    </dependency>
    <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy-agent</artifactId>
      <version>1.12.12</version>
      <scope>test</scope>
    </dependency>
创建普通类OrderService
package com.itxs.service;
public class OrderService {
    public String addOrder(){
        System.out.println("=====do addOrder==========");
        return "1000000001";
    }
    public String getOrder(String orderId){
        System.out.println("=====do getOrder==========");
        return orderId;
    }
    public String getOrder(String orderId,String status){
        System.out.println("=====do getOrder two params==========");
        return orderId+status;
    }
}
创建拦截器类TestInterceptor
package com.itxs.interceptor;
import net.bytebuddy.implementation.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
public class TestInterceptor {
    @RuntimeType //将返回值转换成具体的方法返回值类型,加了这个注解 intercept 方法才会被执行
    public  Object intercept(
            // 被拦截的目标对象 (动态生成的目标对象)
            @This Object target,
            // 正在执行的方法Method 对象(目标对象父类的Method)
            @Origin Method method,
            // 正在执行的方法的全部参数
            @AllArguments Object[] argumengts,
            // 目标对象的一个代理
            @Super Object delegate,
            // 方法的调用者对象 对原始方法的调用依靠它
            @SuperCall Callable<?> callable) throws Exception {
        //目标方法执行前执行日志记录
        System.out.println("prepare do method="+method.getName());
        // 调用目标方法
        Object result = callable.call();
        //目标方法执行后执行日志记录
        System.out.println("have down method="+method.getName());
        return result;
    }
}
创建普通类代理测试类ByteBuddyTest
package com.itxs;
import com.itxs.interceptor.TestInterceptor;
import com.itxs.service.OrderService;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
public class ByteBuddyTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<? extends OrderService> generateClass = new ByteBuddy()
                // 创建一个UserService 的子类
                .subclass(OrderService.class)
                //指定类的名称
                .name("com.itxs.service.OrderServiceImpl")
                // 指定要拦截的方法
                .method(ElementMatchers.named("getOrder").and(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(2))))
                // 为方法添加拦截器 如果拦截器方法是静态的 这里可以传 LogInterceptor.class
                .intercept(MethodDelegation.to(new TestInterceptor()))
                // 动态创建对象,但还未加载
                .make()
                // 设置类加载器 并指定加载策略(默认WRAPPER)
                .load(ByteBuddy.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                // 开始加载得到 Class
                .getLoaded();
        OrderService orderService = generateClass.newInstance();
        System.out.println(orderService.addOrder());
        System.out.println(orderService.getOrder("2000000000"));
        System.out.println(orderService.getOrder("3000000000","支付中"));
    }
}
在程序中用到ByteBuddy的MethodDelegation对象,它可以将拦截的目标方法委托给其他对象处理,注解使用说明如下:
- @RuntimeType:不进行严格的参数类型检测,在参数匹配失败时,尝试使用类型转换方式(runtime type casting)进行类型转换,匹配相应方法。
 - @This:注入被拦截的目标对象(动态生成的目标对象)。
 - @Origin:注入正在执行的方法Method 对象(目标对象父类的Method)。如果拦截的是字段的话,该注解应该标注到 Field 类型参数。
 - @AllArguments:注入正在执行的方法的全部参数。
 - @Super:注入目标对象的一个代理。
 - @SuperCall:这个注解比较特殊,我们要在 intercept() 方法中调用 被代理/增强 的方法的话,需要通过这种方式注入,与 Spring AOP 中的 ProceedingJoinPoint.proceed() 方法有点类似,需要注意的是,这里不能修改调用参数,从上面的示例的调用也能看出来,参数不用单独传递,都包含在其中了。另外,@SuperCall 注解还可以修饰 Runnable 类型的参数,只不过目标方法的返回值就拿不到了。
 
运行ByteBuddyTest,增强的方法输出就是上面代码中方法匹配名称为getOrder且返回值为String且有两个入参的结果。

自定义Agent案例
Java Agent十分强大,使用Transformer等高级功能进行类替换,方法修改等,要使用Instrumentation的相关API则需要对字节码等技术有较深的认识。接下来ByteBuddy结合Java Agent技术实现一个统计方法耗时的示例。
在上面的PreAgentDemo项目中加入依赖byte-buddy和byte-buddy-agent的依赖,上面测试工程Pom文件有
创建耗时统计拦截器类
package com.itxs.agent.interceptor;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
public class TimeConsumingInterceptor {
    /***
     * 拦截方法
     * @param method:拦截的方法
     * @param callable:调用对象的代理对象
     * @return
     * @throws Exception
     */
    @RuntimeType // 声明为static
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        //时间统计开始
        long start = System.currentTimeMillis();
        // 执行原函数
        Object result = callable.call();
        //执行时间统计
        System.out.println(method.getName() + ":time consuming total" + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}
创建JavaAgentCase的premain实现
package com.itxs.agent;
import com.itxs.agent.interceptor.TimeConsumingInterceptor;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class JavaAgentCase {
    /***
     * 执行方法拦截
     * @param agentArgs:-javaagent 命令携带的参数。在前面介绍 SkyWalking Agent 接入时提到
     *                 agent.service_name 这个配置项的默认值有三种覆盖方式,
     *                 其中,使用探针配置进行覆盖,探针配置的值就是通过该参数传入的。
     * @param instrumentation:java.lang.instrumen.Instrumentation 是 Instrumention 包中定义的一个接口,它提供了操作类定义的相关方法。
     */
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        // 动态构建操作,根据transformer规则执行拦截操作,匹配上的具体的类型描述
        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
            // 构建拦截规则
            return builder
                    // method()指定哪些方法需要被拦截,ElementMatchers.any()表示拦截所有方法
                    .method(ElementMatchers.any())
                    // intercept()指定拦截上述方法的拦截器
                    .intercept(MethodDelegation.to(TimeConsumingInterceptor.class));
        };
        // 采用Byte Buddy的AgentBuilder结合Java Agent处理程序
        new AgentBuilder
                // 采用ByteBuddy作为默认的Agent实例
                .Default()
                // 拦截匹配方式:类以com.itxs.service开始,也即是com.itxs.service包下的所有类
                .type(ElementMatchers.nameStartsWith("com.itxs.service"))
                // 拦截到的类由transformer处理
                .transform(transformer)
                // 安装到 Instrumentation
                .installOn(instrumentation);
    }
}

重新打包好PreAgentDemo-1.0.jar,准备测试类UserService.java
package com.itxs.service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class UserService {
    private static Random random = new Random();
    public void getUser(){
        System.out.println("=====do getUser==========");
        try {
            TimeUnit.SECONDS.sleep(random.nextInt(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void updateUser(){
        System.out.println("=====do updateUser==========");
        try {
            TimeUnit.SECONDS.sleep(random.nextInt(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
创建启动测试类
package com.itxs;
import com.itxs.service.UserService;
public class Application
{
    public static void main( String[] args ) {
        System.out.println("Application main start run-----------");
        UserService service = new UserService();
        service.getUser();
        service.updateUser();
    }
}
启动参数中jvm参数添加javaagent,可参考上面示例,执行Application的main后从日志可以看到UserService的方法被增强了

**本人博客网站 **IT小神 www.itxiaoshen.com
SkyWalking分布式系统应用程序性能监控工具-中的更多相关文章
- SkyWalking分布式系统应用程序性能监控工具-上
		
概述 微服务系统监控三要素 现在系统基本都是微服务架构,对于复杂微服务链路调用如下问题如何解决? 一个请求经过了这些服务后其中出现了一个调用失败的问题,如何定位问题发生的地方? 如何计算每个节点访问流 ...
 - [.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中)
		
[.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中) 本节要点: 上节介绍了多线程的基本使用方法和基本应用示例,本节深入介绍.NET ...
 - Java程序性能监控工具
		
系统性能监控: 确定系统运行的整体状态,基本定位问题所在 uptime命令 [root@localhost ~]# uptime23:19:38 up 244 days, 3:39, 34 users ...
 - JVM-Java程序性能监控-初级篇
		
前篇 - 小伙们都知道,java程序的性能监控主要是针对jvm中heap的监控~ 那么在做压力测试时如何对heap.线程等一系列的指标进行的监控的呢? 首先-你若不懂命令,那么就需要了解一套Java程 ...
 - Web标准中用于改善Web应用程序性能的各种方法总结
		
提起Web应用程序中的性能改善,广大开发者们可能会想到JavaScript与DOM访问等基于各种既存技术的性能改善方法.最近,各种性能改善方法被汇总成为一个Web标准. 本文对Web标准中所包含的各种 ...
 - 在 NetBeans IDE 6.0 中分析 Java 应用程序性能
		
NetBeans IDE 6.0 包含一个强大的性能分析工具,可提供与应用程序运行时行为有关的重要信息.通过 NetBeans 性能分析工具,我们可以方便地在 IDE 中监控应用程序的线程状态.CPU ...
 - (转)在.NET程序运行过程中,什么是堆,什么是栈?什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?
		
转自:http://www.cnblogs.com/xiaoyao2011/archive/2011/09/09/2172427.html 在.NET程序运行过程中,什么是堆,什么是栈? 堆也就是托管 ...
 - 五大最佳开源java性能监控工具
		
如果你正在寻找性能监控工具,不妨看看以下推荐的这五款开源工具,这些工具目前已经可以替代付费工具了,你可以看看是否是你的最佳选择.本文推荐的五款开源工具目前是开源社区中最受欢迎的. 1. Stagemo ...
 - 你值得拥有:25个Linux性能监控工具
		
一.基于命令行的性能监控工具 1.dstat - 多类型资源统计工具 该命令整合了vmstat,iostat和ifstat三种命令.同时增加了新的特性和功能可以让你能及时看到各种的资源使用情况,从而能 ...
 
随机推荐
- python牛顿法求一元多次函数极值
			
现在用牛顿法来实现一元函数求极值问题 首先给出这样一个问题,如果有这么一个函数$f(x) = x^6+x$,那么如何求这个函数的极值点 先在jupyter上简单画个图形 %matplotlib inl ...
 - python 动态规划(背包问题和最长公共子串)
			
背包问题 现在要往一个可以装4个单位重量的背包里怎么装价值最高:A重量1个单位,价值15:B重量3个单位,价值20:C重量4个重量,价值30 使用动态规划填充空格 class SolutionBag: ...
 - 一文带你读懂 Hbase 的架构组成
			
hi,大家好,我是大D.今天咱们继续深挖一下 HBase 的架构组成. Hbase 作为 NoSQL 数据库的代表,属于三驾马车之一 BigTable 的对应实现,HBase 的出现很好地弥补了大数据 ...
 - vue生命周期加载顺序
			
1.beforeCreate(创建前)表示实例完全被创建出来之前,vue 实例的挂载元素$el和数据对象 data 都为 undefined,还未初始化.此钩子函数不能获取到数据,dom元素也没有渲染 ...
 - WPF 分组
			
分组和树形结构是不一样的. 树形结构是以递归形式存在.分组是以键值对存在的形式,类似于GroupBy这样的形式. 举个例子 ID NAME SEX Class 1 张三 男 1 2 李四 女 2 3 ...
 - 什么是HBase?终于有人讲明白了
			
一.初识HBase HBase 是一个面向列式存储的分布式数据库,其设计思想来源于 Google 的 BigTable 论文.HBase 底层存储基于 HDFS 实现,集群的管理基于 ZooKeepe ...
 - 【freertos】012-事件标志概念和实现细节
			
目录 前言 12.1 实现事件机制的预备知识 12.1.1 守护任务 12.1.2 事件的不确定性 12.1.3 事件组的报文 12.2 事件概念 12.3 事件用途参考 12.4 事件实现原理简述 ...
 - Arraylist集合、对象数组
			
Arraylist集合 ArrayList是List接口的一个实现类,它是程序中最常见的一种集合. 他的特点:在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此不适合做大量的增删操作,Ar ...
 - LVS+keepalived简单搭建(二)
			
在LVS1的基础上进行搭建 https://www.cnblogs.com/hikoukay/p/12860476.html keeplived主机 用node01,node04两台 先清掉原先nod ...
 - C语言学习之我见-strncat()可调整的字符串拼接函数
			
strncat()函数,用于两个字符串的拼接. (1)函数原型 char * strncat(char * Dest,const char * Source,size_t _Count)` (2)头文 ...