pinpoint 文档_手把手教你定制自己的Pinpoint插件

本文介绍了如何根据需求定制Pinpoint插件,包括插件装配原理、元数据注册、配置检测和TransformCallback类的添加。通过这些步骤,可以将自编写的插件打包放入pinpoint-agent/plugins目录,实现APM工具的个性化功能。
摘要由CSDN通过智能技术生成

 Pinpoint是款非常优秀的APM工具,但是其默认实现的功能并不能满足我们的需求,今天我们就来说说如何定制自己的插件。

a416dd7d51ca60c3942f07c5c2b988d2.png

01

背景

前面文章我们曾介绍过Pinpoint这款非常优秀的APM工具(参见《如何将业务应用日志与Pinpoint链路关联起来》),但是其默认实现的功能并不能满足我们的需求,如2.0之前版本不支持多线程链路展示。这时就需要我们自行定制插件了,今天我们就来说说如何定制自己的插件。

02

插件装配原理

首先来看一张图,pinpoint plugin的目录结构: d88e47655d0017bfc45cdaba833e00e2.png 是不是很熟悉,没错,Pinpoint的插件机制就是基于java SPI的ServiceLoader机制实现的。 Pinpoint agent在启动的时候会加载plugins文件夹下所有的插件。它会扫描插件jar包中 META-INF/services 目录下的两个配置文件来确认ProfilerPlugin和TraceMetadataProvider的实现类。 META-INF/services/com.naercorp.pinpoint.bootstrap.plugin.ProfilerPlugin:
com.navercorp.pinpoint.plugin.thread.ThreadPlugin

META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider:

com.navercorp.pinpoint.plugin.thread.ThreadTypeProvider

03

元数据注册

package com.navercorp.pinpoint.common.trace;public interface TraceMetadataProvider {    void setup(TraceMetadataSetupContext var1);}
里面只有一个setup方法,我们实现类使用setup方法注册定制的ServiceType和AnnotationKey:
public class ThreadTypeProvider implements TraceMetadataProvider {    @Override    public void setup(TraceMetadataSetupContext context) {        context.addServiceType(ThreadConstants.SERVICE_TYPE);        context.addAnnotationKey(ThreadConstants.ZEYE_TRACEID_ANNOTATION_KEY);    }}...此处省略1w行代码...public static final ServiceType SERVICE_TYPE = ServiceTypeFactory.of(6001, SCOPE_NAME);public static final AnnotationKey ZEYE_TRACEID_ANNOTATION_KEY = AnnotationKeyFactory.of(919, "zeye.traceId", new AnnotationKeyProperty[]{AnnotationKeyProperty.VIEW_IN_RECORD_SET});
需要注意的是这里,ServiceType和AnnotationKey的code都是有范围的,这部分内容官方文档很详细了。 84b3814e36d80d42b747b2227719f2a7.png 7c9527592c6f7fcb9d091f5455e2e489.png

04

开始开发自己的插件自定义插件必须实现ProfilerPlugin接口,且只需要实现一个setup方法。在这个方法里,我们需要做两件事:
  1. 配置检测
  2. 给指定类添加注册TransformCallback类
@Override    public void setup(ProfilerPluginSetupContext context) {        ThreadConfig threadConfig = new ThreadConfig(context.getConfig()); // #1        String threadMatchPackages = threadConfig.getThreadMatchPackage();        if (StringUtils.isEmpty(threadMatchPackages)) {            logger.info("thread plugin package is empty,skip it");            return;        }        List<String> threadMatchPackageList = StringUtils.tokenizeToStringList(threadMatchPackages, ",");        for (String threadMatchPackage : threadMatchPackageList) {            addRunnableInterceptor(threadMatchPackage); // #2            addCallableInterceptor(threadMatchPackage); // #3        }    }
小编这里举例的是多线程插件,上面片段代码中#1为读取相关配置项,配置文件是pinpoint.config。这里的配置项是根据package前缀匹配类名。
############################################################ Thread############################################################ which package of runnable(callable) instance can be thread plugin trace# Set the package name to track# eg) profiler.thread.match.package=com.company.shopping.cartprofiler.thread.match.package=cn.gov.zcy.thread,cn.gov.zcy.dubbo.test
#2和#3是为匹配到的类注册拦截器。
private void addRunnableInterceptor(String threadMatchPackage) {        Matcher matcher = Matchers.newPackageBasedMatcher(threadMatchPackage, new InterfaceInternalNameMatcherOperand("java.lang.Runnable", true));        transformTemplate.transform(matcher, new RunnableTransformCallback());    }private void addCallableInterceptor(String threadMatchPackage) {        Matcher matcher = Matchers.newPackageBasedMatcher(threadMatchPackage, new InterfaceInternalNameMatcherOperand("java.util.concurrent.Callable", true));        transformTemplate.transform(matcher, new CallableTransformCallback());    }
6613b118ea20885ebcc0177b34831805.png 拦截器工作原理示意图(图片来自网络) 上面两个方法注册拦截器逻辑相似,我们以transformTemplate.transform(matcher, new RunnableTransformCallback())为例进行讲解。
public static class RunnableTransformCallback implements TransformCallback {        @Override        public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {            final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);            logger.info("ThreadPlugin targetName: {}", target.getName());            List allConstructor = new ArrayList();            allConstructor.add(target.getConstructor(null));            // #4 注册构造方法拦截器            for (InstrumentMethod instrumentMethod : allConstructor) {                logger.info("ThreadPlugin className: {} instrumentMethod: {}", className, instrumentMethod);                instrumentMethod.addScopedInterceptor(ThreadConstructorInterceptor.class.getName(), ThreadConstants.SCOPE_NAME, ExecutionPolicy.ALWAYS);            }          // #5 动态注册属性,主要用来做数据传递            target.addField(AsyncContextAccessor.class.getName());            target.addField(ZeyeTraceIdAccessor.class.getName());          // #6 注册方法拦截器            final InstrumentMethod runMethod = target.getDeclaredMethod("run");            if (runMethod != null) {                logger.info("runMethod: {}", runMethod.getName());                runMethod.addInterceptor(ThreadCallInterceptor.class.getName());            }            // #7 返回类字节码            return target.toBytecode();        }    }
关键步骤的注释已经进行了说明。这里要说明的一点是,本例中同时注册了构造方法拦截器和方法拦截器,原因是本例是做多线程上下文传递的,即需要在创建多线程时将主线程的上下文信息传递给子线程。 继续看ThreadConstructorInterceptor拦截器的具体实现:
// #8 自定义拦截器必须实现AroundInterceptor或其子类,AroundInterceptor根据参数的个数有多个实现public class ThreadConstructorInterceptor implements AroundInterceptor {    private final PLogger logger = PLoggerFactory.getLogger(this.getClass());    private final boolean isDebug = logger.isDebugEnabled();    private TraceContext traceContext;    private MethodDescriptor descriptor;    public ThreadConstructorInterceptor(TraceContext traceContext, MethodDescriptor descriptor) {        this.traceContext = traceContext;        this.descriptor = descriptor;    }    @Override    public void before(Object target, Object[] args) {        logger.info("ThreadConstructorInterceptor before");        if (isDebug) {            logger.beforeInterceptor(target, args);        }        // #9 获取当前链路追踪上下文        final Trace trace = traceContext.currentTraceObject();        if (trace == null) {            return;        }        // #10 开启一个新的span        final SpanEventRecorder recorder = trace.traceBlockBegin();                // #11 匹配前文中的addField,这里设置field的值        if (target instanceof AsyncContextAccessor) {            final AsyncContext asyncContext = recorder.recordNextAsyncContext();            ((AsyncContextAccessor) target)._$PINPOINT$_setAsyncContext(asyncContext);        }        // #12 匹配前文中的addField,这里设置field的值        if (target instanceof ZeyeTraceIdAccessor) { // 兼容自研zeye            String traceId = MDC.get("traceId");            ((ZeyeTraceIdAccessor) target)._$PINPOINT$_setZeyeTraceId(traceId);        }    }    @Override    public void after(Object target, Object[] args, Object result, Throwable throwable) {        if (isDebug) {            logger.afterInterceptor(target, args, result, throwable);        }        final Trace trace = traceContext.currentTraceObject();        logger.info("ThreadConstructorInterceptor after trace: {}", trace);        if (trace == null) {            return;        }        try {            // #13 记录span信息            final SpanEventRecorder recorder = trace.currentSpanEventRecorder();            recorder.recordApi(this.descriptor);            recorder.recordServiceType(ThreadConstants.SERVICE_TYPE);            recorder.recordException(throwable);            // #14 上面几项是pinpoint默认属性,这里的recordAttribute可以定制自己的属性,并展示在链路查看页面上            recorder.recordAttribute(ThreadConstants.ZEYE_TRACEID_ANNOTATION_KEY, MDC.get("traceId"));        } finally {            trace.traceBlockEnd();        }    }    }
Pinpoint的AroundInterceptor有点类似spring AOP中的around切面,但相对简单很多,我们只需要按照约定实现自己的逻辑即可。 ThreadCallInterceptor拦截器实现和上面的ThreadConstructorInterceptor类似,这里就不做多余说明,直接贴出代码,小伙伴可以自己阅读。
public class ThreadCallInterceptor extends AsyncContextSpanEventSimpleAroundInterceptor {    private final PLogger logger = PLoggerFactory.getLogger(this.getClass());    public ThreadCallInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor) {        super(traceContext, methodDescriptor);    }    @Override    protected void doInBeforeTrace(SpanEventRecorder recorder, AsyncContext asyncContext, Object target, Object[] args) {        try {            logger.info("ThreadCallInterceptor doInBeforeTrace: {}, {}", args, target);            if (target instanceof ZeyeTraceIdAccessor) {                String traceId = ((ZeyeTraceIdAccessor) target)._$PINPOINT$_getZeyeTraceId();                logger.info("ThreadCallInterceptor doInBeforeTrace Zeye traceId: {}", traceId);                if (traceId != null && !"".equals(traceId)) {                    MDC.put("traceId", traceId);                }            }        } catch (Exception e) {            e.printStackTrace();        }    }    @Override    protected void doInAfterTrace(SpanEventRecorder recorder, Object target, Object[] args, Object result, Throwable throwable) {        recorder.recordApi(methodDescriptor);        recorder.recordServiceType(ThreadConstants.SERVICE_TYPE);        recorder.recordException(throwable);        recorder.recordAttribute(ThreadConstants.ZEYE_TRACEID_ANNOTATION_KEY, MDC.get("traceId"));    }}
这里需要注意的是多线程拦截器必须实现AsyncContextSpanEventSimpleAroundInterceptor接口。

05

发布插件

最后,我们将自己编写的插件打包,生成的jar包丢到pinpoint-agent/plugins/目录下就可以了。最终本文定制的多线程插件实现效果如下图示:

e36852505f1e74a6026f914badbdddac.png

参考文档:

https://github.com/naver/pinpoint/tree/master/plugins

https://naver.github.io/pinpoint/plugindevguide.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值