/* * Copyright (c) 2007, 2015, 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.api.impl; import com.sun.btrace.CommandListener; import com.sun.btrace.api.BTraceCompiler; import com.sun.btrace.client.Client; import com.sun.btrace.comm.Command; import com.sun.btrace.comm.RetransformationStartNotification; import java.io.IOException; import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import com.sun.btrace.api.BTraceEngine; import com.sun.btrace.api.BTraceSettings; import com.sun.btrace.api.BTraceTask; import com.sun.btrace.comm.ErrorCommand; import com.sun.btrace.spi.BTraceCompilerFactory; import com.sun.btrace.spi.BTraceSettingsProvider; import com.sun.btrace.spi.ClasspathProvider; import com.sun.btrace.spi.PortLocator; import com.sun.btrace.spi.impl.BTraceCompilerFactoryImpl; import com.sun.btrace.spi.impl.BTraceSettingsProviderImpl; import com.sun.btrace.spi.OutputProvider; import com.sun.btrace.spi.impl.PortLocatorImpl; import java.lang.ref.WeakReference; import java.util.EnumSet; import java.util.Iterator; import java.util.ServiceLoader; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * * @author Jaroslav Bachorik */ public class BTraceEngineImpl extends BTraceEngine { final private static Logger LOGGER = Logger.getLogger(BTraceEngineImpl.class.getName()); /** * Basic state listener<br> * Makes it possible to intercept start/stop of a certain {@linkplain BTraceTask} */ interface StateListener extends EventListener { /** * Called on task startup * @param task The task that has started up */ void onTaskStart(BTraceTask task); /** * Called on task shutdown * @param task The task that has stopped */ void onTaskStop(BTraceTask task); } private BTraceSettingsProvider settingsProvider; private BTraceCompilerFactory compilerFactory; private ClasspathProvider cpProvider; private PortLocator portLocator; private OutputProvider outputProvider; private Map<BTraceTask, Client> clientMap = new HashMap<BTraceTask, Client>(); final private Set<WeakReference<StateListener>> listeners = new HashSet<WeakReference<StateListener>>(); final private ExecutorService commQueue = Executors.newCachedThreadPool(); public BTraceEngineImpl() { this.settingsProvider = initSettingsProvider(); this.compilerFactory = initCompilerFactory(); this.cpProvider = initClasspathProvider(); this.portLocator = initPortLocator(); this.outputProvider = initOutputProvider(); } private static BTraceCompilerFactory initCompilerFactory() { ServiceLoader<BTraceCompilerFactory> loader = ServiceLoader.load(BTraceCompilerFactory.class); if (loader != null) { Iterator<BTraceCompilerFactory> iter = loader.iterator(); if (iter.hasNext()) { return iter.next(); } } return new BTraceCompilerFactoryImpl(); } private static ClasspathProvider initClasspathProvider() { ServiceLoader<ClasspathProvider> loader = ServiceLoader.load(ClasspathProvider.class); if (loader != null) { Iterator<ClasspathProvider> iter = loader.iterator(); if (iter.hasNext()) { return iter.next(); } } return ClasspathProvider.EMPTY; } private static BTraceSettingsProvider initSettingsProvider() { ServiceLoader<BTraceSettingsProvider> loader = ServiceLoader.load(BTraceSettingsProvider.class); if (loader != null) { Iterator<BTraceSettingsProvider> iter = loader.iterator(); if (iter.hasNext()) { return iter.next(); } } return new BTraceSettingsProviderImpl(); } private static PortLocator initPortLocator() { ServiceLoader<PortLocator> loader = ServiceLoader.load(PortLocator.class); if (loader != null) { Iterator<PortLocator> iter = loader.iterator(); if (iter.hasNext()) { return iter.next(); } } return new PortLocatorImpl(); } private static OutputProvider initOutputProvider() { ServiceLoader<OutputProvider> loader = ServiceLoader.load(OutputProvider.class); if (loader != null) { Iterator<OutputProvider> iter = loader.iterator(); if (iter.hasNext()) { return iter.next(); } } return OutputProvider.DEFAULT; } @Override public BTraceTask createTask(int pid) { return new BTraceTaskImpl(pid, this); } void addListener(StateListener listener) { synchronized(listeners) { listeners.add(new WeakReference<StateListener>(listener)); } } void removeListener(StateListener listener) { synchronized(listeners) { for(Iterator<WeakReference<StateListener>> iter=listeners.iterator();iter.hasNext();) { WeakReference<StateListener> ref = iter.next(); StateListener l = ref.get(); if (l == null || l.equals(listener)) { iter.remove(); } } } } boolean start(final BTraceTask task) { LOGGER.finest("Starting BTrace task"); boolean result = doStart(task); LOGGER.log(Level.FINEST, "BTrace task {0}", result ? "started successfuly" : "failed"); if (result) { fireOnTaskStart(task); } return result; } final private AtomicBoolean stopping = new AtomicBoolean(false); boolean stop(final BTraceTask task) { LOGGER.finest("Attempting to stop BTrace task"); try { if (stopping.compareAndSet(false, true)) { LOGGER.finest("Stopping BTrace task"); boolean result = doStop(task); LOGGER.log(Level.FINEST, "BTrace task {0}", result ? "stopped successfuly" : "not stopped"); if (result) { fireOnTaskStop(task); } return result; } return true; } finally { stopping.set(false); } } @SuppressWarnings("FutureReturnValueIgnored") private boolean doStart(BTraceTask task) { final AtomicBoolean result = new AtomicBoolean(false); final BTraceTaskImpl btrace = (BTraceTaskImpl) task; try { final CountDownLatch latch = new CountDownLatch(1); final BTraceCompiler compiler = compilerFactory.newCompiler(btrace); btrace.setState(BTraceTask.State.COMPILING); final byte[] bytecode = compiler.compile(btrace.getScript(), task.getClassPath(), outputProvider.getStdErr(task)); if (bytecode.length == 0) { btrace.setState(BTraceTask.State.FAILED); return false; } btrace.setState(BTraceTask.State.COMPILED); LOGGER.log(Level.FINEST, "Compiled the trace: {0} bytes", bytecode.length); commQueue.submit(new Runnable() { @Override public void run() { int port = portLocator.getTaskPort(btrace); LOGGER.log(Level.FINEST, "BTrace agent listening on port {0}", port); BTraceSettings settings = settingsProvider.getSettings(); final Client client = new Client( port, null, ".", settings.isDebugMode(), true, btrace.isTrusted(), settings.isDumpClasses(), settings.getDumpClassPath(), settings.getStatsd() ); try { client.attach(String.valueOf(btrace.getPid()), compiler.getAgentJarPath(), compiler.getToolsJarPath(), null); Thread.sleep(200); // give the server side time to initialize and open the port client.submit(bytecode, new String[]{}, new CommandListener() { @Override public void onCommand(Command cmd) throws IOException { LOGGER.log(Level.FINEST, "Received command: {0}", cmd.toString()); switch (cmd.getType()) { case Command.SUCCESS: { if (btrace.getState() == BTraceTask.State.COMPILED) { btrace.setState(BTraceTask.State.ACCEPTED); } else if (EnumSet.of(BTraceTask.State.INSTRUMENTING, BTraceTask.State.ACCEPTED).contains(btrace.getState())) { btrace.setState(BTraceTask.State.RUNNING); result.set(true); clientMap.put(btrace, client); latch.countDown(); } break; } case Command.EXIT: { btrace.setState(BTraceTask.State.FINISHED); latch.countDown(); stop(btrace); break; } case Command.RETRANSFORMATION_START: { int numClasses = ((RetransformationStartNotification)cmd).getNumClasses(); btrace.setInstrClasses(numClasses); btrace.setState(BTraceTask.State.INSTRUMENTING); break; } case Command.ERROR: { ((ErrorCommand)cmd).getCause().printStackTrace(outputProvider.getStdErr(btrace)); btrace.setState(BTraceTask.State.FAILED); latch.countDown(); stop(btrace); break; } } btrace.dispatchCommand(cmd); } }); } catch (Exception e) { LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); result.set(false); latch.countDown(); } } }); latch.await(); } catch (InterruptedException ex) { LOGGER.log(Level.WARNING, null, ex); } return result.get(); } private boolean doStop(BTraceTask task) { Client client = clientMap.remove(task); if (client != null) { try { client.sendExit(0); Thread.sleep(300); client.close(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (IOException ex) { // ignore all IO related exception during the stop sequence } } return true; } void sendEvent(BTraceTaskImpl task) { Client client = clientMap.get(task); if (client != null) { try { client.sendEvent(); } catch (IOException ex) { LOGGER.log(Level.SEVERE, null, ex); } } } void sendEvent(BTraceTaskImpl task, String eventName) { Client client = clientMap.get(task); if (client != null) { try { client.sendEvent(eventName); } catch (IOException ex) { LOGGER.log(Level.SEVERE, null, ex); } } } ClasspathProvider getClasspathProvider() { return cpProvider; } private void fireOnTaskStart(BTraceTask task) { synchronized(listeners) { for(WeakReference<StateListener> ref : listeners) { StateListener l = ref.get(); if (l != null) l.onTaskStart(task); } } } private void fireOnTaskStop(BTraceTask task) { synchronized(listeners) { for(WeakReference<StateListener> ref : listeners) { StateListener l = ref.get(); if (l != null) l.onTaskStop(task); } } } }