diff --git a/docs/en/guides/Java-Plugin-Development-Guide.md b/docs/en/guides/Java-Plugin-Development-Guide.md
index 3055d7291..664974f8f 100644
--- a/docs/en/guides/Java-Plugin-Development-Guide.md
+++ b/docs/en/guides/Java-Plugin-Development-Guide.md
@@ -138,7 +138,7 @@ Create ExitSpan by operation name(e.g. service name, uri) and new **ContextCarri
*/
AbstractSpan setOperationName(String endpointName);
```
-Besides set operation name, tags and logs, two attributes shoule be set, which are component and layer,
+Besides setting operation name, tags and logs, two attributes should be set, which are component and layer,
especially for EntrySpan and ExitSpan
SpanLayer is the catalog of span. Here are 5 values:
@@ -151,6 +151,36 @@ SpanLayer is the catalog of span. Here are 5 values:
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.
+### 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.
+
+#### Tag key `status_code`
+The value should be an integer. The response code of OAL entities is according to this.
+
+### 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 `receiver-trace/default/maxSlowSQLLength`.
+The threshold of slow statement is defined by following [`receiver-trace/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.
+1. Define a separated logic endpoint. Provide its own endpoint name, latency and status. Suitable for entry and local span.
+```json
+{
+ "name": "GraphQL-service",
+ "latency": 100,
+ "status": true
+}
+```
+2. Declare the current local span representing a logic endpoint.
+```json
+{
+ "logic-span": true
+}
+```
+
### 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
@@ -345,6 +375,13 @@ 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.
+### 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.
+
+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.
### Contribute plugins into Apache SkyWalking repository
We are welcome everyone to contribute plugins.
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/RequestType.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/RequestType.java
index 9c47f8ccb..3298da098 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/RequestType.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/RequestType.java
@@ -18,6 +18,16 @@
package org.apache.skywalking.oap.server.core.source;
+/**
+ * RPC request type.
+ */
public enum RequestType {
- DATABASE, HTTP, RPC, gRPC
+ DATABASE,
+ HTTP,
+ RPC,
+ gRPC,
+ /**
+ * Logic request only.
+ */
+ LOGIC
}
diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/SpanTags.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/SpanTags.java
index 4f2de970d..a605306c2 100644
--- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/SpanTags.java
+++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/SpanTags.java
@@ -18,10 +18,32 @@
package org.apache.skywalking.oap.server.receiver.trace.provider.parser;
+/**
+ * Reserved keys of the span. The backend analysis the metrics according the existed tags.
+ */
public class SpanTags {
public static final String STATUS_CODE = "status_code";
public static final String DB_STATEMENT = "db.statement";
public static final String DB_TYPE = "db.type";
+
+ /**
+ * Tag, x-le(extension logic endpoint) series tag. Value is JSON format.
+ *
+ * {
+ * "name": "GraphQL-service",
+ * "latency": 100,
+ * "status": true
+ * }
+ *
+ *
+ * Also, could use value to indicate this local span is representing a logic endpoint.
+ *
+ * {
+ * "logic-span": true
+ * }
+ *
+ */
+ public static final String LOGIC_ENDPOINT = "x-le";
}
diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListener.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListener.java
index 2aa2923f6..342d26462 100755
--- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListener.java
+++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListener.java
@@ -18,6 +18,8 @@
package org.apache.skywalking.oap.server.receiver.trace.provider.parser.listener;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
@@ -27,6 +29,7 @@ import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference;
import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanType;
import org.apache.skywalking.apm.util.StringUtil;
import org.apache.skywalking.oap.server.core.Const;
import org.apache.skywalking.oap.server.core.CoreModule;
@@ -47,6 +50,8 @@ import org.apache.skywalking.oap.server.receiver.trace.provider.DBLatencyThresho
import org.apache.skywalking.oap.server.receiver.trace.provider.TraceServiceModuleConfig;
import org.apache.skywalking.oap.server.receiver.trace.provider.parser.SpanTags;
+import static org.apache.skywalking.oap.server.receiver.trace.provider.parser.SpanTags.LOGIC_ENDPOINT;
+
/**
* MultiScopesSpanListener includes the most segment to source(s) logic.
*
@@ -54,10 +59,12 @@ import org.apache.skywalking.oap.server.receiver.trace.provider.parser.SpanTags;
*/
@Slf4j
@RequiredArgsConstructor
-public class MultiScopesAnalysisListener implements EntryAnalysisListener, ExitAnalysisListener {
+public class MultiScopesAnalysisListener implements EntryAnalysisListener, ExitAnalysisListener, LocalAnalysisListener {
private final List entrySourceBuilders = new ArrayList<>(10);
private final List exitSourceBuilders = new ArrayList<>(10);
private final List slowDatabaseAccesses = new ArrayList<>(10);
+ private final List logicEndpointBuilders = new ArrayList<>(10);
+ private final Gson gson = new Gson();
private final SourceReceiver sourceReceiver;
private final TraceServiceModuleConfig config;
private final NetworkAddressAliasCache networkAddressAliasCache;
@@ -65,7 +72,7 @@ public class MultiScopesAnalysisListener implements EntryAnalysisListener, ExitA
@Override
public boolean containsPoint(Point point) {
- return Point.Entry.equals(point) || Point.Exit.equals(point);
+ return Point.Entry.equals(point) || Point.Exit.equals(point) || Point.Local.equals(point);
}
/**
@@ -132,6 +139,8 @@ public class MultiScopesAnalysisListener implements EntryAnalysisListener, ExitA
setPublicAttrs(sourceBuilder, span);
entrySourceBuilders.add(sourceBuilder);
}
+
+ parseLogicEndpoints(span, segmentObject);
}
/**
@@ -252,6 +261,11 @@ public class MultiScopesAnalysisListener implements EntryAnalysisListener, ExitA
}
}
+ @Override
+ public void parseLocal(final SpanObject span, final SegmentObject segmentObject) {
+ parseLogicEndpoints(span, segmentObject);
+ }
+
@Override
public void build() {
entrySourceBuilders.forEach(entrySourceBuilder -> {
@@ -292,6 +306,52 @@ public class MultiScopesAnalysisListener implements EntryAnalysisListener, ExitA
});
slowDatabaseAccesses.forEach(sourceReceiver::receive);
+
+ logicEndpointBuilders.forEach(logicEndpointBuilder -> {
+ sourceReceiver.receive(logicEndpointBuilder.toEndpoint());
+ });
+ }
+
+ /**
+ * Logic endpoint could be represent through an entry span or local span. It has special meaning from API
+ * perspective. But it is an actual RPC call.
+ */
+ private void parseLogicEndpoints(final SpanObject span, final SegmentObject segmentObject) {
+ span.getTagsList().forEach(tag -> {
+ switch (tag.getKey()) {
+ case LOGIC_ENDPOINT:
+ final JsonObject tagValue = gson.fromJson(tag.getValue(), JsonObject.class);
+ final boolean isLocalSpan = SpanType.Local.equals(span.getSpanType());
+ String logicEndpointName;
+ int latency;
+ boolean status;
+ if (isLocalSpan && tagValue.has("logic-span") && tagValue.get("logic-span").getAsBoolean()) {
+ logicEndpointName = span.getOperationName();
+ latency = (int) (span.getEndTime() - span.getStartTime());
+ status = !span.getIsError();
+ } else if (tagValue.has("name") && tagValue.has("latency") && tagValue.has("status")) {
+ logicEndpointName = tagValue.get("name").getAsString();
+ latency = tagValue.get("latency").getAsInt();
+ status = tagValue.get("status").getAsBoolean();
+ } else {
+ break;
+ }
+ SourceBuilder sourceBuilder = new SourceBuilder(namingControl);
+ sourceBuilder.setTimeBucket(TimeBucket.getMinuteTimeBucket(span.getStartTime()));
+ sourceBuilder.setDestServiceName(segmentObject.getService());
+ sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance());
+ sourceBuilder.setDestEndpointName(logicEndpointName);
+ sourceBuilder.setDestNodeType(NodeType.Normal);
+ sourceBuilder.setDetectPoint(DetectPoint.SERVER);
+ sourceBuilder.setLatency(latency);
+ sourceBuilder.setStatus(status);
+ sourceBuilder.setType(RequestType.LOGIC);
+ sourceBuilder.setResponseCode(Const.NONE);
+ logicEndpointBuilders.add(sourceBuilder);
+ default:
+ break;
+ }
+ });
}
public static class Factory implements AnalysisListenerFactory {
diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MockReceiver.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MockReceiver.java
new file mode 100644
index 000000000..94437444b
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MockReceiver.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.receiver.trace.provider.parser.listener;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import org.apache.skywalking.oap.server.core.analysis.DispatcherDetectorListener;
+import org.apache.skywalking.oap.server.core.source.Source;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+
+/**
+ * Mock receiver for testing.
+ */
+public class MockReceiver implements SourceReceiver {
+ @Getter
+ private List receivedSources = new ArrayList<>();
+
+ @Override
+ public void receive(final Source source) {
+ receivedSources.add(source);
+ }
+
+ public void clear() {
+ receivedSources.clear();
+ }
+
+ @Override
+ public DispatcherDetectorListener getDispatcherDetectorListener() {
+ throw new UnsupportedOperationException("No getDispatcherDetectorListener in mock receiver.");
+ }
+}
diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListenerTest.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListenerTest.java
new file mode 100644
index 000000000..b94418914
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/MultiScopesAnalysisListenerTest.java
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.receiver.trace.provider.parser.listener;
+
+import com.google.gson.JsonObject;
+import java.util.List;
+import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
+import org.apache.skywalking.apm.network.language.agent.v3.RefType;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanType;
+import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.analysis.NodeType;
+import org.apache.skywalking.oap.server.core.analysis.manual.networkalias.NetworkAddressAlias;
+import org.apache.skywalking.oap.server.core.cache.NetworkAddressAliasCache;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
+import org.apache.skywalking.oap.server.core.source.All;
+import org.apache.skywalking.oap.server.core.source.DatabaseAccess;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.EndpointRelation;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceInstanceRelation;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.ServiceRelation;
+import org.apache.skywalking.oap.server.core.source.Source;
+import org.apache.skywalking.oap.server.receiver.trace.provider.TraceServiceModuleConfig;
+import org.apache.skywalking.oap.server.receiver.trace.provider.UninstrumentedGatewaysConfig;
+import org.apache.skywalking.oap.server.receiver.trace.provider.parser.SpanTags;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import static org.apache.skywalking.oap.server.receiver.trace.provider.parser.SpanTags.LOGIC_ENDPOINT;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * MultiScopesSpanListener includes the most segment to source(s) logic. This test covers most cases about the segment
+ * to sources translation.
+ *
+ * This test is a good way to study about how OAP analysis trace segment.
+ */
+public class MultiScopesAnalysisListenerTest {
+ @Mock
+ private static TraceServiceModuleConfig CONFIG;
+ @Mock
+ private static NetworkAddressAliasCache CACHE;
+ @Mock
+ private static NetworkAddressAliasCache CACHE2;
+ private static NamingControl NAMING_CONTROL = new NamingControl(
+ 70,
+ 100,
+ 100,
+ new EndpointNameGrouping()
+ );
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+
+ when(CACHE.get(any())).thenReturn(null);
+ final NetworkAddressAlias networkAddressAlias = new NetworkAddressAlias();
+ final String serviceId = IDManager.ServiceID.buildId("target-service", true);
+ final String instanceId = IDManager.ServiceInstanceID.buildId(serviceId, "target-instance");
+ networkAddressAlias.setRepresentServiceId(serviceId);
+ networkAddressAlias.setRepresentServiceInstanceId(instanceId);
+ when(CACHE2.get(any())).thenReturn(networkAddressAlias);
+ final UninstrumentedGatewaysConfig uninstrumentedGatewaysConfig = Mockito.mock(
+ UninstrumentedGatewaysConfig.class);
+ when(uninstrumentedGatewaysConfig.isAddressConfiguredAsGateway(any())).thenReturn(false);
+ when(CONFIG.getUninstrumentedGatewaysConfig()).thenReturn(uninstrumentedGatewaysConfig);
+ }
+
+ @Test
+ public void testContainsPoint() {
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ new MockReceiver(),
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+ Assert.assertTrue(listener.containsPoint(AnalysisListener.Point.Entry));
+ Assert.assertTrue(listener.containsPoint(AnalysisListener.Point.Local));
+ Assert.assertTrue(listener.containsPoint(AnalysisListener.Point.Exit));
+ Assert.assertFalse(listener.containsPoint(AnalysisListener.Point.First));
+ Assert.assertFalse(listener.containsPoint(AnalysisListener.Point.Segment));
+ }
+
+ /**
+ * Entry span without ref, usually the first span of the whole trace.
+ */
+ @Test
+ public void testEntrySpanWithoutRef() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/springMVC")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(true)
+ .setSpanType(SpanType.Entry)
+ .addTags(
+ KeyStringValuePair.newBuilder()
+ .setKey(SpanTags.STATUS_CODE)
+ .setValue("500")
+ .build()
+ )
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseEntry(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(7, receivedSources.size());
+ final All all = (All) receivedSources.get(0);
+ final Service service = (Service) receivedSources.get(1);
+ final ServiceInstance serviceInstance = (ServiceInstance) receivedSources.get(2);
+ final Endpoint endpoint = (Endpoint) receivedSources.get(3);
+ final ServiceRelation serviceRelation = (ServiceRelation) receivedSources.get(4);
+ final ServiceInstanceRelation serviceInstanceRelation = (ServiceInstanceRelation) receivedSources.get(5);
+ final EndpointRelation endpointRelation = (EndpointRelation) receivedSources.get(6);
+ Assert.assertEquals("mock-service", service.getName());
+ Assert.assertEquals(500, service.getResponseCode());
+ Assert.assertFalse(service.isStatus());
+ Assert.assertEquals("mock-instance", serviceInstance.getName());
+ Assert.assertEquals("/springMVC", endpoint.getName());
+ Assert.assertEquals(Const.USER_SERVICE_NAME, serviceRelation.getSourceServiceName());
+ Assert.assertEquals(service.getName(), serviceRelation.getDestServiceName());
+ Assert.assertEquals(Const.USER_INSTANCE_NAME, serviceInstanceRelation.getSourceServiceInstanceName());
+ Assert.assertEquals(serviceInstance.getName(), serviceInstanceRelation.getDestServiceInstanceName());
+ Assert.assertEquals(Const.USER_ENDPOINT_NAME, endpointRelation.getEndpoint());
+ Assert.assertEquals(endpoint.getName(), endpointRelation.getChildEndpoint());
+ }
+
+ /**
+ * Entry span with ref, meaning the downstream has been instrumented.
+ */
+ @Test
+ public void testEntrySpanRef() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/springMVC")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(true)
+ .setSpanType(SpanType.Entry)
+ .setSpanLayer(SpanLayer.RPCFramework)
+ .addRefs(
+ SegmentReference.newBuilder()
+ .setRefType(RefType.CrossProcess)
+ .setParentService("downstream-service")
+ .setParentServiceInstance("downstream-instance")
+ .setParentEndpoint("downstream-endpoint")
+ .setNetworkAddressUsedAtPeer("127.0.0.1")
+ .build()
+ )
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseEntry(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(7, receivedSources.size());
+ final All all = (All) receivedSources.get(0);
+ final Service service = (Service) receivedSources.get(1);
+ final ServiceInstance serviceInstance = (ServiceInstance) receivedSources.get(2);
+ final Endpoint endpoint = (Endpoint) receivedSources.get(3);
+ final ServiceRelation serviceRelation = (ServiceRelation) receivedSources.get(4);
+ final ServiceInstanceRelation serviceInstanceRelation = (ServiceInstanceRelation) receivedSources.get(5);
+ final EndpointRelation endpointRelation = (EndpointRelation) receivedSources.get(6);
+ Assert.assertEquals("mock-service", service.getName());
+ Assert.assertEquals("mock-instance", serviceInstance.getName());
+ Assert.assertEquals("/springMVC", endpoint.getName());
+ Assert.assertEquals("downstream-service", serviceRelation.getSourceServiceName());
+ Assert.assertEquals(service.getName(), serviceRelation.getDestServiceName());
+ Assert.assertEquals("downstream-instance", serviceInstanceRelation.getSourceServiceInstanceName());
+ Assert.assertEquals(serviceInstance.getName(), serviceInstanceRelation.getDestServiceInstanceName());
+ Assert.assertEquals("downstream-endpoint", endpointRelation.getEndpoint());
+ Assert.assertEquals(endpoint.getName(), endpointRelation.getChildEndpoint());
+ }
+
+ /**
+ * Entry span with ref, but as a MQ server, or uninstrumented server.
+ */
+ @Test
+ public void testEntrySpanMQRef() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/springMVC")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(true)
+ .setSpanType(SpanType.Entry)
+ .setSpanLayer(SpanLayer.MQ)
+ .addRefs(
+ SegmentReference.newBuilder()
+ .setRefType(RefType.CrossProcess)
+ .setParentService("downstream-service")
+ .setParentServiceInstance("downstream-instance")
+ .setParentEndpoint("downstream-endpoint")
+ .setNetworkAddressUsedAtPeer("127.0.0.1")
+ .build()
+ )
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseEntry(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(7, receivedSources.size());
+ final All all = (All) receivedSources.get(0);
+ final Service service = (Service) receivedSources.get(1);
+ final ServiceInstance serviceInstance = (ServiceInstance) receivedSources.get(2);
+ final Endpoint endpoint = (Endpoint) receivedSources.get(3);
+ final ServiceRelation serviceRelation = (ServiceRelation) receivedSources.get(4);
+ final ServiceInstanceRelation serviceInstanceRelation = (ServiceInstanceRelation) receivedSources.get(5);
+ final EndpointRelation endpointRelation = (EndpointRelation) receivedSources.get(6);
+ Assert.assertEquals("mock-service", service.getName());
+ Assert.assertEquals("mock-instance", serviceInstance.getName());
+ Assert.assertEquals("/springMVC", endpoint.getName());
+ Assert.assertEquals("127.0.0.1", serviceRelation.getSourceServiceName());
+ Assert.assertEquals(service.getName(), serviceRelation.getDestServiceName());
+ Assert.assertEquals("127.0.0.1", serviceInstanceRelation.getSourceServiceInstanceName());
+ Assert.assertEquals(serviceInstance.getName(), serviceInstanceRelation.getDestServiceInstanceName());
+ Assert.assertEquals("downstream-endpoint", endpointRelation.getEndpoint());
+ Assert.assertEquals(endpoint.getName(), endpointRelation.getChildEndpoint());
+ }
+
+ /**
+ * Local span analysis is triggered with logic span tag.
+ */
+ @Test
+ public void testParseLocalLogicSpan() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ final JsonObject logicSpanTagValue = new JsonObject();
+ logicSpanTagValue.addProperty("logic-span", true);
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/logic-call")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(false)
+ .setSpanType(SpanType.Local)
+ .addTags(KeyStringValuePair.newBuilder()
+ .setKey(LOGIC_ENDPOINT)
+ .setValue(logicSpanTagValue.toString())
+ .build())
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseLocal(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(1, receivedSources.size());
+ final Endpoint source = (Endpoint) receivedSources.get(0);
+ Assert.assertEquals("/logic-call", source.getName());
+
+ mockReceiver.clear();
+ }
+
+ /**
+ * Local span analysis is triggered with extension logic service tags.
+ */
+ @Test
+ public void testParseSpanWithLogicEndpointTag() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ final JsonObject logicSpanTagValue = new JsonObject();
+ logicSpanTagValue.addProperty("name", "/GraphQL-service");
+ logicSpanTagValue.addProperty("latency", 100);
+ logicSpanTagValue.addProperty("status", false);
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/logic-call")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(false)
+ .setSpanType(SpanType.Local)
+ .addTags(KeyStringValuePair.newBuilder()
+ .setKey(LOGIC_ENDPOINT)
+ .setValue(logicSpanTagValue.toString())
+ .build())
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseLocal(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(1, receivedSources.size());
+ final Endpoint source = (Endpoint) receivedSources.get(0);
+ Assert.assertEquals("/GraphQL-service", source.getName());
+
+ mockReceiver.clear();
+ }
+
+ /**
+ * Exit span, represent calling a 3rd party system, when the alias has not been setup, including access database.
+ */
+ @Test
+ public void testExitSpanWithoutAlias() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/springMVC")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(true)
+ .setSpanType(SpanType.Exit)
+ .setSpanLayer(SpanLayer.Database)
+ .setPeer("127.0.0.1:8080")
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseExit(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(4, receivedSources.size());
+ final ServiceRelation serviceRelation = (ServiceRelation) receivedSources.get(0);
+ final ServiceInstanceRelation serviceInstanceRelation = (ServiceInstanceRelation) receivedSources.get(1);
+ final ServiceMeta serviceMeta = (ServiceMeta) receivedSources.get(2);
+ final DatabaseAccess databaseAccess = (DatabaseAccess) receivedSources.get(3);
+ Assert.assertEquals("mock-service", serviceRelation.getSourceServiceName());
+ Assert.assertEquals("127.0.0.1:8080", serviceRelation.getDestServiceName());
+ Assert.assertEquals("mock-instance", serviceInstanceRelation.getSourceServiceInstanceName());
+ Assert.assertEquals("127.0.0.1:8080", serviceInstanceRelation.getDestServiceInstanceName());
+ Assert.assertEquals("127.0.0.1:8080", serviceMeta.getName());
+ Assert.assertEquals(NodeType.Database, serviceMeta.getNodeType());
+ Assert.assertEquals("127.0.0.1:8080", databaseAccess.getName());
+ }
+
+ /**
+ * Exit span, represent calling a 3rd party system, when the alias has been setup.
+ */
+ @Test
+ public void testExitSpanWithAlias() {
+ final MockReceiver mockReceiver = new MockReceiver();
+ MultiScopesAnalysisListener listener = new MultiScopesAnalysisListener(
+ mockReceiver,
+ CONFIG,
+ CACHE2,
+ NAMING_CONTROL
+ );
+
+ final long startTime = System.currentTimeMillis();
+ SpanObject spanObject = SpanObject.newBuilder()
+ .setOperationName("/springMVC")
+ .setStartTime(startTime)
+ .setEndTime(startTime + 1000L)
+ .setIsError(true)
+ .setSpanType(SpanType.Exit)
+ .setSpanLayer(SpanLayer.MQ)
+ .setPeer("127.0.0.1:8080")
+ .build();
+ final SegmentObject segment = SegmentObject.newBuilder()
+ .setService("mock-service")
+ .setServiceInstance("mock-instance")
+ .addSpans(spanObject)
+ .build();
+ listener.parseExit(spanObject, segment);
+ listener.build();
+
+ final List receivedSources = mockReceiver.getReceivedSources();
+ Assert.assertEquals(2, receivedSources.size());
+ final ServiceRelation serviceRelation = (ServiceRelation) receivedSources.get(0);
+ final ServiceInstanceRelation serviceInstanceRelation = (ServiceInstanceRelation) receivedSources.get(1);
+ Assert.assertEquals("mock-service", serviceRelation.getSourceServiceName());
+ Assert.assertEquals("target-service", serviceRelation.getDestServiceName());
+ Assert.assertEquals("mock-instance", serviceInstanceRelation.getSourceServiceInstanceName());
+ Assert.assertEquals("target-instance", serviceInstanceRelation.getDestServiceInstanceName());
+ mockReceiver.clear();
+ }
+}