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]>
  • Loading branch information
mmorel-35 committed Jul 27, 2023
1 parent dc5d76a commit 4a0db07
Show file tree
Hide file tree
Showing 5 changed files with 375 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ public final class PeerServiceAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {

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

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

/**
Expand All @@ -53,27 +53,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 serverSocketDomain = attributesGetter.getServerSocketDomain(request, response);
peerService = mapToPeerService(serverSocketDomain);
Integer serverSocketPort = attributesGetter.getServerSocketPort(request, response);
// TODO wire protocol and path
peerService = mapToPeerService(null, 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,137 @@
/*
* 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.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class PeerServiceResolver {

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

public 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 ? Integer.valueOf(uri.getPort()) : null;
Set<ServiceMatcher> matchers = MAPPING.get(host);
ServiceMatcher serviceMatcher =
new ServiceMatcher(serviceName, protocol, port, uri.getPath());
if (matchers == null) {
matchers = new HashSet<>();
matchers.add(serviceMatcher);
MAPPING.put(host, matchers);
} else {
matchers.add(serviceMatcher);
}
} catch (URISyntaxException use) {
// TODO wire logging
}
});
}

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

public String resolveService(String protocol, String host, Integer port, String path) {
Set<ServiceMatcher> matchers = MAPPING.get(host);
if (matchers == null) {
return null;
} else {
return matchers.stream()
.filter(serviceMatcher -> serviceMatcher.matches(protocol, port, path))
.max(Comparator.comparingInt(ServiceMatcher::getPathLength))
.map(ServiceMatcher::getServiceName)
.orElse(null);
}
}

private static class ServiceMatcher {

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

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

public String getServiceName() {
return serviceName;
}

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
@@ -0,0 +1,141 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

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

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class PeerServiceResolver {

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

public 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 ? Integer.valueOf(uri.getPort()) : null;
Set<ServiceMatcher> matchers = MAPPING.get(host);
ServiceMatcher serviceMatcher =
new ServiceMatcher(serviceName, protocol, port, uri.getPath());
if (matchers == null) {
matchers = new HashSet<>();
matchers.add(serviceMatcher);
MAPPING.put(host, matchers);
} else {
matchers.add(serviceMatcher);
}
} catch (URISyntaxException use) {
// TODO wire logging
}
});
}

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

public String resolveService(String protocol, String host, Integer port, String path) {
Set<ServiceMatcher> matchers = MAPPING.get(host);
if (matchers == null) {
return null;
} else {
return matchers.stream()
.filter(serviceMatcher -> serviceMatcher.matches(protocol, port, path))
.max(Comparator.comparingInt(ServiceMatcher::getPathLength))
.map(ServiceMatcher::getServiceName)
.orElse(null);
}
}

private static class ServiceMatcher {

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

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

public String getServiceName() {
return serviceName;
}

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
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
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);

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

0 comments on commit 4a0db07

Please sign in to comment.