APM - 零侵入监控Service服务

本文涉及的产品
应用实时监控服务ARMS - 应用监控,每月50GB免费额度
简介: APM - 零侵入监控Service服务

20201008094153486.png

需求


通常情况下,如果我们没有系统内部的调用情况,比如我们这里重点聚焦的Service层的接口性能指标 ,比如 调用次数、Avg执行时间、Min执行时间、Max执行时间、成功次数、失败次数、慢执行次数等等,以及根据监控结果触发某些告警等等 ,上述指标都是没有办法很灵活的采集到的


采集方案


我们先来讨论下实现上述需求的方案

  1. 硬编码
  2. AOP
  3. JavaAgent 字节码插桩


那如何做到更灵活的实现代码零侵入的实现Service层的接口监控呢?

OK ,直奔主题 。方案必然是第三种,使用字节码插桩实现Service的零侵入监控


采集目标


我们需要对哪些对象插桩呢?

@Service注解 标注的类吗? 这里犯了一个致命的错误,如果想要做这种底层的基础组件,不要对用户的使用场景做设定 ,方案要更具有通用性


我们更倾向于让用户自主配置监控的 include 与 exclude .


我们不知道统计哪个类,也不知道统计哪个方法 ,一切都是基于用户自主的配置


模型设计


核心: 使用JavaAgent获取到用户配置的数据, 匹配(排除)后 使用javassist来修改字节码,进行插桩 ,插入我们的监控逻辑。

那我们都需要监控哪些指标呢?

开始时间、用时、异常消息、异常类型、服务类名、方法名 ,当然了都是可以扩展的比如我们可以增加主机IP、应用名称、标识追踪ID等等

简单起见,我们先不引入过多的字段。


Code

我们先聚焦到Javassit修改字节码,不要和JavaAgent掺和在一起,先实现第一步

public class ArtisanServiceCollect extends AbstractCollect  {
    private String targetPackage;
    public ArtisanServiceCollect(String target_package) {
        this.targetPackage = target_package;
    }
    public void transform(Instrumentation instrumentation) {
        instrumentation.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className,
                                    Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer) {
                if (className == null) {
                    return null;
                }
                if (!className.startsWith(targetPackage.replaceAll("\\.", "/"))) {
                    return null;
                }
                try {
                    return buildCtClass(loader, className.
                            replaceAll("/", ".")).toBytecode();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }
    public CtClass buildCtClass(ClassLoader loader, String className) throws NotFoundException {
        ClassPool pool = new ClassPool();
        pool.insertClassPath(new LoaderClassPath(loader));
        CtClass ctClass = pool.get(className);
        CtMethod[] methods = ctClass.getDeclaredMethods();
        for (CtMethod m : methods) {
            if (!Modifier.isPublic(m.getModifiers())) {
                continue;
            }
            if (Modifier.isStatic(m.getModifiers())) {
                continue;
            }
            if (Modifier.isNative(m.getModifiers())) {
                continue;
            }
            try {
                buildMethod(ctClass, m);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ctClass;
    }
    public void buildMethod(CtClass ctClass, CtMethod oldMethod) throws Exception {
        // copy 一个方法
        // 修改源方法名称 $agent
        // 原方法中 插入模板代码
        CtMethod newMethod = CtNewMethod.copy(oldMethod, ctClass, null);
        oldMethod.setName(oldMethod.getName() + "$agent");
        String beginSrc = String.
                format("Object stat=com.artisan.agent.collect.ServiceCollect.begin(\"%s\",\"%s\");", ctClass.getName(), oldMethod.getName());
        String errorSrc = "com.artisan.agent.collect.ServiceCollect.error(e,stat);";
        String endSrc = "com.artisan.agent.collect.ServiceCollect.end(stat);";
        String template = oldMethod.getReturnType().getName().equals("void") ? voidSource : source;
        newMethod.setBody(String.format(template, beginSrc, newMethod.getName(), errorSrc, endSrc));
        ctClass.addMethod(newMethod);
    }
    public static ServiceStatistics begin(String className, String methodName) {
        ServiceStatistics bean = new ServiceStatistics();
        bean.setBeginTime(System.currentTimeMillis());
        bean.setServiceName(className);
        bean.setMethodName(methodName);
        bean.setModelType("service");
        System.out.println(JSON.toJSONString(bean));
        return bean;
    }
    public static void error(Throwable e, Object obj) {
        ServiceStatistics bean = (ServiceStatistics) obj;
        bean.setErrorType(e.getClass().getSimpleName());
        bean.setErrorMsg(e.getMessage());
    }
    public static void end(Object obj) {
        ServiceStatistics bean = (ServiceStatistics) obj;
        bean.setUseTime(System.currentTimeMillis() - bean.getBeginTime());
        System.out.println(JSON.toJSONString(obj));
    }
    // Object obj= begin (className,methodName)
    // error(err,obj)
    // end(obj)
    final static String source = "{\n"
            + "%s"
            + "        Object result=null;\n"
            + "       try {\n"
            + "            result=($w)%s$agent($$);\n"
            + "        } catch (Throwable e) {\n"
            + "%s"
            + "            throw e;\n"
            + "        }finally{\n"
            + "%s"
            + "        }\n"
            + "        return ($r) result;\n"
            + "}\n";
    final static String voidSource = "{\n"
            + "%s"
            + "        try {\n"
            + "            %s$agent($$);\n"
            + "        } catch (Throwable e) {\n"
            + "%s"
            + "            throw e;\n"
            + "        }finally{\n"
            + "%s"
            + "        }\n"
            + "}\n";
}


写个单元测试测试一下javassit是否生效。

当然了仅仅有这个javassit是无法直接运行的,我们还要依靠javaagent来实现对类的拦截


相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
2月前
|
运维 监控 数据可视化
ARMS的微服务监控
【8月更文挑战第23天】
44 6
|
5月前
|
监控 Java 索引
APM Server监控
APM Server监控
|
2月前
|
监控 前端开发 JavaScript
ARMS的Web应用监控
【8月更文挑战第23天】
46 8
|
2月前
|
监控 JavaScript 前端开发
ARMS的移动应用监控
【8月更文挑战第23天】
41 6
|
5月前
|
Kubernetes 监控 安全
Kustomize 生产实战 - 注入监控 APM Agent
Kustomize 生产实战 - 注入监控 APM Agent
|
2月前
|
Prometheus 监控 前端开发
ARMS设置监控规则
【8月更文挑战第24天】
56 9
|
2月前
|
监控 前端开发 JavaScript
ARMS集成监控代码
【8月更文挑战第24天】
39 6
|
2月前
|
数据采集 运维 监控
ARMS自定义监控
【8月更文挑战第25天】
46 3
|
2月前
|
SQL 运维 监控
ARMS全链路监控
【8月更文挑战第22天】
47 3
|
3月前
|
存储 消息中间件 监控
JVM内存问题之ARMS监控显示堆内存和我设置的不同如何解决
JVM内存问题之ARMS监控显示堆内存和我设置的不同如何解决
下一篇
无影云桌面