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(); + } +}