agent实现apm上报

本文涉及的产品
应用实时监控服务-用户体验监控,每月100OCU免费额度
应用实时监控服务-应用监控,每月50GB免费额度
简介: 最近刚刚换了工作,进了公司的架构组,有些项目蛮有意思,也是很感兴趣,也会工作之余自行学习,比如说有个自研apm项目

# 前言

最近刚刚换了工作,进了公司的架构组,有些项目蛮有意思,也是很感兴趣,也会工作之余自行学习,比如说有个自研apm项目


> 当然在此声明一下,本篇代码属于个人学习编写,并非copy公司代码


对于springcloud,有一套sleuth(主要是traceId+spanId生成)+zipkin(数据统计功能)

skywalking,淘宝的鹰眼,蚂蚁金服sofatrace等等


# 动手实现apm上报功能


1. 首先agent用的是byte-buddy

2. 定义一个context方便储存traceid,spanid以及上报的数据

3. 只是一个比较简陋的demo,需要后续一些功能优化


## 代码

**编写一个agent**


```

package com.example.demo.agent;


import com.example.demo.interceptor.MyInterceptor;

import net.bytebuddy.agent.builder.AgentBuilder;

import net.bytebuddy.description.type.TypeDescription;

import net.bytebuddy.dynamic.DynamicType;

import net.bytebuddy.implementation.MethodDelegation;

import net.bytebuddy.matcher.ElementMatchers;

import net.bytebuddy.utility.JavaModule;


import java.lang.instrument.Instrumentation;


public class MyAgent {


   public static final ThreadLocal<TraceContext> LOCAL = new ThreadLocal<>();


   public static void premain(String agentArgs, Instrumentation inst) {

       System.out.println("this is an perform monitor agent.");


       AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader) -> {

           return builder

                   .method(ElementMatchers.any()) // 拦截任意方法

                   .intercept(MethodDelegation.to(MyInterceptor.class)); // 委托

       };


       AgentBuilder.Listener listener = new AgentBuilder.Listener() {

           @Override

           public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {

           }


           @Override

           public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {

           }


           @Override

           public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {

           }


           @Override

           public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {

           }

       };


       new AgentBuilder

               .Default()

               .type(ElementMatchers.nameStartsWith("com.example.demo").and(ElementMatchers.not(ElementMatchers.nameStartsWith("com.example.demo.agent"))))

               // 指定需要拦截的类

               .transform(transformer)

               .with(listener)

               .installOn(inst);

   }

}


```

然后写下agent拦截下这些类之后需要做的操作


```

package com.example.demo.interceptor;


import com.alibaba.fastjson.JSON;

import com.example.demo.agent.TraceContext;

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;


import static com.example.demo.agent.MyAgent.LOCAL;


public class MyInterceptor {


   @RuntimeType

   public static Object intercept(@Origin Method method,

                                  @SuperCall Callable<?> callable) throws Exception {

       long start = System.currentTimeMillis();

       try {

           // 原有函数执行

           return callable.call();

       } finally {

           TraceContext context = LOCAL.get();

           if(context != null){

               context.setMethodType(method.getDeclaringClass().getName()+"."+method.getName());

               context.setTime("调用方法时间:"+ (System.currentTimeMillis() - start) +"ms");

               //上报操作,rpc,这里还需要修改sql等等打印到logback也上报到收集中心

               System.out.println(JSON.toJSONString(context));

               context.clear();

           }

       }

   }


}


```

**看下自定义上下文context**


```

package com.example.demo.agent;


import lombok.AllArgsConstructor;

import lombok.Builder;

import lombok.Data;

import lombok.NoArgsConstructor;


import java.io.Serializable;

import java.util.concurrent.atomic.AtomicInteger;


/**

* @author M

*/

@Builder

@Data

@AllArgsConstructor

@NoArgsConstructor

public class TraceContext implements Serializable {

   private String traceId;

   private String spanId;

   private String parentSpaceId;

   private String time;

   private String methodType;

   private String data;

   //spanId separator

   public static final String RPC_ID_SEPARATOR = ".";


   /**

    * sub-context counter

    */

   private static AtomicInteger childContextIndex = new AtomicInteger(0);


   /**

    * 如果rpc调用的时候需要将spanid传递成这个方法的值

    *

    * @return

    */

   public String nextChildContextId() {

       return this.spanId + RPC_ID_SEPARATOR + childContextIndex.incrementAndGet();

   }


   public static TraceContext cloneContext(TraceContext context) {

       if(context==null){

           return new TraceContext();

       }

       return TraceContext.builder()

               .spanId(context.nextChildContextId())

               .parentSpaceId(context.getSpanId())

               .traceId(context.getTraceId())

               .build();

   }


   public void clear() {

       //上报之后需要清理之前的一些数据

       this.data = "";

   }

}


```

**生成agent jar包**


这个自行百度,启动的时候加上 **-javaagent:D:\github\agent-apm\out\artifacts\MyAgent\MyAgent.jar**


**单元测试**


```

package com.example.demo.test;


import com.alibaba.fastjson.JSON;

import com.example.demo.agent.TraceContext;


import java.util.UUID;


import static com.example.demo.agent.MyAgent.LOCAL;


public class AgentTest {


   private void fun1() throws Exception {

       TraceContext context = LOCAL.get();

       if (context != null) {

           //由于没有集成sleuth,spaceId需要自己模调用的时候简单的自增

           String spaceId = context.getSpanId();

           //rpc调用的时候需要+1

           context.setSpanId(spaceId);

           context.setParentSpaceId(spaceId);

           context.setData("fun1需要上报的数据");

       }

       System.out.println("this is fun 1.");

       Thread.sleep(500);

   }


   private void fun2() throws Exception {

       TraceContext context = LOCAL.get();

       if (context != null) {

           //由于没有集成sleuth,spaceId需要自己模调用的时候简单的自增

           String spaceId = context.getSpanId();

           //rpc调用的时候需要+1

           context.setSpanId(spaceId);

           context.setParentSpaceId(spaceId);

           if (!"".equals(context.getData())) {

               System.out.println("fun2可以拿到之前context上传数据:" + context.getData());

           }

       }

       System.out.println("this is fun 2.");

       Thread.sleep(500);

       //模拟调用rpc

       TraceContext rpcContext = TraceContext.cloneContext(context);

       System.out.println("上报rpc context:" + JSON.toJSONString(rpcContext));


   }



   /**

    * 可以重写logback append逻辑,打印日志也上报到收集数据的系统

    *

    * @param args

    * @throws Exception

    */

   public static void main(String[] args) throws Exception {

       //实际开发由sleuth来生成traceId

       String traceId = UUID.randomUUID().toString();

       String spaceId = "0";

       TraceContext context = TraceContext.builder()

               .spanId(spaceId)

               .parentSpaceId("0")

               .traceId(traceId)

               .build();

       //如果是rpc的话,需要使用拦截器,将context塞到LOCAL里面

       LOCAL.set(context);

       AgentTest test = new AgentTest();

       test.fun1();

       test.fun2();

       //实际开发需要拦截器去删除本地变量

       LOCAL.remove();

   }

}


```

**打印结果**


```

this is an perform monitor agent.

this is fun 1.

{"data":"fun1需要上报的数据","methodType":"com.example.demo.test.AgentTest.fun1","parentSpaceId":"0","spanId":"0","time":"调用方法时间:503ms","traceId":"add0ac18-9918-4c59-846d-a04802000bae"}

this is fun 2.

上报rpc context:{"parentSpaceId":"0","spanId":"0.1","traceId":"add0ac18-9918-4c59-846d-a04802000bae"}

{"data":"","methodType":"com.example.demo.test.AgentTest.fun2","parentSpaceId":"0","spanId":"0","time":"调用方法时间:507ms","traceId":"add0ac18-9918-4c59-846d-a04802000bae"}


```

## 检验结果

有traceid、spanid可以**构建一条调用链路**,其次的话会打印方法名,执行时间,可以进行**后续相应的代码优化**,以及加上了需要**自定义上报的数据**


## 总结

我们可以看到打印结果,可以看出同一个traceId,以及同一个应用spanid也是一样的,如果说跨应用,spanid需要重新设置,然后进行传递


**spanId生成规则**

![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e8c74bc411a34e8dbcdee71aa369897f~tplv-k3u1fbpfcp-zoom-1.image)

参考下sofatrace 生成规则

![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53114e293ccd4b55bf282886cc5b5da4~tplv-k3u1fbpfcp-zoom-1.image)

就是上面的0.1,0.1.1 spanId


# 等待优化点


1. rpc部分需要重新,从header头拿到context赋值到threadlocal

2. mysql打印sql以及执行时间,也需要重写

3. 重写logback append逻辑,我们平时打印的日志也需要上报的数据中心


# 项目链接

github:[https://github.com/dajitui/agent-apm](https://github.com/dajitui/agent-apm)

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
6月前
|
Kubernetes 监控 安全
Kustomize 生产实战 - 注入监控 APM Agent
Kustomize 生产实战 - 注入监控 APM Agent
|
存储 数据采集 缓存
【运维知识进阶篇】Zabbix5.0稳定版详解9(Zabbix优化:高并发对MySQL进行拆分、Zabbix-agent主动上报模式、使用proxy代理模式、系统自带监控项优化、进程优化、缓存优化)
【运维知识进阶篇】Zabbix5.0稳定版详解9(Zabbix优化:高并发对MySQL进行拆分、Zabbix-agent主动上报模式、使用proxy代理模式、系统自带监控项优化、进程优化、缓存优化)
833 0
如何设置agent上报频率监控间隔时间 - WGCLOUD
在agent/config/application.properties中设置即可
如何设置agent上报频率监控间隔时间 - WGCLOUD
|
监控 Linux Windows
[wgcloud-agent]2022/07/01 00:13:46 WgcloudAgent.go:287: 防篡改校验错误次数大于10次,不再上报数据: 32
WGCLOUD监控平台在运行中,发现主机下线了,查看agent日志,发现以下错误日志
[wgcloud-agent]2022/07/01 00:13:46 WgcloudAgent.go:287: 防篡改校验错误次数大于10次,不再上报数据: 32
|
Web App开发 监控 前端开发
通过页面埋点做监控却不影响性能?解密ARMS前端监控数据上报技术内幕
本文将为您介绍,在采集多类日志数据的情况下,阿里云业务实时监控服务(ARMS)之前端监控如何优化日志上报
6740 6
|
3月前
|
存储 人工智能
|
7天前
|
存储 人工智能 自然语言处理
AI经营|多Agent择优生成商品标题
商品标题中关键词的好坏是商品能否被主搜检索到的关键因素,使用大模型自动优化标题成为【AI经营】中的核心能力之一,本文讲述大模型如何帮助商家优化商品素材,提升商品竞争力。
AI经营|多Agent择优生成商品标题
|
8天前
|
人工智能 算法 搜索推荐
清华校友用AI破解162个高数定理,智能体LeanAgent攻克困扰陶哲轩难题!
清华校友开发的LeanAgent智能体在数学推理领域取得重大突破,成功证明了162个未被人类证明的高等数学定理,涵盖抽象代数、代数拓扑等领域。LeanAgent采用“持续学习”框架,通过课程学习、动态数据库和渐进式训练,显著提升了数学定理证明的能力,为数学研究和教育提供了新的思路和方法。
19 3
|
9天前
|
人工智能 自然语言处理 算法
企业内训|AI/大模型/智能体的测评/评估技术-某电信运营商互联网研发中心
本课程是TsingtaoAI专为某电信运营商的互联网研发中心的AI算法工程师设计,已于近日在广州对客户团队完成交付。课程聚焦AI算法工程师在AI、大模型和智能体的测评/评估技术中的关键能力建设,深入探讨如何基于当前先进的AI、大模型与智能体技术,构建符合实际场景需求的科学测评体系。课程内容涵盖大模型及智能体的基础理论、测评集构建、评分标准、自动化与人工测评方法,以及特定垂直场景下的测评实战等方面。
55 4