接上文
pinpoint支持编写插件来扩展监控的覆盖范围
这里重申下pinpoint一个trace的基本构成(最小单元为span)
插件结构
pinpoint插件由type-provider.yml 和实现组成
type-provider.yml
定义给插件使用的ServiceTypes和AnnotationKeys,并提供给agent, collector and web,概述如下
serviceTypes:- code: <short>name: <String>desc: <String> # May be omitted, defaulting to the same value as name.property: # May be omitted, all properties defaulting to false.terminal: <boolean> # May be omitted, defaulting to false.queue: <boolean> # May be omitted, defaulting to false.recordStatistics: <boolean> # May be omitted, defaulting to false.includeDestinationId: <boolean> # May be omitted, defaulting to false.alias: <boolean> # May be omitted, defaulting to false. matcher: # May be omittedtype: <String> # Any one of 'args', 'exact', 'none'code: <int> # Annotation key code - required only if type is 'exact'annotationKeys:- code: <int>name: <String>property: # May be omitted, all properties defaulting to false.viewInRecordSet: <boolean>
ServiceTypes和AnnotationKeys在agent加载时实例化,可以使用ServiceTypeProvider和AnnotationKeyProvider获得,例如配置为:
// ServiceType
ServiceType serviceType = ServiceTypeProvider.getByCode(1000); // by ServiceType code
ServiceType serviceType = ServiceTypeProvider.getByName("NAME"); // by ServiceType name
// AnnotationKey
AnnotationKey annotationKey = AnnotationKeyProvider.getByCode("100");
ServiceType
每个Span和SpanEvent都包含一个ServiceType。ServiceType表示被跟踪方法属于哪个库,以及应如何处理Span和SpanEvent。
code必须使用其相应类别中的值。下表显示了这些类别及其代码范围。
code必须是唯一的。因此,如果你正在编写一个将公开共享的插件,你必须联系Pinpoint开发团队以获得分配的ServiceType code。如果你的插件是私有的,你可以从下表中自由选择ServiceType代码的值。
ServiceTypes有以下属性。
AnnotationKey
可以用更多信息注释Span和SpanEvent。Annotation是一个键值对,其中键是AnnotationKey类型,值是基元类型、String或byte[]。常用的注释类型有预定义的AnnotationKeys,但如果还不够,您可以在type-provider.yml中定义自己的键。
如果你正在编写一个供公众使用的插件,并希望添加一个新的AnnotationKey,你必须联系Pinpoint开发团队以分配AnnotationKey代码。如果你的插件是私有的,你可以安全地选择一个900到999之间的值作为AnnotationKey的code。
下表显示了AnnotationKey属性。
Profiler插件
插件通过java agent技术动态修改class字节码来实现收集数据
工作流程如下:
- Pinpoint Agent随JVM一同启动
- Pinpoint Agent加载插件目录下的所有插件
- Pinpoint Agent调用每个插件的ProfilerPlugin.setup(ProfilerPluginSetupContext)方法
- 业务应用启动
- 每当一个业务类被加载时,Pinpoint Agent都会查找注册到该类的TransformerCallback
- 如果注册了TransformerCallback,则Agent将调用其doInTransform方法
- TransformerCallback修改业务类的字节码。(例如,添加拦截器、添加字段等)
- 修改后的字节码返回给JVM,类加载修改后的字节码
- 应用程序继续运行
- 当调用修改后的方法时,会调用注入的拦截器的前后方法。
- 拦截器记录跟踪数据。
源码解析
下载源码,可以看到agent-module的plugins包下已有很多预制的插件
这里我们看下activemq的插接定义
serviceTypes:- code: 8310name: 'ACTIVEMQ_CLIENT'property:queue: truerecordStatistics: truematcher:type: 'exact'code: 100 # message.queue.url- code: 8311name: 'ACTIVEMQ_CLIENT_INTERNAL'desc: 'ACTIVEMQ_CLIENT'matcher:type: 'args'annotationKeys:- code: 101name: 'activemq.broker.address'property:viewInRecordSet: true- code: 102name: 'activemq.message'property:viewInRecordSet: true
插件主类为ActiveMQClientPlugin
@Overridepublic void setup(ProfilerPluginSetupContext context) {ActiveMQClientPluginConfig config = new ActiveMQClientPluginConfig(context.getConfig());if (!config.isTraceActiveMQClient()) {logger.info("{} disabled", this.getClass().getSimpleName());return;}logger.info("{} config:{}", this.getClass().getSimpleName(), config);if (config.isTraceActiveMQClientConsumer() || config.isTraceActiveMQClientProducer()) {this.addTransportEditor();this.addConnectionEditor();
// this.addMessageDispatchChannelEditor();if (config.isTraceActiveMQClientProducer()) {this.addProducerEditor();}if (config.isTraceActiveMQClientConsumer()) {this.addConsumerEditor();}}}
setup方法中根据配置分别添加传输、连接、生产者、消费者的Transform类
private void addTransportEditor() {transformTemplate.transform("org.apache.activemq.transport.failover.FailoverTransport", FailoverTransportTransform.class);transformTemplate.transform("org.apache.activemq.transport.tcp.TcpTransport", TcpTransportTransform.class);}
private void addConnectionEditor() {transformTemplate.transform("org.apache.activemq.ActiveMQConnection", ActiveMQConnectionTransform.class);}
private void addProducerEditor() {transformTemplate.transform("org.apache.activemq.ActiveMQMessageProducer", ActiveMQMessageProducerTransform.class);}
private void addConsumerEditor() {transformTemplate.transform("org.apache.activemq.ActiveMQMessageConsumer", ActiveMQMessageConsumerTransform.class);transformTemplate.transform("org.apache.activemq.command.MessageDispatch", AddAsyncContextAccessorTransform.class);transformTemplate.transform("org.apache.activemq.command.ActiveMQMessage", AddAsyncContextAccessorTransform.class);}
这些Transform类实现TransformCallback,重写doInTransform方法,改写目标类
public static class ActiveMQMessageConsumerTransform implements TransformCallback {@Overridepublic byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);ActiveMQClientPluginConfig config = new ActiveMQClientPluginConfig(instrumentor.getProfilerConfig());Filter<String> excludeDestinationFilter = config.getExcludeDestinationFilter();boolean traceActiveMQTextMessage = config.isTraceActiveMQTextMessage();target.addGetter(ActiveMQSessionGetter.class, "session");final InstrumentMethod dispatchMethod = target.getDeclaredMethod("dispatch", "org.apache.activemq.command.MessageDispatch");if (dispatchMethod != null) {dispatchMethod.addScopedInterceptor(ActiveMQMessageConsumerDispatchInterceptor.class, va(excludeDestinationFilter), ActiveMQClientConstants.ACTIVEMQ_CLIENT_SCOPE);}InstrumentMethod receive = target.getDeclaredMethod("receive");if (receive != null) {receive.addScopedInterceptor(ActiveMQMessageConsumerReceiveInterceptor.class, va(traceActiveMQTextMessage), ActiveMQClientConstants.ACTIVEMQ_CLIENT_SCOPE);}InstrumentMethod receiveWithParam = target.getDeclaredMethod("receive", "long");if (receiveWithParam != null) {receiveWithParam.addScopedInterceptor(ActiveMQMessageConsumerReceiveInterceptor.class, va(traceActiveMQTextMessage), ActiveMQClientConstants.ACTIVEMQ_CLIENT_SCOPE);}InstrumentMethod receiveNoWait = target.getDeclaredMethod("receiveNoWait");if (receiveNoWait != null) {receiveNoWait.addScopedInterceptor(ActiveMQMessageConsumerReceiveInterceptor.class, va(traceActiveMQTextMessage), ActiveMQClientConstants.ACTIVEMQ_CLIENT_SCOPE);}InstrumentMethod createActiveMQMessage = target.getDeclaredMethod("createActiveMQMessage", "org.apache.activemq.command.MessageDispatch");if (createActiveMQMessage != null) {createActiveMQMessage.addInterceptor(ActiveMQMessageConsumerCreateActiveMQMessageInterceptor.class);}return target.toBytecode();}}
例如这里对org.apache.activemq.command.MessageDispatch的dispatch方法增加了ActiveMQMessageConsumerDispatchInterceptor拦截器
ActiveMQMessageConsumerDispatchInterceptor实现AroundInterceptor,分别在before和after中增加trace
@Overridepublic void before(Object target, Object[] args) {if (!validate(target, args)) {return;}if (isDebug) {logger.beforeInterceptor(target, args);}try {final Trace trace = createTrace(target, args);if (trace == null) {return;}if (!trace.canSampled()) {return;}// ------------------------------------------------------SpanEventRecorder recorder = trace.traceBlockBegin();recorder.recordServiceType(ActiveMQClientConstants.ACTIVEMQ_CLIENT_INTERNAL);AsyncContextAccessor accessor = ArrayArgumentUtils.getArgument(args, 0, AsyncContextAccessor.class);if (accessor != null) {AsyncContext asyncContext = recorder.recordNextAsyncContext();accessor._$PINPOINT$_setAsyncContext(asyncContext);}} catch (Throwable th) {if (logger.isWarnEnabled()) {logger.warn("BEFORE. Caused:{}", th.getMessage(), th);}}}@Overridepublic void after(Object target, Object[] args, Object result, Throwable throwable) {if (!validate(target, args)) {return;}if (isDebug) {logger.afterInterceptor(target, args, result, throwable);}final Trace trace = traceContext.currentRawTraceObject();if (trace == null) {return;}if (!trace.canSampled()) {traceContext.removeTraceObject();return;}try {SpanEventRecorder recorder = trace.currentSpanEventRecorder();recorder.recordApi(methodDescriptor);if (throwable != null) {recorder.recordException(throwable);}} catch (Throwable th) {if (logger.isWarnEnabled()) {logger.warn("after. Caused:{}", th.getMessage(), th);}} finally {traceContext.removeTraceObject();trace.traceBlockEnd();trace.close();}}
实操
当前采集的servlet事件如下:
我们找到tomcat对应插件的interceptor类
在after方法里增加一个属性的返回
编译并打包
./mvnw install -Prelease -DskipTests=true -e -X
成功打出pinpoint-agent包
注意:如果在编译中遇到如下maven-compiler-plugin报错,可以尝试增加参数重新编译
重新部署agent
nohup java -jar -javaagent:pinpoint-agent-3.0.1-SNAPSHOT/pinpoint-bootstrap.jar -Dpinpoint.agentId=test-agent1 -Dprofiler.sampling.type=PERCENT -Dprofiler.sampling.percent.sampling-rate=100 -Dpinpoint.applicationName=businesstest1 businesstest-0.0.1-SNAPSHOT.jar &
新的调用链中已经增加了属性