Skywalking website: skywalking.apache.org/

The architecture of Skywalking is shown below:

Skywalking’s agent collects data, sends it to the Collector, aggregates it, stores it, and provides an easy-to-use UI side for viewing monitored metrics.

Let’s start analyzing the source code for Skywalking.

Download the source code and build it

Skywalking uses GRPC to transfer data between server and client for high performance communication, so we need to do a little bit of work after importing. We can refer to docs/en/guides/ how-to build.md to build it.

Pack to build

You can fork the Skywalking source code on Github and download it locally.

/ / direct git clone - recurse submodules HTTP: / / https://github.com/apache/skywalking.git / / or git clone https://github.com/apache/skywalking.git cd skywalking/ git submodule init git submodule updateCopy the code

Execute command:

./mvnw clean package -DskipTests
Copy the code

The final package is in the dist directory

Build source code in IDEA

Open the Skywalking project with IDEA (as maven project import)

Then run the script from the command line in skyWalking’s directory to compile the build (since SkyWalking uses GRPC) :

./mvnw compile -Dmaven.test.skip=false
Copy the code

Then look at the source code generated by the Settings (mainly the source code generated by compiling the Potobuf file)

  • Apm-protocol /apm-network/target/generated-sources/protobuf Select grpc-java and Java under this directory, Right-click Mark Directory As–>Generated Sources Root, As shown in the following figure

  • Oap-server /server-core/target/generated-sources/protobuf grPC-java and Java folders Mark Directory As–> generated sources Root`

  • Oap-server /server-receiver-plugin/receiver-proto/target/generated-sources/ Protobuf Grpc-java and Java folder Mark Directory As–>Generated Sources Root`

  • Oap – server/exporter/target/generated sources/protobuf GRPC – Java and Java folder Mark Directory As – > generated sources Root `

  • The grpc-java and Java folders Mark are in the oap-server/server-configuration/grpc-configuration-sync/target/generated-sources/protobuf directory Directory As–>Generated Sources Root`

  • Oap-server /oal-grammar/target/generated-sources grpc-java and Java folders Mark Directory As–> generated sources Root

Build the source code in Eclipse

1. Import into Eclipse as per maven project

2. Add content to Skywalking /pom.xml

< plugin > < groupId > org. Codehaus. Mojo < / groupId > < artifactId > build - helper - maven - plugin < / artifactId > < version > 1.8 < / version > <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>src/java/main</source> <source>apm-protocol/apm-network/target/generated-sources/protobuf</source> <source>apm-collector/apm-collector-remote/collector-remote-grpc-provider/target/generated-sources/protobuf</source> </sources> </configuration> </execution> </executions> </plugin>Copy the code

3. Add the following to enable eclipse’s M2e plug-in to support extended configuration

<pluginManagement>
    <plugins>
    <!--This plugin"s configuration is used to store Eclipse m2e settings 
    only. It has no influence on the Maven build itself. -->
        <plugin>
            <groupId>org.eclipse.m2e</groupId>
            <artifactId>lifecycle-mapping</artifactId>
            <version>1.0.0</version>
            <configuration>
                <lifecycleMappingMetadata>
                    <pluginExecutions>
                        <pluginExecution>
                            <pluginExecutionFilter>
                                <groupId>org.codehaus.mojo</groupId>
                                <artifactId>build-helper-maven-plugin</artifactId>
                                <versionRange>[1.8,)</versionRange>
                                <goals>
                                    <goal>add-source</goal>
                                </goals>
                            </pluginExecutionFilter>
                        </pluginExecution>
                    </pluginExecutions>
                </lifecycleMappingMetadata>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>
Copy the code

4. Add the following dependencies to the apm-collector-remote/collector-remote-grpc-provider/ PEM. XML file

< the dependency > < groupId > com. Google. Guava < / groupId > < artifactId > guava < / artifactId > < version > 24.0 jre < / version > </dependency>Copy the code

5. Run the command

./mvnw compile -Dmaven.test.skip=true
Copy the code

6. Run the command

Run Maven clean first, then Maven Update

7, execute command:

./mvnw compile
Copy the code

Refresh the project

Source code analysis

The distributed link tracking process of Skywalking is roughly as follows:

  1. Agent Data Collection

  2. The Agent sends data to the Collector

  3. Collector Receives data

  4. The Collector stores the received data in the persistence layer

Here we mainly explore the Agent to collect the data of Java class systems such as Spring, etc., in terms of a simple distributed system of Spring Cloud:

This is a suggested Order system that has Eureka, Order, Product, Stock, and when the Order is placed, Order will call Product, and Product will call Stock.

We started with the APM-Sniffer project.

Apm – agent project

We see that this project has only one class

org.apache.skywalking.apm.agent.SkyWalkingAgent

This class has a method:

/** * main entry, implemented using byte-buddy to enhance all classes defined in the plug-in. */ public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException, IOException { }Copy the code

Agent Data Collection

We focus here on JVM data and Spring-related data

The JVM data

We see there is a class in apm – agent – core: org. Apache. Skywalking. Apm. Agent. The core. The JVM. JVMService

This class implements the BootService and java.lang.Runnable interfaces. How does this class implement some of its methods? In the apm – agent – the core of this project/SRC/main/resources/meta-inf/services/org. Apache. Skywalking. Apm. Agent. The core. The boot. There are many kind of BootService file Fully qualified name information:

org.apache.skywalking.apm.agent.core.remote.TraceSegmentServiceClient
org.apache.skywalking.apm.agent.core.context.ContextManager
org.apache.skywalking.apm.agent.core.sampling.SamplingService
org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager
org.apache.skywalking.apm.agent.core.jvm.JVMService
org.apache.skywalking.apm.agent.core.remote.ServiceAndEndpointRegisterClient
org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService
org.apache.skywalking.apm.agent.core.commands.CommandService
org.apache.skywalking.apm.agent.core.commands.CommandExecutorService
org.apache.skywalking.apm.agent.core.context.OperationNameFormatService
Copy the code

Each of these classes implements the BootService excuse, which is the interface that all remote exchanges need to be implemented when the plug-in mechanism comes into play. The boot method is called when BootService is started.

Org. Apache. Skywalking. Apm. Agent. The core. The boot. The ServiceManager class – this class inside will all achieve BootService class instances execute it again.

The contents of the boot method executed after the JVMService class is instantiated are as follows

@override public void boot() throws Throwable {// Create a single thread pool that continuously collects (produces) metrics. This thread pool is executed periodically (per second). And enforce a JVMService run method collectMetricFuture = Executors. NewSingleThreadScheduledExecutor (new DefaultNamedThreadFactory("JVMService-produce")) .scheduleAtFixedRate(new RunnableWithExceptionProtection(this, new RunnableWithExceptionProtection.CallbackWhenException() { @Override public void handle(Throwable t) { logger.error("JVMService produces metrics failure.", t); } }), 0, 1, TimeUnit.SECONDS); // Create a single thread pool that continuously sends (consumes) data. This thread pool is executed periodically (per second), Sender and enforce a JVMService inner class run method sendMetricFuture = Executors. NewSingleThreadScheduledExecutor (new DefaultNamedThreadFactory("JVMService-consume")) .scheduleAtFixedRate(new RunnableWithExceptionProtection(sender, new RunnableWithExceptionProtection.CallbackWhenException() { @Override public void handle(Throwable t) { logger.error("JVMService consumes and upload failure.", t); } } ), 0, 1, TimeUnit.SECONDS); }Copy the code

The JVMService class run method:

public void run() { if (RemoteDownstreamConfig.Agent.SERVICE_ID ! = DictionaryUtil.nullValue() && RemoteDownstreamConfig.Agent.SERVICE_INSTANCE_ID ! = DictionaryUtil.nullValue() ) { long currentTimeMillis = System.currentTimeMillis(); try { JVMMetric.Builder jvmBuilder = JVMMetric.newBuilder(); jvmBuilder.setTime(currentTimeMillis); jvmBuilder.setCpu(CPUProvider.INSTANCE.getCpuMetric()); jvmBuilder.addAllMemory(MemoryProvider.INSTANCE.getMemoryMetricList()); jvmBuilder.addAllMemoryPool(MemoryPoolProvider.INSTANCE.getMemoryPoolMetricsList()); jvmBuilder.addAllGc(GCProvider.INSTANCE.getGCList()); JVMMetric JVMMetric = jvmBuilder.build(); // After collecting data, place it in the message queue LinkedBlockingQueue<JVMMetric> queue if (! queue.offer(jvmMetric)) { queue.poll(); queue.offer(jvmMetric); } } catch (Exception e) { logger.error(e, "Collect JVM info fail."); }}}Copy the code

The inner Sender class’s run method:

@Override public void run() { if (RemoteDownstreamConfig.Agent.SERVICE_ID ! = DictionaryUtil.nullValue() && RemoteDownstreamConfig.Agent.SERVICE_INSTANCE_ID ! = DictionaryUtil.nullValue() ) { if (status == GRPCChannelStatus.CONNECTED) { try { JVMMetricCollection.Builder builder = JVMMetricCollection.newBuilder(); LinkedList<JVMMetric> buffer = new LinkedList<JVMMetric>(); queue.drainTo(buffer); if (buffer.size() > 0) { builder.addAllMetrics(buffer); builder.setServiceInstanceId(RemoteDownstreamConfig.Agent.SERVICE_INSTANCE_ID); WithDeadlineAfter (GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS).collect(builder.build()); ServiceManager.INSTANCE.findService(CommandService.class).receiveCommand(commands); } } catch (Throwable t) { logger.error(t, "send JVM metrics to Collector fail."); }}}}Copy the code

How is the data sent? Let’s look at the collected metric class jvmmetry.java

public  final class JVMMetric extends
    com.google.protobuf.GeneratedMessageV3 implements
    // @@protoc_insertion_point(message_implements:JVMMetric)
    JVMMetricOrBuilder {
    ...
}
Copy the code

Proto (JVMMetric. Proto, JVMMetric. Proto)

syntax = "proto3"; option java_multiple_files = true; option java_package = "org.apache.skywalking.apm.network.language.agent.v2"; option csharp_namespace = "SkyWalking.NetworkProtocol"; import "common/common.proto"; import "common/JVM.proto"; Service JVMMetricReportService {// The method defined by GRPC. The parameter type is JVMMetricCollection. The return type is: Commands rpc collect (JVMMetricCollection) returns (Commands) { } } message JVMMetricCollection { repeated JVMMetric metrics = 1; int32 serviceInstanceId = 2; }Copy the code

Common.proto reads as follows:

​
syntax = "proto3";
​
option java_multiple_files = true;
option java_package = "org.apache.skywalking.apm.network.common";
option csharp_namespace = "SkyWalking.NetworkProtocol";
​
message KeyStringValuePair {
    string key = 1;
    string value = 2;
}
​
message KeyIntValuePair {
    string key = 1;
    int32 value = 2;
}
​
message CPU {
    double usagePercent = 2;
}
​
// In most cases, detect point should be `server` or `client`.
// Even in service mesh, this means `server`/`client` side sidecar
// `proxy` is reserved only.
enum DetectPoint {
    client = 0;
    server = 1;
    proxy = 2;
}
​
message Commands {
    repeated Command commands = 1;
}
​
message Command {
    string command = 1;
    repeated KeyStringValuePair args = 2;
}
​
enum ServiceType {
    // An agent works inside the normal business application.
    normal = 0;
    // An agent works inside the database.
    database = 1;
    // An agent works inside the MQ.
    mq = 2;
    // An agent works inside the cache server.
    cache = 3;
    // An agent works inside the browser.
    browser = 4;
}
​
Copy the code

JVM. Proto contains the following contents:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.apache.skywalking.apm.network.language.agent";
option csharp_namespace = "SkyWalking.NetworkProtocol";

import "common/common.proto";

message JVMMetric {
    int64 time = 1;
    CPU cpu = 2;
    repeated Memory memory = 3;
    repeated MemoryPool memoryPool = 4;
    repeated GC gc = 5;
}

message Memory {
    bool isHeap = 1;
    int64 init = 2;
    int64 max = 3;
    int64 used = 4;
    int64 committed = 5;
}

message MemoryPool {
    PoolType type = 1;
    int64 init = 2;
    int64 max = 3;
    int64 used = 4;
    int64 commited = 5;
}

enum PoolType {
    CODE_CACHE_USAGE = 0;
    NEWGEN_USAGE = 1;
    OLDGEN_USAGE = 2;
    SURVIVOR_USAGE = 3;
    PERMGEN_USAGE = 4;
    METASPACE_USAGE = 5;
}

message GC {
    GCPhrase phrase = 1;
    int64 count = 2;
    int64 time = 3;
}

enum GCPhrase {
    NEW = 0;
    OLD = 1;
}
Copy the code

How does the service receiver, the Collector, receive?

The receiving class JVMMetricsServiceHandler is dedicated to handling JVM monitoring data. The collect method of this class is as follows:

@Override public void collect(JVMMetrics request, StreamObserver<Downstream> responseObserver) { int serviceInstanceId = request.getApplicationInstanceId(); if (logger.isDebugEnabled()) { logger.debug("receive the jvm metrics from service instance, id: {}", serviceInstanceId); } // Process data, JvmSourceDispatcher sends request.getMetricslist (). ForEach (metrics -> {long minuteTimeBucket = TimeBucket.getMinuteTimeBucket(metrics.getTime()); jvmSourceDispatcher.sendMetric(serviceInstanceId, minuteTimeBucket, metrics); }); responseObserver.onNext(Downstream.newBuilder().build()); responseObserver.onCompleted(); }Copy the code

Then let’s look at the sendMetric method of JVMSourceDispatcher

void sendMetric(int serviceInstanceId, long minuteTimeBucket, JVMMetric metrics) {
    ServiceInstanceInventory serviceInstanceInventory = instanceInventoryCache.get(serviceInstanceId);
    int serviceId;
    if (Objects.nonNull(serviceInstanceInventory)) {
        serviceId = serviceInstanceInventory.getServiceId();
    } else {
        logger.warn("Can"t find service by service instance id from cache, service instance id is: {}", serviceInstanceId);
        return;
    }
​
    this.sendToCpuMetricProcess(serviceId, serviceInstanceId, minuteTimeBucket, metrics.getCpu());
    this.sendToMemoryMetricProcess(serviceId, serviceInstanceId, minuteTimeBucket, metrics.getMemoryList());
    this.sendToMemoryPoolMetricProcess(serviceId, serviceInstanceId, minuteTimeBucket, metrics.getMemoryPoolList());
    this.sendToGCMetricProcess(serviceId, serviceInstanceId, minuteTimeBucket, metrics.getGcList());
}
Copy the code

Then we look at the sendTopCpuMetricProcess method

  private void sendToCpuMetricProcess(int serviceId, int serviceInstanceId, long timeBucket, CPU cpu) {
      ServiceInstanceJVMCPU serviceInstanceJVMCPU = new ServiceInstanceJVMCPU();
      serviceInstanceJVMCPU.setId(serviceInstanceId);
      serviceInstanceJVMCPU.setName(Const.EMPTY_STRING);
      serviceInstanceJVMCPU.setServiceId(serviceId);
      serviceInstanceJVMCPU.setServiceName(Const.EMPTY_STRING);
      serviceInstanceJVMCPU.setUsePercent(cpu.getUsagePercent());
      serviceInstanceJVMCPU.setTimeBucket(timeBucket);
      sourceReceiver.receive(serviceInstanceJVMCPU);
  }
Copy the code

Receive of SourceReceiver to receive data

SourceReceiver, however, is an interface

public interface SourceReceiver extends Service {
    void receive(Source source);
}
Copy the code

This interface has only one implementation class

public class SourceReceiverImpl implements SourceReceiver {
}
Copy the code

Implementation of receive:

@Override public void receive(Source source) {
    dispatcherManager.forward(source);
}
Copy the code

We see that the forward method of DispatcherManager is called again

public void forward(Source source) { if (source == null) { return; } List<SourceDispatcher> dispatchers = dispatcherMap.get(source.scope()); /** * Dispatcher is only generated by oal script analysis result. * So these will/could be possible, the given source doesn"t have the dispatcher, * when the receiver is open, and oal script doesn"t ask for analysis. */ if (dispatchers ! = null) { for (SourceDispatcher dispatcher : dispatchers) { dispatcher.dispatch(source); }}}Copy the code

The Dispatch method of SourceDispatcher is then called

While we see so many classes implementing the SourceDispatcher interface, which method implements it? We can call log can also be simple analyse, can be ruled out first book EndpointCallRElationDispatcher, HttpAccessLogDispatcher,

JaegerSpanRecordDispatcher, ServiceCallRelationDispatcher, ServiceInstanceCallRelationDispatcher, ZipkinSpanRecordDispatche The r classes that we can focus on

DatabaseStatementDispatcher and SegmentDispatcher these two classes, and the DatabaseStatementDispatcher did not be used, so we can focus on analysis SegmentDispatcher this class

public class SegmentDispatcher implements SourceDispatcher<Segment> { @Override public void dispatch(Segment source) { SegmentRecord segment = new SegmentRecord(); segment.setSegmentId(source.getSegmentId()); segment.setTraceId(source.getTraceId()); segment.setServiceId(source.getServiceId()); segment.setServiceInstanceId(source.getServiceInstanceId()); segment.setEndpointName(source.getEndpointName()); segment.setEndpointId(source.getEndpointId()); segment.setStartTime(source.getStartTime()); segment.setEndTime(source.getEndTime()); segment.setLatency(source.getLatency()); segment.setIsError(source.getIsError()); segment.setDataBinary(source.getDataBinary()); segment.setTimeBucket(source.getTimeBucket()); segment.setVersion(source.getVersion()); / / construct SegmentRecord object, and then RecordStreamProcessor in method to deal with (consumption) segment information RecordStreamProcessor. GetInstance (). In (segment); }}Copy the code

Then let’s look at the In method of the RecordStreamProcessor

public void in(Record record) {
    RecordPersistentWorker worker = workers.get(record.getClass());
    if (worker != null) {
        worker.in(record);
    }
}
Copy the code

Then there’s the in method for RecordPersistentWorker

@Override public void in(Record record) { try { InsertRequest insertRequest = recordDAO.prepareBatchInsert(model, record); batchDAO.asynchronous(insertRequest); } catch (IOException e) { logger.error(e.getMessage(), e); }}Copy the code

Here we can see the operation of persisting to the database (calling the relevant interface implementation of ES or H2)

Throughout the process we saw that the JVM data was persisted (H2 or ES) immediately after the Agent sent it to the collector.

Plug-in source code analysis

In the apm-sniffer/ apm-SDK-plugin directory, we can collect the data of the Spring framework by using the java-plugin-developing-guide. md file. Spring-plugins are all spring-related plugins that are used to collect data from the Spring framework

Take the MVC-annotation-4.x-plugin project as an example to see how the Skywalking plug-in was developed.

We can see that the resources directory file SRC/main/resources/skywalking – plugin. Def the skywalking – plugin. Def is used to define the plugin.

spring-mvc-annotation-4.x=org.apache.skywalking.apm.plugin.spring.mvc.v4.define.ControllerInstrumentation spring-mvc-annotation-4.x=org.apache.skywalking.apm.plugin.spring.mvc.v4.define.RestControllerInstrumentation spring-mvc-annotation-4.x=org.apache.skywalking.apm.plugin.spring.mvc.v4.define.HandlerMethodInstrumentation spring-mvc-annotation-4.x=org.apache.skywalking.apm.plugin.spring.mvc.v4.define.InvocableHandlerInstrumentation spring-mvc-annotation-4.x=org.apache.skywalking.apm.plugin.spring.mvc.v4.define.ControllerForLowVersionInstrumentation spring-mvc-annotation-4.x=org.apache.skywalking.apm.plugin.spring.mvc.v4.define.RestControllerForLowVersionInstrumentati onCopy the code

There are a few plugins defined here

  • ControllerInstrumentation

  • RestControllerInstrumentation

  • HandlerMethodInstrumentation

  • InvocableHandlerInstrumentation

  • ControllerForLowVersionInstrumentation

  • RestControllerForLowVersionInstrumentation

We analyze the code according to the plugin’s development process. There should be one class that defines the interception mechanism and another class that defines the enhancement mechanism. Let’s take a look at the structure of the class:

We see AbstractClassEnhancePluginDefine, ClassEnhancePluginDefine, ClassInstanceMethodsEnhancePluginDefine skywalking provides the base class, The class enhancements in this plug-in are inherited from these superclasses.

Let’s look at org. Apache. Skywalking. Apm. Plugin. Spring. MVC. V4. Define. ControllerInstrumentation, We look at the abstract class AbstractSpring4Instrumentation content:

public abstract class AbstractSpring4Instrumentation extends ClassInstanceMethodsEnhancePluginDefine { // Witness_class got me wrong,  public static final String WITHNESS_CLASSES = "org.springframework.cache.interceptor.SimpleKey"; @Override protected String[] witnessClasses() { return new String[] {WITHNESS_CLASSES, "org.springframework.cache.interceptor.DefaultKeyGenerator"}; }}Copy the code

Then its subclasses:

/ * * * ControllerInstrumentation improve all have RequestMapping annotations and Controller of annotation * ControllerConstructorInterceptor class constructor and method Before the constructor will controller in the implementation of the base path (path) on the dynamic inside * * field RequestMappingMethodInterceptor first request was obtained from the dynamic field inside the path, If you don't find * RequestMappingMethodInterceptor will in combination with the annotation and the base path and the current path will be a new path on the dynamic * field inside * @ the author zhangxin * / public abstract Class AbstractControllerInstrumentation extends AbstractSpring4Instrumentation {/ / constructor interception point @ Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { return new ConstructorInterceptPoint[] { new ConstructorInterceptPoint () {/ / match way, Public ElementMatcher<MethodDescription> getConstructorMatcher() {return any(); } / / interceptor class @ Override public String getConstructorInterceptor () {return "org.apache.skywalking.apm.plugin.spring.mvc.v4.ControllerConstructorInterceptor"; }}}; } // The instance method interceptor returns an array, one annotation for @requestMapping, One is @override public for @getMapping, @postMapping, @putMapping, @deletemapping, and @PatchMapping InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { return new InstanceMethodsInterceptPoint[] { new DeclaredInstanceMethodsInterceptPoint () {/ / all have RequestMapping the annotation @ Override public ElementMatcher < MethodDescription > getMethodsMatcher() { return isAnnotatedWith(named("org.springframework.web.bind.annotation.RequestMapping")); } // RequestMappingMethodInterceptor @Override public String getMethodsInterceptor() { return Constants.REQUEST_MAPPING_METHOD_INTERCEPTOR; } @Override public boolean isOverrideArgs() { return false; }}, new DeclaredInstanceMethodsInterceptPoint() { @Override public ElementMatcher<MethodDescription> getMethodsMatcher() { return isAnnotatedWith(named("org.springframework.web.bind.annotation.GetMapping")) .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PostMapping"))) .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PutMapping"))) .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.DeleteMapping"))) .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PatchMapping"))); } // RestMappingMethodInterceptor @Override public String getMethodsInterceptor() { return Constants.REST_MAPPING_METHOD_INTERCEPTOR; } @Override public boolean isOverrideArgs() { return false; }}}; } // Override protected ClassMatch enhanceClass() {// The abstract class does not define the matching method, but gives it to the subclass to implement the getEnhanceAnnotations method. return ClassAnnotationMatch.byClassAnnotationMatch(getEnhanceAnnotations()); } protected abstract String[] getEnhanceAnnotations(); }Copy the code

AbstractControllerInstrumentation this class does not define certain classes of matching And then the ControllerInstrumentation

public class ControllerInstrumentation extends AbstractControllerInstrumentation { public static final String ENHANCE_ANNOTATION = "org.springframework.stereotype.Controller"; // Match all classes with @controller annotation @override protected String[] GetenhanceAnnotation () {return new String[] {ENHANCE_ANNOTATION};  }}Copy the code

Next let’s watch a interceptor class constructor ControllerConstructorInterceptor

/** * The <code>ControllerConstructorInterceptor</code> intercepts the Controller"s constructor, in order to acquire the * mapping annotation, if exist. * * But, you can see we only use the first mapping value, <B>Why? </B> * * Right now, we intercept the controller by annotation as you known, so we CAN"T know which uri patten is actually * matched. Even we know, that costs a lot. * * If we want to resolve that, we must intercept the Spring MVC core codes, that is not a good choice for now. * * Comment by @wu-sheng */ public class ControllerConstructorInterceptor implements InstanceConstructorInterceptor { @Override public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { String basePath = ""; // Get @requestMapping information, RequestMapping basePathRequestMapping = objInst.getClass().getannotation (requestMapping.class); if (basePathRequestMapping ! = null) { if (basePathRequestMapping.value().length > 0) { basePath = basePathRequestMapping.value()[0]; } else if (basePathRequestMapping.path().length > 0) { basePath = basePathRequestMapping.path()[0]; } } EnhanceRequireObjectCache enhanceRequireObjectCache = new EnhanceRequireObjectCache(); enhanceRequireObjectCache.setPathMappingCache(new PathMappingCache(basePath)); objInst.setSkyWalkingDynamicField(enhanceRequireObjectCache); }}Copy the code

Then we see that the plugin only defines the matching form of the classes that need to be enhanced. There is no specific processing logic to create EntrySpan and ExitSpan. Actually the processing logic is defined in AbstractControllerInstrumentation methods intercept set specific process by which class is two main categories: RequestMappingMethodInterceptor RestMappingMethodInterceptor.

One for @requestMapping annotations and one for @getMapping annotations. In fact, @getMapping comes from @requestMapping. GetMapping itself uses @requestMapping, which is equivalent to specifying @requestMapping for method.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
}
Copy the code

RequestMappingMethodInterceptor, RestMappingMethodInterceptor inherited the same parent class:

AbstractMethodInterceptor. These classes themselves override only two methods of the parent class:

    public abstract String getRequestURL(Method method);
​
    public abstract String getAcceptedMethodTypes(Method method);
Copy the code

So we focus on the parent class (AbstractMethodInterceptor) two methods:

  • BeforeMethod (logic beforeMethod calls)

  • AfterMethod (logic afterMethod invocation)

    @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { // forwardRequestFlag Boolean forwardRequestFlag = (Boolean)ContextManager.getRuntimeContext().get(FORWARD_REQUEST_FLAG); /** * Spring MVC plugin does nothing if current request is forward request. * Ref: https://github.com/apache/skywalking/pull/1325 */ if (forwardRequestFlag ! = null && forwardRequestFlag) { return; } String operationName; if (Config.Plugin.SpringMVC.USE_QUALIFIED_NAME_AS_ENDPOINT_NAME) { operationName = MethodUtil.generateOperationName(method); } else { EnhanceRequireObjectCache pathMappingCache = (EnhanceRequireObjectCache)objInst.getSkyWalkingDynamicField(); String requestURL = pathMappingCache.findPathMapping(method); if (requestURL == null) { requestURL = getRequestURL(method); pathMappingCache.addPathMapping(method, requestURL); requestURL = getAcceptedMethodTypes(method) + pathMappingCache.findPathMapping(method); } operationName = requestURL; } // Set operationName to requestURL // get HttpServletRequest HttpServletRequest Request = (HttpServletRequest)ContextManager.getRuntimeContext().get(REQUEST_KEY_IN_RUNTIME_CONTEXT); if (request ! = null) {// Get StackDepth StackDepth = (StackDepth)ContextManager.getRuntimeContext().get(CONTROLLER_METHOD_STACK_DEPTH); If (stackDepth == null) {// New ContextCarrier ContextCarrier = new ContextCarrier(); CarrierItem next = contextCarrier.items(); while (next.hasNext()) { next = next.next(); next.setHeadValue(request.getHeader(next.getHeadKey())); } / / create EntrySpan AbstractSpan span = ContextManager. CreateEntrySpan (operationName contextCarrier); Tags.URL.set(span, request.getRequestURL().toString()); Tags.HTTP.METHOD.set(span, request.getMethod()); span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION); SpanLayer.asHttp(span); if (Config.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) { final Map parameterMap = request.getParameterMap(); if (parameterMap ! = null && ! parameterMap.isEmpty()) { String tagValue = CollectionUtil.toString(parameterMap); tagValue = Config.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD > 0 ? StringUtil.cut(tagValue, Config.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD) : tagValue; Tags.HTTP.PARAMS.set(span, tagValue); } } stackDepth = new StackDepth(); ContextManager.getRuntimeContext().put(CONTROLLER_METHOD_STACK_DEPTH, stackDepth); } else { AbstractSpan span = ContextManager.createLocalSpan(buildOperationName(objInst, method)); span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION); } stackDepth.increment(); } } private String buildOperationName(Object invoker, Method method) { StringBuilder operationName = new StringBuilder(invoker.getClass().getName()) .append(“.”).append(method.getName()).append(“(“); for (Class type : method.getParameterTypes()) { operationName.append(type.getName()).append(“,”); } if (method.getParameterTypes().length > 0) { operationName = operationName.deleteCharAt(operationName.length() – 1); } return operationName.append(“)”).toString(); }

afterMethod

@Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<? >[] argumentsTypes, Object ret) throws Throwable { Boolean forwardRequestFlag = (Boolean)ContextManager.getRuntimeContext().get(FORWARD_REQUEST_FLAG); /** * Spring MVC plugin do nothing if current request is forward request. * Ref: https://github.com/apache/skywalking/pull/1325 */ if (forwardRequestFlag ! = null && forwardRequestFlag) { return ret; } HttpServletRequest request = (HttpServletRequest)ContextManager.getRuntimeContext().get(REQUEST_KEY_IN_RUNTIME_CONTEXT); if (request ! = null) { StackDepth stackDepth = (StackDepth)ContextManager.getRuntimeContext().get(CONTROLLER_METHOD_STACK_DEPTH); if (stackDepth == null) { throw new IllegalMethodStackDepthException(); } else { stackDepth.decrement(); } / / get the current span AbstractSpan span = ContextManager. ActiveSpan (); if (stackDepth.depth() == 0) { HttpServletResponse response = (HttpServletResponse)ContextManager.getRuntimeContext().get(RESPONSE_KEY_IN_RUNTIME_CONTEXT); if (response == null) { throw new ServletResponseNotFoundException(); } if (IS_SERVLET_GET_STATUS_METHOD_EXIST && response.getStatus() >= 400) { span.errorOccurred(); Tags.STATUS_CODE.set(span, Integer.toString(response.getStatus())); } / / clear some context information ContextManager. GetRuntimeContext (), remove (REQUEST_KEY_IN_RUNTIME_CONTEXT); ContextManager.getRuntimeContext().remove(RESPONSE_KEY_IN_RUNTIME_CONTEXT); ContextManager.getRuntimeContext().remove(CONTROLLER_METHOD_STACK_DEPTH); } // stopSpan contextmanager.stopspan (); } return ret; }Copy the code