/* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.btrace.agent; import com.sun.btrace.runtime.BTraceProbeFactory; import com.sun.btrace.runtime.ClassCache; import com.sun.btrace.runtime.ClassInfo; import com.sun.btrace.runtime.BTraceTransformer; import com.sun.btrace.DebugSupport; import com.sun.btrace.SharedSettings; import java.io.IOException; import com.sun.btrace.org.objectweb.asm.ClassReader; import com.sun.btrace.org.objectweb.asm.ClassWriter; import com.sun.btrace.BTraceRuntime; import com.sun.btrace.CommandListener; import com.sun.btrace.comm.ErrorCommand; import com.sun.btrace.comm.ExitCommand; import com.sun.btrace.comm.InstrumentCommand; import com.sun.btrace.comm.OkayCommand; import com.sun.btrace.comm.RenameCommand; import com.sun.btrace.PerfReader; import com.sun.btrace.comm.RetransformationStartNotification; import com.sun.btrace.runtime.BTraceProbe; import com.sun.btrace.runtime.ClassFilter; import com.sun.btrace.runtime.Instrumentor; import com.sun.btrace.runtime.InstrumentUtils; import com.sun.btrace.util.templates.impl.MethodTrackingExpander; import java.io.BufferedWriter; import java.io.File; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.lang.management.ManagementFactory; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.regex.Matcher; import java.util.regex.Pattern; import sun.reflect.annotation.AnnotationParser; import sun.reflect.annotation.AnnotationType; /** * Abstract class that represents a BTrace client * at the BTrace agent. * * @author A. Sundararajan * @author J. Bachorik (j.bachorik@btrace.io) */ abstract class Client implements CommandListener { private static final Map<String, PrintWriter> WRITER_MAP = new HashMap<>(); protected final Instrumentation inst; private volatile BTraceRuntime runtime; private volatile String outputName; private byte[] btraceCode; private BTraceProbe probe; private Timer flusher; protected volatile PrintWriter out; protected final SharedSettings settings; protected final DebugSupport debug; private final BTraceTransformer transformer; private volatile boolean shuttingDown = false; static { ClassFilter.class.getClassLoader(); InstrumentUtils.class.getClassLoader(); Instrumentor.class.getClassLoader(); ClassReader.class.getClassLoader(); ClassWriter.class.getClassLoader(); AnnotationParser.class.getClassLoader(); AnnotationType.class.getClassLoader(); Annotation.class.getClassLoader(); MethodTrackingExpander.class.getClassLoader(); ClassCache.class.getClassLoader(); ClassInfo.class.getClassLoader(); BTraceRuntime.init(createPerfReaderImpl(), new RunnableGeneratorImpl()); } Client(ClientContext ctx) { this(ctx.getInstr(), ctx.getSettings(), ctx.getTransformer()); } private Client(Instrumentation inst, SharedSettings s, BTraceTransformer t) { this.inst = inst; this.settings = s != null ? s : SharedSettings.GLOBAL; this.transformer = t; this.debug = new DebugSupport(settings); setupWriter(); } @SuppressWarnings("DefaultCharset") protected final void setupWriter() { String outputFile = settings.getOutputFile(); if (outputFile == null || outputFile.equals("::null")) return; if (!outputFile.equals("::stdout")) { String outputDir = settings.getOutputDir(); String output = (outputDir != null ? outputDir + File.separator : "") + outputFile; outputFile = templateOutputFileName(output); infoPrint("Redirecting output to " + outputFile); } out = WRITER_MAP.get(outputFile); if (out == null) { if (outputFile.equals("::stdout")) { out = new PrintWriter(System.out); } else { if (settings.getFileRollMilliseconds() > 0) { out = new PrintWriter(new BufferedWriter( TraceOutputWriter.rollingFileWriter(new File(outputFile), settings) )); } else { out = new PrintWriter(new BufferedWriter(TraceOutputWriter.fileWriter(new File(outputFile), settings))); } } WRITER_MAP.put(outputFile, out); out.append("### BTrace Log: " + DateFormat.getInstance().format(new Date()) + "\n\n"); startFlusher(); } outputName = outputFile; } private void startFlusher() { int flushInterval; String flushIntervalStr = System.getProperty("com.sun.btrace.FileClient.flush", "5"); try { flushInterval = Integer.parseInt(flushIntervalStr); } catch (NumberFormatException e) { flushInterval = 5; // default } final int flushSec = flushInterval; if (flushSec > -1) { flusher = new Timer("BTrace FileClient Flusher", true); flusher.scheduleAtFixedRate(new TimerTask() { @Override public void run() { if (out != null) { out.flush(); } } }, flushSec, flushSec); } else { flusher = null; } } private String templateOutputFileName(String fName) { if (fName != null) { boolean dflt = fName.contains("[default]"); String agentName = System.getProperty("btrace.agent", "default"); String clientName = settings.getClientName(); fName = fName .replace("${client}", clientName != null ? clientName : "") .replace("${ts}", String.valueOf(System.currentTimeMillis())) .replace("${pid}", pid()) .replace("${agent}", agentName != null ? "." + agentName : "") .replace("[default]", ""); fName = replaceSysProps(fName); if (dflt && settings.isDebug()) { debugPrint("scriptOutputFile not specified. defaulting to " + fName); } } return fName; } private static final Pattern SYSPROP_PTN = Pattern.compile("\\$\\{(.+?)\\}"); private String replaceSysProps(String str) { int replaced = 0; do { StringBuffer sb = new StringBuffer(); replaced = replaceSysProps(str, sb); str = sb.toString(); } while (replaced > 0); return str; } private int replaceSysProps(String str, StringBuffer sb) { int cnt = 0; Matcher m = SYSPROP_PTN.matcher(str); while (m.find()) { String key = m.group(1); String val = System.getProperty(key); if (val != null) { cnt++; m.appendReplacement(sb, val); } else { m.appendReplacement(sb, m.group(0)); } } m.appendTail(sb); return cnt; } protected synchronized void onExit(int exitCode) { if (!shuttingDown) { BTraceRuntime.leave(); try { debugPrint("onExit:"); debugPrint("cleaning up transformers"); cleanupTransformers(); debugPrint("removing instrumentation"); retransformLoaded(); debugPrint("closing all I/O"); Thread.sleep(300); try { closeAll(); } catch (IOException e) { // ignore IOException when closing } debugPrint("done"); } catch (Throwable th) { // ExitException is expected here if (!th.getClass().getName().equals("com.sun.btrace.ExitException")) { debugPrint(th); BTraceRuntime.handleException(th); } } finally { runtime.shutdownCmdLine(); } } } protected final Class loadClass(InstrumentCommand instr) throws IOException { String[] args = instr.getArguments(); this.btraceCode = instr.getCode(); try { probe = load(btraceCode); if (!probe.isVerified()) { throw probe.getVerifierException(); } } catch (Throwable th) { debugPrint(th); errorExit(th); return null; } if (probe.isClassRenamed()) { if (isDebug()) { debugPrint("class renamed to " + probe.getClassName()); } onCommand(new RenameCommand(probe.getClassName())); } if (isDebug()) { debugPrint("creating BTraceRuntime instance for " + probe.getClassName()); } this.runtime = new BTraceRuntime(probe.getClassName(), args, this, debug, inst); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { boolean entered = BTraceRuntime.enter(runtime); try { shuttingDown = true; if (runtime != null) { runtime.handleExit(0); } } finally { if (entered) { BTraceRuntime.leave(); } } } })); if (isDebug()) { debugPrint("created BTraceRuntime instance for " + probe.getClassName()); debugPrint("sending Okay command"); } onCommand(new OkayCommand()); try { return probe.register(runtime, transformer); } catch (Throwable th) { debugPrint(th); errorExit(th); return null; } } protected void closeAll() throws IOException { if (flusher != null) { flusher.cancel(); } if (out != null) { out.close(); } WRITER_MAP.remove(outputName); } protected final void errorExit(Throwable th) throws IOException { debugPrint("sending error command"); onCommand(new ErrorCommand(th)); debugPrint("sending exit command"); onCommand(new ExitCommand(1)); closeAll(); } protected final void cleanupTransformers() { if (probe != null) { probe.unregister(); } } // package privates below this point final void infoPrint(String msg) { DebugSupport.info(msg); } final boolean isDebug() { return settings.isDebug(); } final void debugPrint(String msg) { debug.debug(msg); } final void debugPrint(Throwable th) { debug.debug(th); } final BTraceRuntime getRuntime() { return runtime; } protected final String getClassName() { return probe != null ? probe.getClassName() : "<unknown>"; } final boolean isCandidate(Class c) { String cname = c.getName().replace('.', '/'); if (c.isInterface() || c.isPrimitive() || c.isArray()) { return false; } if (ClassFilter.isSensitiveClass(cname)) { return false; } else { return probe.willInstrument(c); } } final void startRetransformClasses(int numClasses) { try { onCommand(new RetransformationStartNotification(numClasses)); if (isDebug()) { debugPrint("calling retransformClasses (" + numClasses + " classes to be retransformed)"); } } catch (IOException e) { debugPrint(e); } } final void endRetransformClasses() { try { onCommand(new OkayCommand()); if (isDebug()) debugPrint("finished retransformClasses"); } catch (IOException e) { debugPrint(e); } } // Internals only below this point private BTraceProbe load(byte[] buf) { BTraceProbeFactory f = new BTraceProbeFactory(settings); debugPrint("loading BTrace class"); BTraceProbe cn = f.createProbe(buf); if (isDebug()) { if (cn.isVerified()) { debugPrint("loaded '" + cn.getClassName() + "' successfully"); } else { debugPrint(cn.getClassName() + " failed verification"); } } return cn; } @SuppressWarnings("LiteralClassName") private static PerfReader createPerfReaderImpl() { // see if we can access any jvmstat class try { if (Client.class.getResource("sun/jvmstat/monitor/MonitoredHost.class") != null) { return (PerfReader) Class.forName("com.sun.btrace.agent.PerfReaderImpl").getDeclaredConstructor().newInstance(); } } catch (Exception exp) { // can happen if jvmstat is not available } // no luck, create null implementation return new NullPerfReaderImpl(); } void retransformLoaded() throws UnmodifiableClassException { if (runtime != null) { if (probe.isTransforming() && settings.isRetransformStartup()) { ArrayList<Class> list = new ArrayList<>(); debugPrint("retransforming loaded classes"); debugPrint("filtering loaded classes"); ClassCache cc = ClassCache.getInstance(); for (Class c : inst.getAllLoadedClasses()) { if (c != null) { cc.get(c); if (inst.isModifiableClass(c) && isCandidate(c)) { debugPrint("candidate " + c + " added"); list.add(c); } } } list.trimToSize(); int size = list.size(); if (size > 0) { Class[] classes = new Class[size]; list.toArray(classes); startRetransformClasses(size); if (isDebug()) { for(Class c : classes) { try { debugPrint("Attempting to retransform class: " + c.getName()); inst.retransformClasses(c); } catch (VerifyError e) { debugPrint("verification error: " + c.getName()); } } } else { inst.retransformClasses(classes); } } } runtime.send(new OkayCommand()); } } private static String pid() { String pName = ManagementFactory.getRuntimeMXBean().getName(); if (pName != null && pName.length() > 0) { String[] parts = pName.split("@"); if (parts.length == 2) { return parts[0]; } } return "-1"; } }