/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.java.btrace.client;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import jline.console.ConsoleReader;
import jline.console.completer.Completer;
import jline.console.completer.FileNameCompleter;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import net.java.btrace.api.core.BTraceLogger;
import net.java.btrace.api.extensions.ExtensionsRepository;
import net.java.btrace.api.extensions.ExtensionsRepositoryFactory;
import net.java.btrace.jps.JpsProxy;
import net.java.btrace.jps.JpsVM;
/**
*
* @author Jaroslav Bachorik <jaroslav.bachorik at oracle.com>
*/
public class Main1 {
public static final int BTRACE_DEFAULT_PORT = 2020;
final private static String bootstrap;
final private static String agentpath;
final private static String defaultExtPath;
static {
bootstrap = getJarFor("net/java/btrace/api/core/BTraceRuntime.class");
agentpath = getJarFor("net/java/btrace/agent/Main.class");
defaultExtPath = getJarFor("net/java/btrace/ext/Printer.class") + File.pathSeparator +
getJarFor("net/java/btrace/ext/profiling/Profiler.class") + File.pathSeparator +
getJarFor("net/java/btrace/ext/aggregations/Aggregations.class") + File.pathSeparator +
getJarFor("net/java/btrace/ext/collections/Collections.class") + File.pathSeparator +
getJarFor("net/java/btrace/ext/export/Export.class") + File.separator +
getJarFor("net/java/btrace/ext/sys/Memory.class").replace("null" + File.pathSeparator, "") + File.pathSeparator;
}
public static void main(String ... args) throws IOException {
OptionParser parser = new OptionParser();
parser.accepts("p").withRequiredArg().ofType(int.class).describedAs("Process PID to attach to");
parser.accepts("P").withRequiredArg().ofType(int.class).describedAs("BTrace port").defaultsTo(BTRACE_DEFAULT_PORT);
parser.accepts("s").withRequiredArg().ofType(String.class).describedAs("BTrace script to deploy");
parser.accepts("x").withOptionalArg().ofType(String.class).describedAs("BTrace extensions location descriptor").defaultsTo("");
parser.accepts("unsafe");
parser.accepts("debug");
parser.accepts("dumpClasses").withOptionalArg().ofType(String.class).describedAs("Debug dump of the modified classes").defaultsTo(System.getProperty("java.io.tmpdir"));
run(parser.parse(args));
}
private static void run(OptionSet optionSet) throws IOException {
final ConsoleReader cr = new ConsoleReader();
Integer pid = (Integer)optionSet.valueOf("p");
String trace = (String)optionSet.valueOf("s");
String extensions = (String)optionSet.valueOf("x");
while (pid == null || trace == null) {
pid = readPid(pid, cr);
trace = readTrace(trace, cr);
}
ExtensionsRepository extRepository = getRepository(defaultExtPath.isEmpty() ? extensions : defaultExtPath + File.pathSeparator + extensions);
Compiler compiler = new Compiler(optionSet.has("unsafe"), extRepository);
byte[] code = compiler.compile(trace, ".", null);
if (code == null) {
errorExit("BTrace compilation failed", 1);
}
final Client client = Client.forPID(Integer.valueOf(pid));
if (agentpath != null) {
client.setAgentPath(agentpath);
}
if (bootstrap != null) {
client.setBootstrapPath(bootstrap);
}
final ClientWriter cw = new ClientWriter(System.out);
registerExitHook(client);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
cr.println("*** BTrace CLI Ready ***\nPress Ctrl-o to access the menu.");
cr.flush();
int ch = cr.readCharacter();
if (ch == 15) { // CTRL-o
try {
cw.park();
cr.println("Please enter your option:");
cr.println("\t1. exit\n\t2. send an event\n\t3. send a named event\n\n\t0. continue");
cr.flush();
int option = cr.readCharacter('1', '2', '3', '0');
if (option == '1') {
System.exit(0);
} else if (option == '2') {
BTraceLogger.debugPrint("sending event command");
sendEvent(client);
} else if (option == '3') {
cr.setPrompt("Please enter the event name: ");
String name = cr.readLine();
if (name != null) {
BTraceLogger.debugPrint("sending event command");
sendEvent(client, name);
}
} else if (option == '0') {
BTraceLogger.debugPrint("continuing");
} else {
cr.println("invalid option!");
}
} finally {
cw.unpark();
}
}
}
} catch (IOException e) {
BTraceLogger.debugPrint(e);
} catch (InterruptedException e) {
BTraceLogger.debugPrint(e);
}
System.exit(0);
}
});
t.setDaemon(true);
t.start();
BTraceLogger.debugPrint("attaching to process");
client.
setPrintWriter(cw).
setExtRepository(extRepository).
setUnsafe(optionSet.has("unsafe")).
setDumpClasses(optionSet.has("dumpClasses")).
setDumpDir((String)optionSet.valueOf("dumpClasses")).
setPort((Integer)optionSet.valueOf("P"))
.attach();
BTraceLogger.debugPrint("submitting the BTrace program");
client.submit(trace, code, new String[0]);
}
private static Integer readPid(Integer pid, final ConsoleReader cr) throws IOException, NumberFormatException {
while (pid == null) {
cr.println("Select a process to attach to:");
int counter = 0;
List<JpsVM> vms = new ArrayList<JpsVM>();
for(JpsVM vm : JpsProxy.getRunningVMs()) {
vms.add(vm);
cr.println((counter++ + 1) + ") " + vm.getPid() + "\t" + vm.getMainClass());
}
cr.setPrompt("Please, make a choice:");
cr.flush();
int ch = cr.readCharacter();
int option = Integer.parseInt(String.valueOf((char)ch));
if (option > 0 && option <= counter) {
pid = vms.get(option - 1).getPid();
cr.println("Attaching to: " + vms.get(option - 1).getMainClass() + "(PID=" + pid + ")");
cr.flush();
}
}
return pid;
}
private static String readTrace(String trace, ConsoleReader cr) throws IOException {
Completer c = new FileNameCompleter();
try {
cr.addCompleter(c);
cr.setPrompt("Select the script to deploy: ");
while (trace == null || trace.isEmpty()) {
cr.flush();
trace = cr.readLine().trim();
}
return trace;
} finally {
cr.removeCompleter(c);
}
}
private static String getJarFor(String clz) {
ClassLoader cl = Main1.class.getClassLoader();
URL resURL = cl.getResource(clz);
if (resURL == null) return null;
String jarPath = resURL.toString().replace("jar:file:", "");
jarPath = jarPath.substring(0, jarPath.indexOf(".jar!") + 4);
return jarPath;
}
private static ExtensionsRepository getRepository(String extPath) {
BTraceLogger.debugPrint("getting repository for " + extPath);
return ExtensionsRepositoryFactory.composite(
ExtensionsRepository.Location.BOTH,
ExtensionsRepositoryFactory.builtin(ExtensionsRepository.Location.BOTH),
ExtensionsRepositoryFactory.fixed(ExtensionsRepository.Location.BOTH, extPath)
);
}
private static void errorExit(String msg, int code) {
System.err.println(msg);
exitCode.set(code);
System.exit(code);
}
private static AtomicInteger exitCode = new AtomicInteger(-1); // -1 == normal shutdown
private static void registerExitHook(final Client client) {
BTraceLogger.debugPrint("registering shutdown hook");
Runtime.getRuntime().addShutdownHook(new Thread(
new Runnable() {
public void run() {
BTraceLogger.debugPrint("exitting btrace client");
client.exit(exitCode.get());
}
})
);
}
private static void sendEvent(Client client) throws IOException {
sendEvent(client, null);
}
private static void sendEvent(Client client, final String eName) throws IOException {
client.sendEvent(eName);
}
}