org.glowroot.agent.live.JvmTool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glowroot-agent-it-harness Show documentation
Show all versions of glowroot-agent-it-harness Show documentation
Glowroot Agent Integration Test Harness
/*
* Copyright 2017-2018 the original author or authors.
*
* Licensed 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.glowroot.agent.live;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.util.List;
import javax.tools.ToolProvider;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Joiner;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Splitter;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.StandardSystemProperty;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Strings;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Lists;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.io.ByteStreams;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.io.Closer;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.LoggerFactory;
import org.glowroot.agent.util.JavaVersion;
import org.glowroot.agent.shaded.org.glowroot.common.live.LiveJvmService.UnavailableDueToDockerAlpinePidOneException;
import org.glowroot.agent.shaded.org.glowroot.common.live.LiveJvmService.UnavailableDueToRunningInJreException;
import static org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Preconditions.checkNotNull;
class JvmTool {
private static final Logger logger = LoggerFactory.getLogger(JvmTool.class);
private static final int UNAVAILABLE_DUE_TO_RUNNING_IN_JRE_STATUS = 12345;
private static final int UNAVAILABLE_PROBABLY_DUE_TO_DOCKER_PID_ONE_STATUS = 23456;
private JvmTool() {}
public static void main(String[] args) throws Exception {
long pid = Long.parseLong(args[0]);
String methodName = args[1];
try {
run(pid, methodName, new SystemOutProcessor());
} catch (UnavailableDueToRunningInJreException e) {
System.exit(UNAVAILABLE_DUE_TO_RUNNING_IN_JRE_STATUS);
} catch (UnavailableDueToDockerAlpinePidOneException e) {
System.exit(UNAVAILABLE_PROBABLY_DUE_TO_DOCKER_PID_ONE_STATUS);
}
}
static T run(long pid, String methodName, InputStreamProcessor processor,
boolean allowAttachSelf, @Nullable File glowrootJarFile) throws Exception {
if (allowAttachSelf) {
return run(pid, methodName, processor);
} else {
return runExternalAttach(pid, methodName, processor, glowrootJarFile);
}
}
private static T run(long pid, String methodName, InputStreamProcessor processor)
throws Exception {
ClassLoader systemToolClassLoader;
if (JavaVersion.isGreaterThanOrEqualToJava9()) {
systemToolClassLoader = LiveJvmServiceImpl.class.getClassLoader();
} else {
systemToolClassLoader = ToolProvider.getSystemToolClassLoader();
}
Class> vmClass;
try {
vmClass = Class.forName("com.sun.tools.attach.VirtualMachine", true,
systemToolClassLoader);
} catch (ClassNotFoundException e) {
throw new UnavailableDueToRunningInJreException();
}
Method attachMethod = vmClass.getMethod("attach", String.class);
Method detachMethod = vmClass.getMethod("detach");
Class> hotSpotVmClass = Class.forName("sun.tools.attach.HotSpotVirtualMachine", true,
systemToolClassLoader);
Method method = hotSpotVmClass.getMethod(methodName, Object[].class);
Object vm;
try {
vm = attachMethod.invoke(null, Long.toString(pid));
} catch (Exception e) {
Throwable cause = e.getCause();
if (cause != null
&& cause.getClass().getName()
.equals("com.sun.tools.attach.AttachNotSupportedException")
&& "Unable to get pid of LinuxThreads manager thread".equals(cause.getMessage())
&& new File("/etc/alpine-release").exists()
&& pid == 1) {
throw new UnavailableDueToDockerAlpinePidOneException();
}
throw e;
}
try {
InputStream in = (InputStream) method.invoke(vm, (Object) new Object[0]);
checkNotNull(in);
return processAndClose(in, processor);
} finally {
detachMethod.invoke(vm);
}
}
private static T runExternalAttach(long pid, String methodName,
InputStreamProcessor processor, @Nullable File glowrootJarFile) throws Exception {
List command = buildCommand(pid, methodName, glowrootJarFile);
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
Closer closer = Closer.create();
ErrorStreamReader errorStreamReader;
T result = null;
Exception processingException = null;
try {
InputStream in = closer.register(process.getInputStream());
InputStream err = closer.register(process.getErrorStream());
errorStreamReader = new ErrorStreamReader(err);
Thread errorStreamReaderThread = new Thread(errorStreamReader);
errorStreamReaderThread.setName("Glowroot-JVM-Tool-Error-Stream-Reader");
errorStreamReaderThread.setDaemon(true);
errorStreamReaderThread.start();
try {
result = processAndClose(in, processor);
} catch (Exception e) {
processingException = e;
} catch (Throwable t) {
processingException = new RuntimeException(t);
}
errorStreamReaderThread.join();
} catch (Throwable t) {
throw closer.rethrow(t);
} finally {
closer.close();
}
int status = process.waitFor();
if (status == UNAVAILABLE_DUE_TO_RUNNING_IN_JRE_STATUS) {
throw new UnavailableDueToRunningInJreException();
} else if (status == UNAVAILABLE_PROBABLY_DUE_TO_DOCKER_PID_ONE_STATUS) {
throw new UnavailableDueToDockerAlpinePidOneException();
} else if (status != 0) {
logger.error("error occurred while trying to run jvm tool:\n{}\n{}",
Joiner.on(' ').join(command), errorStreamReader.getOutput().trim());
throw new IllegalStateException("Error occurred while trying to run jvm tool");
}
if (result == null) {
throw checkNotNull(processingException);
}
return result;
}
private static List buildCommand(long pid, String methodName,
@Nullable File glowrootJarFile) {
List command = Lists.newArrayList();
String javaExecutable = StandardSystemProperty.JAVA_HOME.value() + File.separator + "bin"
+ File.separator + "java";
command.add(javaExecutable);
command.add("-classpath");
command.add(Joiner.on(File.pathSeparatorChar).join(buildClasspath(glowrootJarFile)));
command.add(JvmTool.class.getName());
command.add(Long.toString(pid));
command.add(methodName);
return command;
}
private static List buildClasspath(@Nullable File glowrootJarFile) {
if (glowrootJarFile == null || !isShaded()) {
// this is just to support testing
List classpath = Lists.newArrayList();
if (glowrootJarFile != null) {
classpath.add(glowrootJarFile.getAbsolutePath());
}
classpath.addAll(splitClasspath(StandardSystemProperty.JAVA_CLASS_PATH.value()));
for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
if (jvmArg.startsWith("-Xbootclasspath/a:")) {
classpath.addAll(
splitClasspath(jvmArg.substring("-Xbootclasspath/a:".length())));
break;
}
}
return classpath;
} else {
return ImmutableList.of(glowrootJarFile.getAbsolutePath());
}
}
private static List splitClasspath(@Nullable String classpath) {
if (Strings.isNullOrEmpty(classpath)) {
return ImmutableList.of();
}
return Splitter.on(File.pathSeparatorChar).splitToList(classpath);
}
private static T processAndClose(InputStream in, InputStreamProcessor processor)
throws IOException {
Closer closer = Closer.create();
try {
closer.register(in);
return processor.process(in);
} catch (Throwable t) {
throw closer.rethrow(t);
} finally {
closer.close();
}
}
private static boolean isShaded() {
try {
Class.forName("org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger");
return true;
} catch (ClassNotFoundException e) {
// log exception at trace level
logger.trace(e.getMessage(), e);
return false;
}
}
interface InputStreamProcessor {
// the input stream is not buffered, so should be buffered somehow
// the input stream will be closed by the caller
T process(InputStream in) throws IOException;
}
private static class SystemOutProcessor implements InputStreamProcessor*@Nullable*/ Void> {
@Override
public @Nullable Void process(InputStream in) throws IOException {
ByteStreams.copy(in, System.out);
return null;
}
}
private static class ErrorStreamReader implements Runnable {
private final InputStream errorStream;
private final ByteArrayOutputStream baos;
private ErrorStreamReader(InputStream errorStream) {
this.errorStream = errorStream;
this.baos = new ByteArrayOutputStream();
}
@Override
public void run() {
try {
ByteStreams.copy(errorStream, baos);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
private String getOutput() {
return new String(baos.toByteArray());
}
}
}