Do not trace openConnection for Datadog ClassLoader

This commit is contained in:
Nikolay Martynov 2019-08-14 16:38:53 -04:00
parent 7614ec86cb
commit b0ff861392
3 changed files with 120 additions and 110 deletions

View File

@ -1,20 +1,8 @@
package datadog.trace.bootstrap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.NoSuchFileException;
import java.security.Permission;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import lombok.extern.slf4j.Slf4j;
/**
@ -116,99 +104,4 @@ public class DatadogClassLoader extends URLClassLoader {
throw new ClassNotFoundException(name);
}
}
protected static class InternalJarURLHandler extends URLStreamHandler {
private final Map<String, byte[]> filenameToBytes = new HashMap<>();
public InternalJarURLHandler(
final String internalJarFileName, final ClassLoader classloaderForJarResource) {
// "/" is used as the default url of the jar
// This is called by the SecureClassLoader trying to obtain permissions
filenameToBytes.put("/", new byte[] {});
final InputStream jarStream =
classloaderForJarResource.getResourceAsStream(internalJarFileName);
if (jarStream != null) {
try (final JarInputStream inputStream = new JarInputStream(jarStream)) {
JarEntry entry = inputStream.getNextJarEntry();
while (entry != null) {
filenameToBytes.put("/" + entry.getName(), getBytes(inputStream));
entry = inputStream.getNextJarEntry();
}
} catch (final IOException e) {
log.error("Unable to read internal jar", e);
}
} else {
log.error("Internal jar not found");
}
}
@Override
protected URLConnection openConnection(final URL url) throws IOException {
final byte[] bytes = filenameToBytes.get(url.getFile());
if (bytes == null) {
throw new NoSuchFileException(url.getFile(), null, url.getFile() + " not in internal jar");
}
return new InternalJarURLConnection(url, bytes);
}
}
protected static class InternalJarURLConnection extends URLConnection {
private final byte[] bytes;
private InternalJarURLConnection(final URL url, final byte[] bytes) {
super(url);
this.bytes = bytes;
}
@Override
public void connect() throws IOException {
connected = true;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
@Override
public Permission getPermission() {
// No permissions needed because all classes are in memory
return null;
}
}
/**
* Standard "copy InputStream to byte[]" implementation using a ByteArrayOutputStream
*
* <p>IOUtils.toByteArray() or Java 9's InputStream.readAllBytes() could be replacements if they
* were available
*
* <p>This can be optimized using the JarEntry's size(), but its not always available
*
* @param inputStream
* @return a byte[] from the inputstream
* @throws IOException
*/
private static byte[] getBytes(final InputStream inputStream) throws IOException {
final byte[] buffer = new byte[4096];
int bytesRead = inputStream.read(buffer, 0, buffer.length);
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while (bytesRead != -1) {
outputStream.write(buffer, 0, bytesRead);
bytesRead = inputStream.read(buffer, 0, buffer.length);
}
return outputStream.toByteArray();
}
}
}

View File

@ -0,0 +1,111 @@
package datadog.trace.bootstrap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.NoSuchFileException;
import java.security.Permission;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class InternalJarURLHandler extends URLStreamHandler {
private final Map<String, byte[]> filenameToBytes = new HashMap<>();
InternalJarURLHandler(
final String internalJarFileName, final ClassLoader classloaderForJarResource) {
// "/" is used as the default url of the jar
// This is called by the SecureClassLoader trying to obtain permissions
filenameToBytes.put("/", new byte[] {});
final InputStream jarStream =
classloaderForJarResource.getResourceAsStream(internalJarFileName);
if (jarStream != null) {
try (final JarInputStream inputStream = new JarInputStream(jarStream)) {
JarEntry entry = inputStream.getNextJarEntry();
while (entry != null) {
filenameToBytes.put("/" + entry.getName(), getBytes(inputStream));
entry = inputStream.getNextJarEntry();
}
} catch (final IOException e) {
log.error("Unable to read internal jar", e);
}
} else {
log.error("Internal jar not found");
}
}
@Override
protected URLConnection openConnection(final URL url) throws IOException {
final byte[] bytes = filenameToBytes.get(url.getFile());
if (bytes == null) {
throw new NoSuchFileException(url.getFile(), null, url.getFile() + " not in internal jar");
}
return new InternalJarURLConnection(url, bytes);
}
/**
* Standard "copy InputStream to byte[]" implementation using a ByteArrayOutputStream
*
* <p>IOUtils.toByteArray() or Java 9's InputStream.readAllBytes() could be replacements if they
* were available
*
* <p>This can be optimized using the JarEntry's size(), but its not always available
*
* @param inputStream stream to read
* @return a byte[] from the inputstream
*/
private static byte[] getBytes(final InputStream inputStream) throws IOException {
final byte[] buffer = new byte[4096];
int bytesRead = inputStream.read(buffer, 0, buffer.length);
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while (bytesRead != -1) {
outputStream.write(buffer, 0, bytesRead);
bytesRead = inputStream.read(buffer, 0, buffer.length);
}
return outputStream.toByteArray();
}
}
private static class InternalJarURLConnection extends URLConnection {
private final byte[] bytes;
private InternalJarURLConnection(final URL url, final byte[] bytes) {
super(url);
this.bytes = bytes;
}
@Override
public void connect() throws IOException {
connected = true;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
@Override
public Permission getPermission() {
// No permissions needed because all classes are in memory
return null;
}
}
}

View File

@ -12,11 +12,13 @@ import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.Config;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import datadog.trace.bootstrap.InternalJarURLHandler;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.Collections;
import java.util.Map;
import net.bytebuddy.asm.Advice;
@ -49,10 +51,14 @@ public class UrlInstrumentation extends Instrumenter.Default {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void errorSpan(
@Advice.This final URL url, @Advice.Thrown final Throwable throwable) {
@Advice.This final URL url,
@Advice.Thrown final Throwable throwable,
@Advice.FieldValue("handler") final URLStreamHandler handler) {
if (throwable != null) {
final boolean isTraceRequest = Thread.currentThread().getName().equals("dd-agent-writer");
if (isTraceRequest) {
// Various agent components end up calling `openConnection` indirectly
// when loading classes. Avoid tracing these calls.
final boolean disableTracing = handler instanceof InternalJarURLHandler;
if (disableTracing) {
return;
}