From 95feda2379422de7b4076b3e1ef7efeb8db6f948 Mon Sep 17 00:00:00 2001 From: Wing <69376949+wingwong-knh@users.noreply.github.com> Date: Wed, 21 Apr 2021 07:51:50 +0800 Subject: [PATCH] Refine Java-Plugin-Development-Guide.md (#6796) --- .../guides/Java-Plugin-Development-Guide.md | 194 +++++++++--------- 1 file changed, 93 insertions(+), 101 deletions(-) diff --git a/docs/en/guides/Java-Plugin-Development-Guide.md b/docs/en/guides/Java-Plugin-Development-Guide.md index 119a81202a..0b60623f90 100644 --- a/docs/en/guides/Java-Plugin-Development-Guide.md +++ b/docs/en/guides/Java-Plugin-Development-Guide.md @@ -67,13 +67,13 @@ See the following examples, where we use the Apache HTTPComponent client plugin ``` ### ContextSnapshot -Besides across process, across thread but in a process need to be supported, because async process(In-memory MQ) -and batch process are common in Java. Across process and across thread are similar, because they are both about propagating -context. The only difference is that, don't need to serialize for across thread. +Besides cross-process tracing, cross-thread tracing has to be supported as well. For instance, both async process (in-memory MQ) +and batch process are common in Java. Cross-process and cross-thread tracing are very similar in that they both require propagating +context, except that cross-thread tracing does not require serialization. -Here are the three steps about across thread propagation: +Here are the three steps on cross-thread propagation: 1. Use `ContextManager#capture` to get the ContextSnapshot object. -1. Let the sub-thread access the ContextSnapshot by any way, through method arguments or carried by an existed arguments +1. Let the sub-thread access the ContextSnapshot through method arguments or being carried by existing arguments 1. Use `ContextManager#continued` in sub-thread. ## Core APIs @@ -84,20 +84,20 @@ ContextManager provides all major and primary APIs. ```java public static AbstractSpan createEntrySpan(String endpointName, ContextCarrier carrier) ``` -Create EntrySpan by operation name(e.g. service name, uri) and **ContextCarrier**. +Create EntrySpan according to the operation name (e.g. service name, uri) and **ContextCarrier**. 2. Create LocalSpan ```java public static AbstractSpan createLocalSpan(String endpointName) ``` -Create LocalSpan by operation name(e.g. full method signature) +Create LocalSpan according to the operation name (e.g. full method signature). 3. Create ExitSpan ```java public static AbstractSpan createExitSpan(String endpointName, ContextCarrier carrier, String remotePeer) ``` -Create ExitSpan by operation name(e.g. service name, uri) and new **ContextCarrier** and peer address -(e.g. ip+port, hostname+port) +Create ExitSpan according to the operation name (e.g. service name, uri) and the new **ContextCarrier** and peer address +(e.g. ip+port, hostname+port). ### AbstractSpan ```java @@ -144,34 +144,32 @@ Create ExitSpan by operation name(e.g. service name, uri) and new **ContextCarri */ AbstractSpan setOperationName(String endpointName); ``` -Besides setting operation name, tags and logs, two attributes should be set, which are component and layer, -especially for EntrySpan and ExitSpan +Besides setting the operation name, tags and logs, two attributes must be set, namely the component and layer. This is especially important for the EntrySpan and ExitSpan. -SpanLayer is the catalog of span. Here are 5 values: +SpanLayer is the type of span. There are 5 values: 1. UNKNOWN (default) 1. DB -1. RPC_FRAMEWORK, for a RPC framework, not an ordinary HTTP +1. RPC_FRAMEWORK (designed for the RPC framework, rather than an ordinary HTTP call) 1. HTTP 1. MQ -Component IDs are defined and reserved by SkyWalking project. -For component name/ID extension, please follow [Component library definition and extension](Component-library-settings.md) document. +Component IDs are defined and reserved by the SkyWalking project. +For extension of the component name/ID, please follow the [component library definitions and extensions](Component-library-settings.md) document. ### Special Span Tags -All tags are available in the trace view, meanwhile, -in the OAP backend analysis, some special tag or tag combination could provide other advanced features. +All tags are available in the trace view. Meanwhile, in the OAP backend analysis, some special tags or tag combinations provide other advanced features. #### Tag key `status_code` -The value should be an integer. The response code of OAL entities is according to this. +The value should be an integer. The response code of OAL entities corresponds to this value. -#### Tag key `db.statement` and `db.type`. -The value of `db.statement` should be a String, representing the Database statement, such as SQL, or `[No statement]/`+span#operationName if value is empty. -When exit span has this tag, OAP samples the slow statements based on `agent-analyzer/default/maxSlowSQLLength`. -The threshold of slow statement is defined by following [`agent-analyzer/default/slowDBAccessThreshold`](../setup/backend/slow-db-statement.md) +#### Tag keys `db.statement` and `db.type`. +The value of `db.statement` should be a string that represents the database statement, such as SQL, or `[No statement]/`+span#operationName if the value is empty. +When the exit span contains this tag, OAP samples the slow statements based on `agent-analyzer/default/maxSlowSQLLength`. +The threshold of slow statement is defined in accordance with [`agent-analyzer/default/slowDBAccessThreshold`](../setup/backend/slow-db-statement.md) -#### Extension logic endpoint. Tag key `x-le` -Logic endpoint is a concept, which doesn't represent a real RPC call, but requires the statistic. -The value of `x-le` should be JSON format, with two options. +#### Extension logic endpoint: Tag key `x-le` +The logic endpoint is a concept that doesn't represent a real RPC call, but requires the statistic. +The value of `x-le` should be in JSON format. There are two options: 1. Define a separated logic endpoint. Provide its own endpoint name, latency and status. Suitable for entry and local span. ```json { @@ -189,8 +187,7 @@ The value of `x-le` should be JSON format, with two options. ### Advanced APIs #### Async Span APIs -There is a set of advanced APIs in Span, which work specific for async scenario. When tags, logs, attributes(including end time) of the span -needs to set in another thread, you should use these APIs. +There is a set of advanced APIs in Span which is specifically designed for async use cases. When tags, logs, and attributes (including end time) of the span need to be set in another thread, you should use these APIs. ```java /** @@ -218,46 +215,45 @@ needs to set in another thread, you should use these APIs. */ AbstractSpan asyncFinish(); ``` -1. Call `#prepareForAsync` in original context. -1. Do `ContextManager#stopSpan` in original context when your job in current thread is done. +1. Call `#prepareForAsync` in the original context. +1. Run `ContextManager#stopSpan` in the original context when your job in the current thread is complete. 1. Propagate the span to any other thread. -1. After all set, call `#asyncFinish` in any thread. -1. Tracing context will be finished and report to backend when all spans's `#prepareForAsync` finished(Judged by count of API execution). +1. Once the above steps are all set, call `#asyncFinish` in any thread. +1. When `#prepareForAsync` is complete for all spans, the tracing context will be finished and will report to the backend (based on the count of API execution). ## Develop a plugin ### Abstract -The basic method to trace is intercepting a Java method, by using byte code manipulation tech and AOP concept. -SkyWalking boxed the byte code manipulation tech and tracing context propagation, -so you just need to define the intercept point(a.k.a. aspect pointcut in Spring) +The basic method to trace is to intercept a Java method, by using byte code manipulation tech and AOP concept. +SkyWalking has packaged the byte code manipulation tech and tracing context propagation, +so you simply have to define the intercept point (a.k.a. aspect pointcut in Spring). ### Intercept -SkyWalking provide two common defines to intercept constructor, instance method and class method. -* Extend `ClassInstanceMethodsEnhancePluginDefine` defines `constructor` intercept points and `instance method` intercept points. -* Extend `ClassStaticMethodsEnhancePluginDefine` defines `class method` intercept points. +SkyWalking provides two common definitions to intercept constructor, instance method and class method. +* Extend `ClassInstanceMethodsEnhancePluginDefine` to define `constructor` intercept points and `instance method` intercept points. +* Extend `ClassStaticMethodsEnhancePluginDefine` to define `class method` intercept points. -Of course, you can extend `ClassEnhancePluginDefine` to set all intercept points. But it is unusual. +Of course, you can extend `ClassEnhancePluginDefine` to set all intercept points, although it is uncommon to do so. ### Implement plugin -I will demonstrate about how to implement a plugin by extending `ClassInstanceMethodsEnhancePluginDefine` +See the following demonstration on how to implement a plugin by extending `ClassInstanceMethodsEnhancePluginDefine`. -1. Define the target class name +1. Define the target class name. ```java protected abstract ClassMatch enhanceClass(); ``` -ClassMatch represents how to match the target classes, there are 4 ways: -* byName, through the full class name(package name + `.` + class name) -* byClassAnnotationMatch, through the class existed certain annotations. -* byMethodAnnotationMatch, through the class's method existed certain annotations. -* byHierarchyMatch, through the class's parent classes or interfaces +ClassMatch represents how to match the target classes. There are 4 ways: +* `byName`: Based on the full class names (package name + `.` + class name). +* `byClassAnnotationMatch`: Depends on whether there are certain annotations in the target classes. +* `byMethodAnnotationMatch`: Depends on whether there are certain annotations in the methods of the target classes. +* `byHierarchyMatch`: Based on the parent classes or interfaces of the target classes. -**Attentions**: -* Never use `ThirdPartyClass.class` in the instrumentation definitions, such as `takesArguments(ThirdPartyClass.class)`, or `byName(ThirdPartyClass.class.getName())`, because of the fact that `ThirdPartyClass` dose not necessarily exist in the target application and this will break the agent; we have `import` checks to help on checking this in CI, but it doesn't cover all scenarios of this limitation, so never try to work around this limitation by something like using full-qualified-class-name (FQCN), i.e. `takesArguments(full.qualified.ThirdPartyClass.class)` and `byName(full.qualified.ThirdPartyClass.class.getName())` will pass the CI check, but are still invalid in the agent codes, **Use Full Qualified Class Name String Literature Instead**. -* Even you are perfectly sure that the class to be intercepted exists in the target application (such as JDK classes), still, don't use `*.class.getName()` to get the class String name. Recommend you to use literal String. This is for -avoiding ClassLoader issues. -* `by*AnnotationMatch` doesn't support the inherited annotations. -* Don't recommend to use `byHierarchyMatch`, unless it is really necessary. Because using it may trigger intercepting -many unexcepted methods, which causes performance issues and concerns. +**Attention**: +* Never use `ThirdPartyClass.class` in the instrumentation definitions, such as `takesArguments(ThirdPartyClass.class)`, or `byName(ThirdPartyClass.class.getName())`, because of the fact that `ThirdPartyClass` dose not necessarily exist in the target application and this will break the agent; we have `import` checks to assist in checking this in CI, but it doesn't cover all scenarios of this limitation, so never try to work around this limitation by something like using full-qualified-class-name (FQCN), i.e. `takesArguments(full.qualified.ThirdPartyClass.class)` and `byName(full.qualified.ThirdPartyClass.class.getName())` will pass the CI check, but are still invalid in the agent codes. Therefore, **Use Full Qualified Class Name String Literature Instead**. +* Even if you are perfectly sure that the class to be intercepted exists in the target application (such as JDK classes), still, do not use `*.class.getName()` to get the class String name. We recommend you to use a literal string. This is to avoid ClassLoader issues. +* `by*AnnotationMatch` does not support inherited annotations. +* We do not recommend using `byHierarchyMatch` unless necessary. Using it may trigger the interception of +many unexcepted methods, which would cause performance issues. Example: ```java @@ -268,7 +264,7 @@ protected ClassMatch enhanceClassName() { ``` -2. Define an instance method intercept point +2. Define an instance method intercept point. ```java public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints(); @@ -288,17 +284,17 @@ public interface InstanceMethodsInterceptPoint { boolean isOverrideArgs(); } ``` -Also use `Matcher` to set the target methods. Return **true** in `isOverrideArgs`, if you want to change the argument +You may also use `Matcher` to set the target methods. Return **true** in `isOverrideArgs`, if you want to change the argument ref in interceptor. The following sections will tell you how to implement the interceptor. -3. Add plugin define into skywalking-plugin.def file +3. Add plugin definition into the `skywalking-plugin.def` file. ```properties tomcat-7.x/8.x=TomcatInstrumentation ``` -4. Set up `witnessClasses` and/or `witnessMethods` if the instrumentation should be activated in specific versions. +4. Set up `witnessClasses` and/or `witnessMethods` if the instrumentation has to be activated in specific versions. Example: @@ -320,12 +316,12 @@ tomcat-7.x/8.x=TomcatInstrumentation return witnessMethodList; } ``` - For more example, see [WitnessTest.java](../../../apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java) + For more examples, see [WitnessTest.java](../../../apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java) ### Implement an interceptor -As an interceptor for an instance method, the interceptor implements +As an interceptor for an instance method, it has to implement `org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor` ```java /** @@ -361,10 +357,10 @@ public interface InstanceMethodsAroundInterceptor { Throwable t); } ``` -Use the core APIs in before, after and exception handle stages. +Use the core APIs before and after calling the method, as well as during exception handling. -### Do bootstrap class instrumentation. -SkyWalking has packaged the bootstrap instrumentation in the agent core. It is easy to open by declaring it in the Instrumentation definition. +### Bootstrap class instrumentation. +SkyWalking has packaged the bootstrap instrumentation in the agent core. You can easily implement it by declaring it in the instrumentation definition. Override the `public boolean isBootstrapInstrumentation()` and return **true**. Such as ```java @@ -403,24 +399,23 @@ public class URLInstrumentation extends ClassEnhancePluginDefine { } ``` -**NOTICE**, doing bootstrap instrumentation should only happen in necessary, but mostly it effect the JRE core(rt.jar), -and could make very unexpected result or side effect. +**NOTE**: Bootstrap instrumentation should be used only where necessary. During its actual execution, it mostly affects the JRE core(rt.jar). Defining it other than where necessary could lead to unexpected results or side effects. -### Provide Customization Config for the Plugin -The config could provide different behaviours based on the configurations. SkyWalking plugin mechanism provides the configuration +### Provide custom config for the plugin +The config could provide different behaviours based on the configurations. The SkyWalking plugin mechanism provides the configuration injection and initialization system in the agent core. Every plugin could declare one or more classes to represent the config by using `@PluginConfig` annotation. The agent core -could initialize this class' static field though System environments, System properties, and `agent.config` static file. +could initialize this class' static field through System environments, System properties, and `agent.config` static file. -The `#root()` method in the `@PluginConfig` annotation requires to declare the root class for the initialization process. +The `#root()` method in the `@PluginConfig` annotation requires declaring the root class for the initialization process. Typically, SkyWalking prefers to use nested inner static classes for the hierarchy of the configuration. -Recommend using `Plugin`/`plugin-name`/`config-key` as the nested classes structure of the Config class. +We recommend using `Plugin`/`plugin-name`/`config-key` as the nested classes structure of the config class. -NOTE, because of the Java ClassLoader mechanism, the `@PluginConfig` annotation should be added on the real class used in the interceptor codes. +**NOTE**: because of the Java ClassLoader mechanism, the `@PluginConfig` annotation should be added on the real class used in the interceptor codes. -Such as, in the following example, `@PluginConfig(root = SpringMVCPluginConfig.class)` represents the initialization should -start with using `SpringMVCPluginConfig` as the root. Then the config key of the attribute `USE_QUALIFIED_NAME_AS_ENDPOINT_NAME`, +In the following example, `@PluginConfig(root = SpringMVCPluginConfig.class)` indicates that initialization should +start with using `SpringMVCPluginConfig` as the root. Then, the config key of the attribute `USE_QUALIFIED_NAME_AS_ENDPOINT_NAME` should be `plugin.springmvc.use_qualified_name_as_endpoint_name`. ```java public class SpringMVCPluginConfig { @@ -456,20 +451,20 @@ public class SpringMVCPluginConfig { # Meter Plugin -Java agent plugin could use meter APIs to collect the metrics for backend analysis. +Java agent plugin could use meter APIs to collect metrics for backend analysis. -* `Counter` API represents a single monotonically increasing counter, automatic collect data and report to backend. +* `Counter` API represents a single monotonically increasing counter which automatically collects data and reports to the backend. ```java import org.apache.skywalking.apm.agent.core.meter.MeterFactory; Counter counter = MeterFactory.counter(meterName).tag("tagKey", "tagValue").mode(Counter.Mode.INCREMENT).build(); counter.increment(1d); ``` -1. `MeterFactory.counter` Create a new counter builder with the meter name. -1. `Counter.Builder.tag(String key, String value)` Mark a tag key/value pair. -1. `Counter.Builder.mode(Counter.Mode mode)` Change the counter mode, `RATE` mode means reporting rate to the backend. -1. `Counter.Builder.build()` Build a new `Counter` which is collected and reported to the backend. -1. `Counter.increment(double count)` Increment count to the `Counter`, It could be a positive value. +1. `MeterFactory.counter` creates a new counter builder with the meter name. +1. `Counter.Builder.tag(String key, String value)` marks a tag key/value pair. +1. `Counter.Builder.mode(Counter.Mode mode)` changes the counter mode. `RATE` mode means the reporting rate to the backend. +1. `Counter.Builder.build()` builds a new `Counter` which is collected and reported to the backend. +1. `Counter.increment(double count)` increment counts to the `Counter`. It could be a positive value. * `Gauge` API represents a single numerical value. ```java @@ -478,41 +473,38 @@ import org.apache.skywalking.apm.agent.core.meter.MeterFactory; ThreadPoolExecutor threadPool = ...; Gauge gauge = MeterFactory.gauge(meterName, () -> threadPool.getActiveCount()).tag("tagKey", "tagValue").build(); ``` -1. `MeterFactory.gauge(String name, Supplier getter)` Create a new gauge builder with the meter name and supplier function, this function need to return a `double` value. -1. `Gauge.Builder.tag(String key, String value)` Mark a tag key/value pair. -1. `Gauge.Builder.build()` Build a new `Gauge` which is collected and reported to the backend. +1. `MeterFactory.gauge(String name, Supplier getter)` creates a new gauge builder with the meter name and supplier function. This function must return a `double` value. +1. `Gauge.Builder.tag(String key, String value)` marks a tag key/value pair. +1. `Gauge.Builder.build()` builds a new `Gauge` which is collected and reported to the backend. -* `Histogram` API represents a summary sample observations with customize buckets. +* `Histogram` API represents a summary sample observations with customized buckets. ```java import org.apache.skywalking.apm.agent.core.meter.MeterFactory; Histogram histogram = MeterFactory.histogram("test").tag("tagKey", "tagValue").steps(Arrays.asList(1, 5, 10)).minValue(0).build(); histogram.addValue(3); ``` -1. `MeterFactory.histogram(String name)` Create a new histogram builder with the meter name. -1. `Histogram.Builder.tag(String key, String value)` Mark a tag key/value pair. -1. `Histogram.Builder.steps(List steps)` Set up the max values of every histogram buckets. -1. `Histogram.Builder.minValue(double value)` Set up the minimal value of this histogram, default is `0`. -1. `Histogram.Builder.build()` Build a new `Histogram` which is collected and reported to the backend. -1. `Histogram.addValue(double value)` Add value into the histogram, automatically analyze what bucket count needs to be increment. rule: count into [step1, step2). +1. `MeterFactory.histogram(String name)` creates a new histogram builder with the meter name. +1. `Histogram.Builder.tag(String key, String value)` marks a tag key/value pair. +1. `Histogram.Builder.steps(List steps)` sets up the max values of every histogram buckets. +1. `Histogram.Builder.minValue(double value)` sets up the minimal value of this histogram. Default is `0`. +1. `Histogram.Builder.build()` builds a new `Histogram` which is collected and reported to the backend. +1. `Histogram.addValue(double value)` adds value into the histogram, and automatically analyzes what bucket count needs to be incremented. Rule: count into [step1, step2). # Plugin Test Tool -[Apache SkyWalking Agent Test Tool Suite](https://github.com/apache/skywalking-agent-test-tool) -a tremendously useful test tools suite in a wide variety of languages of Agent. Includes mock collector and validator. -The mock collector is a SkyWalking receiver, like OAP server. +The [Apache SkyWalking Agent Test Tool Suite](https://github.com/apache/skywalking-agent-test-tool) is an incredibly useful test tool suite that is available in a wide variety of agent languages. It includes the mock collector and validator. The mock collector is a SkyWalking receiver, like the OAP server. -You could learn how to use this tool to test the plugin in [this doc](Plugin-test.md). If you want to contribute plugins -to SkyWalking official repo, this is required. +You could learn how to use this tool to test the plugin in [this doc](Plugin-test.md). This is a must if you want to contribute plugins to the SkyWalking official repo. -# Contribute plugins into Apache SkyWalking repository -We are welcome everyone to contribute plugins. +# Contribute plugins to the Apache SkyWalking repository +We welcome everyone to contribute their plugins. -Please follow there steps: -1. Submit an issue about which plugins you are going to contribute, including supported version. -1. Create sub modules under `apm-sniffer/apm-sdk-plugin` or `apm-sniffer/optional-plugins`, and the name should include supported library name and versions +Please follow these steps: +1. Submit an issue for your plugin, including any supported versions. +1. Create sub modules under `apm-sniffer/apm-sdk-plugin` or `apm-sniffer/optional-plugins`, and the name should include supported library name and versions. 1. Follow this guide to develop. Make sure comments and test cases are provided. 1. Develop and test. 1. Provide the automatic test cases. Learn `how to write the plugin test case` from this [doc](Plugin-test.md) -1. Send the pull request and ask for review. -1. The plugin committers approve your plugins, plugin CI-with-IT, e2e and plugin tests passed. -1. The plugin accepted by SkyWalking. +1. Send a pull request and ask for review. +1. The plugin committers will approve your plugins, plugin CI-with-IT, e2e, and the plugin tests will be passed. +1. The plugin is accepted by SkyWalking. -- GitLab