Skip to content

Commit

Permalink
Peer Service Mapping
Browse files Browse the repository at this point in the history
Signed-off-by: Matthieu MOREL <[email protected]>
Co-Authored-By: jason plumb <[email protected]>
  • Loading branch information
mmorel-35 and breedx-splk committed Jul 29, 2023
1 parent dc5d76a commit d9faa5e
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.network.ServerAttributesGetter;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.Map;
import javax.annotation.Nullable;
Expand All @@ -21,23 +20,23 @@
public final class PeerServiceAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {

private final ServerAttributesGetter<REQUEST, RESPONSE> attributesGetter;
private final Map<String, String> peerServiceMapping;
private final NetClientAttributesGetter<REQUEST, RESPONSE> attributesGetter;
private final PeerServiceResolver peerServiceResolver;

// visible for tests
PeerServiceAttributesExtractor(
ServerAttributesGetter<REQUEST, RESPONSE> attributesGetter,
NetClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
Map<String, String> peerServiceMapping) {
this.attributesGetter = attributesGetter;
this.peerServiceMapping = peerServiceMapping;
this.peerServiceResolver = new PeerServiceResolver(peerServiceMapping);
}

/**
* Returns a new {@link PeerServiceAttributesExtractor} that will use the passed {@code
* netAttributesExtractor} instance to determine the value of the {@code peer.service} attribute.
*/
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
ServerAttributesGetter<REQUEST, RESPONSE> attributesGetter,
NetClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
Map<String, String> peerServiceMapping) {
return new PeerServiceAttributesExtractor<>(attributesGetter, peerServiceMapping);
}
Expand All @@ -53,27 +52,35 @@ public void onEnd(
@Nullable RESPONSE response,
@Nullable Throwable error) {

if (peerServiceMapping.isEmpty()) {
if (peerServiceResolver.isEmpty()) {
// optimization for common case
return;
}

String serverAddress = attributesGetter.getServerAddress(request);
String peerService = mapToPeerService(serverAddress);
Integer serverPort = attributesGetter.getServerPort(request);
// TODO wire protocol and path
String peerService = mapToPeerService(null, serverAddress, serverPort, null);
if (peerService == null) {
String sockFamily = attributesGetter.getSockFamily(request, response);
String serverSocketDomain = attributesGetter.getServerSocketDomain(request, response);
peerService = mapToPeerService(serverSocketDomain);
Integer serverSocketPort = attributesGetter.getServerSocketPort(request, response);
peerService = mapToPeerService(sockFamily, serverSocketDomain, serverSocketPort, null);
}
if (peerService != null) {
attributes.put(SemanticAttributes.PEER_SERVICE, peerService);
}
}

@Nullable
private String mapToPeerService(@Nullable String endpoint) {
if (endpoint == null) {
private String mapToPeerService(
@Nullable String protocol,
@Nullable String host,
@Nullable Integer port,
@Nullable String path) {
if (host == null) {
return null;
}
return peerServiceMapping.get(endpoint);
return peerServiceResolver.resolveService(protocol, host, port, path);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.net;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

class PeerServiceResolver {

private static final Map<String, Map<ServiceMatcher, String>> MAPPING = new ConcurrentHashMap<>();

PeerServiceResolver(Map<String, String> peerServiceMapping) {
peerServiceMapping.forEach(
(key, serviceName) -> {
try {
String protocol = null;
URI uri;
if (key.contains("://")) {
uri = new URI(key);
protocol = uri.getScheme();
} else {
uri = new URI("http://" + key);
}
String host = uri.getHost();
Integer port = uri.getPort() == -1 ? null : uri.getPort();
Map<ServiceMatcher, String> matchers =
MAPPING.computeIfAbsent(host, x -> new ConcurrentHashMap<>());
matchers.putIfAbsent(new ServiceMatcher(protocol, port, uri.getPath()), serviceName);
} catch (URISyntaxException use) {
// TODO wire logging
}
});
}

boolean isEmpty() {
return MAPPING.isEmpty();
}

String resolveService(String protocol, String host, Integer port, String path) {
Map<ServiceMatcher, String> matchers = MAPPING.get(host);
if (matchers == null) {
return null;
}
return matchers.entrySet().stream()
.filter(entry -> entry.getKey().matches(protocol, port, path))
.max(Comparator.comparingInt(entry -> entry.getKey().getPathLength()))
.map(Map.Entry::getValue)
.orElse(null);
}

private static class ServiceMatcher {

private final String protocol;
private final Integer port;
private final String path;

public ServiceMatcher(String protocol, Integer port, String path) {
this.protocol = protocol;
this.port = port;
this.path = path;
}

public int getPathLength() {
if (this.path == null) {
return 0;
}
return this.path.length();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ServiceMatcher)) {
return false;
}
ServiceMatcher that = (ServiceMatcher) o;
return Objects.equals(protocol, that.protocol)
&& Objects.equals(port, that.port)
&& Objects.equals(path, that.path);
}

@Override
public int hashCode() {
return Objects.hash(protocol, port, path);
}

public boolean matches(String protocol, Integer port, String path) {
if (this.protocol != null) {
if (!this.protocol.equals(protocol)) {
return false;
}
}
if (this.port != null) {
if (!this.port.equals(port)) {
return false;
}
}
if (this.path != null) {
if (path == null) {
return false;
}
if (!path.startsWith(this.path)) {
return false;
}
if (port != null) {
return port.equals(this.port);
}
}
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.network.ServerAttributesGetter;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -28,7 +27,7 @@

@ExtendWith(MockitoExtension.class)
class PeerServiceAttributesExtractorTest {
@Mock ServerAttributesGetter<String, String> netAttributesExtractor;
@Mock NetClientAttributesGetter<String, String> netAttributesExtractor;

@Test
void shouldNotSetAnyValueIfNetExtractorReturnsNulls() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.net;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;

class PeerServiceResolverTest {

@Test
public void test() {
Map<String, String> peerServiceMapping = new HashMap<>();
peerServiceMapping.put("tcp://example.com:8080", "myTcpService");
peerServiceMapping.put("example.com:8080", "myService");
peerServiceMapping.put("example.com", "myServiceBase");
peerServiceMapping.put("1.2.3.4", "someOtherService");
peerServiceMapping.put("1.2.3.4:8080/api", "someOtherService8080");
peerServiceMapping.put("1.2.3.4/api", "someOtherServiceAPI");

PeerServiceResolver peerServiceResolver = new PeerServiceResolver(peerServiceMapping);

assertEquals(
"myServiceBase", peerServiceResolver.resolveService(null, "example.com", null, null));
assertEquals("myService", peerServiceResolver.resolveService("http", "example.com", 8080, "/"));
assertEquals(
"myTcpService", peerServiceResolver.resolveService("tcp", "example.com", 8080, "/api"));
assertEquals(
"someOtherService8080", peerServiceResolver.resolveService(null, "1.2.3.4", 8080, "/api"));
assertEquals(
"someOtherService", peerServiceResolver.resolveService(null, "1.2.3.4", 9000, "/api"));
assertEquals(
"someOtherService", peerServiceResolver.resolveService(null, "1.2.3.4", 8080, null));
assertEquals(
"someOtherServiceAPI", peerServiceResolver.resolveService(null, "1.2.3.4", null, "/api"));
}
}

0 comments on commit d9faa5e

Please sign in to comment.