All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.byteman.contrib.dtest.Instrumentor Maven / Gradle / Ivy

Go to download

The Byteman dtest jar supports instrumentation of test code executed on remote server hosts and validation of assertions describing the expected operation of the instrumented methods.

There is a newer version: 4.0.23
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat, Inc. and/or its affiliates,
 * and individual contributors as indicated by the @author tags.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2010,
 * @author JBoss, by Red Hat.
 */
package org.jboss.byteman.contrib.dtest;

import org.jboss.byteman.agent.submit.ScriptText;
import org.jboss.byteman.agent.submit.Submit;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;

/**
 * The Instrumentor provides for installing tracing and other rules into a remote JVM.
 *
 * @author Jonathan Halliday ([email protected]) 2010-05
 */
public class Instrumentor
{
    private final Submit submit;
    private final Registry registry;
    private final int rmiRegistryPort;
    private final Map instrumentedClasses = new HashMap();
    private final List installedScripts = new LinkedList();
    private File redirectedSubmissionsFile;
    private Class helperClass = BytemanTestHelper.class;

    public Instrumentor(Submit submit, int rmiRegistryPort) throws RemoteException
    {
        this.submit = submit;
        this.rmiRegistryPort = rmiRegistryPort;
        this.registry = LocateRegistry.createRegistry(rmiRegistryPort);
    }

    public Instrumentor() throws RemoteException
    {
        this(new Submit(), BytemanTestHelper.DEFAULT_RMI_PORT);
    }

    public Instrumentor(String address, int port) throws RemoteException
    {
        this(new Submit(address, port), BytemanTestHelper.DEFAULT_RMI_PORT);
    }

    public Instrumentor(String address, int port, int rmiPort) throws RemoteException
    {
        this(new Submit(address, port), rmiPort);
    }

    /**
     * Returns the file to which Rule submission is currently redirected
     *
     * @return a file, or null if no redirection is in effect.
     */
    public File getRedirectedSubmissionsFile()
    {
        return redirectedSubmissionsFile;
    }

    /**
     * Sets the file to which Rule submissions should be redirected.
     *
     * @param redirectedSubmissionsFile a file, or null to cancel any existing redirection.
     */
    public void setRedirectedSubmissionsFile(File redirectedSubmissionsFile)
    {
        this.redirectedSubmissionsFile = redirectedSubmissionsFile;
    }

    /**
     * Returns a helper class which this {@link Instrumentor} instance defines
     * as parameter of HELPER clause.
     * @return the helper class
     */
    public Class getHelperClass() {
        return this.helperClass;
    }

    /**
     * 

* Redefine a helper class which is used as parameter of HELPER clause * by this instance of {@link Instrumentor} *

* When setting you will probably create your own byteman helper class which extends the default * one {@link BytemanTestHelper}.
* You need to know what you are doing when setting this parameter different from default helper * implementation as it provides core functionality for dtest library. * @param helperClass the new helper class */ public void setHelperClass(Class helperClass) { this.helperClass = helperClass; } /** * Add the specified jar to the remote app's system classpath. * * @param path the absolute path to the .jar file. * @throws Exception in case of failure. */ public void installHelperJar(String path) throws Exception { List jarPaths = new LinkedList(); jarPaths.add(path); submit.addJarsToSystemClassloader(jarPaths); Properties properties = new Properties(); properties.setProperty(BytemanTestHelper.RMIREGISTRY_PORT_PROPERTY_NAME, ""+rmiRegistryPort); submit.setSystemProperties(properties); } /** * Add method tracing rules to the specified class. * * @param clazz the Class to instrument. * @return a local proxy for the instrumentation. * @throws Exception in case of failure. */ public InstrumentedClass instrumentClass(Class clazz) throws Exception { Set nullSet = null; return instrumentClass(clazz, nullSet); } public InstrumentedClass instrumentClass(Class clazz, String... methodNames) throws Exception { if (methodNames ==null) { Set nullSet = null; return instrumentClass(clazz, nullSet); } else { return instrumentClass(clazz, new HashSet(Arrays.asList(methodNames))); } } /** * Add method tracing rules to the specified class. * If a non-null set of method names is supplied, only those methods are instrumented. * * @param clazz the Class to instrument. * @param methodNames the selection of methods to instrument. * @return a local proxy for the instrumentation. * @throws Exception in case of failure. */ public InstrumentedClass instrumentClass(Class clazz, Set methodNames) throws Exception { String className = clazz.getCanonicalName(); Set methodNamesToInstrument = new HashSet(); for(Method method : clazz.getDeclaredMethods()) { String declaredMethodName = method.getName(); if(methodNames == null || methodNames.contains(declaredMethodName)) { methodNamesToInstrument.add(declaredMethodName); } } return instrumentClass(className, methodNamesToInstrument); } /** * See {@link #instrumentClass(String, Set)} * * @param className the class name to instrument. * @param methodNames the selection of methods to instrument. * @throws Exception in case of failure. */ public InstrumentedClass instrumentClass(String className, String... methodNames) throws Exception { return instrumentClass(className, new HashSet(Arrays.asList(methodNames))); } /** * Add method tracing rules to the specified class name.
* If a null set of method names is supplied, {@link NullPointerException} is thrown. * * @param className the class name to instrument. * @param methodNames the selection of methods to instrument. * @return a local proxy for the instrumentation. * @throws NullPointerException in case of methodNames parameter is null * @throws Exception in case of failure. */ public InstrumentedClass instrumentClass(String className, Set methodNames) throws Exception { if(methodNames == null) { throw new NullPointerException("methodNames"); } Set instrumentedMethods = new HashSet(); StringBuilder ruleScriptBuilder = new StringBuilder(); for(String methodName : methodNames) { if(instrumentedMethods.contains(methodName)) { // do not add two identical rules for methods which differ by parameters continue; } String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_remotetrace_entry"; RuleConstructor.ClassClause builderClassClause = RuleConstructor.createRule(ruleName); RuleConstructor.MethodClause builderMethodClause = isInterface(className) ? builderClassClause.onInterface(className) : builderClassClause.onClass(className); RuleConstructor builder = builderMethodClause .inMethod(methodName) .atEntry() .helper(helperClass) .ifTrue() .doAction("setTriggering(false), debug(\"firing "+ruleName+"\", $0), remoteTrace(\""+className+"\", \""+methodName+"\", $*)"); ruleScriptBuilder.append(builder.build()); instrumentedMethods.add(methodName); } String scriptString = ruleScriptBuilder.toString(); installScript(className+".instrumentationScript", scriptString); return publish(className); } /** * Inject an action to take place upon the invocation of the specified class.method * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @throws Exception in case of failure. */ public void injectOnCall(Class clazz, String methodName, String action) throws Exception { injectOnCall(clazz.getCanonicalName(), methodName, action); } /** * Inject an action to take place upon the invocation of the specified class.method * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @throws Exception in case of failure. */ public void injectOnCall(String className, String methodName, String action) throws Exception { injectOnMethod(className, methodName, "true", action, "ENTRY"); } /** * Inject an action to take place upon exit of the specified class.method * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @throws Exception in case of failure. */ public void injectOnExit(Class clazz, String methodName, String action) throws Exception { injectOnExit(clazz.getCanonicalName(), methodName, action); } /** * Inject an action to take place upon exit of the specified class.method * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @throws Exception in case of failure. */ public void injectOnExit(String className, String methodName, String action) throws Exception { injectOnMethod(className, methodName, "true", action, "EXIT"); } /** * Inject an action to take place at a given point within the specified class.method * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @param atInjection the injection point e.g. "ENTRY". * @param condition the rule condition * @throws Exception in case of failure. */ public void injectOnMethod(Class clazz, String methodName, String condition, String action, String atInjection) throws Exception { injectOnMethod(clazz.getCanonicalName(), methodName, condition, action, atInjection); } /** * Inject an action to take place at a given point within the specified class.method * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @param atInjection the injection point e.g. "ENTRY". * @param condition the rule condition * @throws Exception in case of failure. */ public void injectOnMethod(String className, String methodName, String condition, String action, String atInjection) throws Exception { String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_injectionat"+atInjection; RuleConstructor.ClassClause builderClassClause = RuleConstructor.createRule(ruleName); RuleConstructor.MethodClause builderMethodClause = isInterface(className) ? builderClassClause.onInterface(className) : builderClassClause.onClass(className); RuleConstructor builder = builderMethodClause .inMethod(methodName) .at(atInjection) .helper(helperClass) .ifCondition(condition) .doAction(action); installScript("onCall"+className+"."+methodName+"."+atInjection, builder.build()); } /** *

* Inject an action to take place at a given point within the specified class.method *

* Difference to {@link #injectOnMethod(Class, String, String, String, String)} resides at * injection definition. The prior one expects "AT" injection point. This one expects the whole * location qualifier. * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @param where the injection definition e.g. "AT ENTRY" or "AFTER SYNCHRONIZATION". * @param condition the rule condition * @throws Exception in case of failure. */ public void injectOnMethodWhere(Class clazz, String methodName, String condition, String action, String where) throws Exception { injectOnMethodWhere(clazz.getCanonicalName(), methodName, condition, action, where); } /** *

* Inject an action to take place at a given point within the specified class.method *

* Difference to {@link #injectOnMethod(String, String, String, String, String)} resides at * injection definition. The prior one expects "AT" injection point. This one expects the whole * location qualifier. * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param action The action that should take place upon invocation of the method. * @param where the injection definition e.g. "AT ENTRY" or "AFTER SYNCHRONIZATION". * @param condition the rule condition * @throws Exception in case of failure. */ public void injectOnMethodWhere(String className, String methodName, String condition, String action, String where) throws Exception { String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_injectionat"+where; RuleConstructor.ClassClause builderClassClause = RuleConstructor.createRule(ruleName); RuleConstructor.MethodClause builderMethodClause = isInterface(className) ? builderClassClause.onInterface(className) : builderClassClause.onClass(className); RuleConstructor builder = builderMethodClause .inMethod(methodName) .where(where) .helper(helperClass) .ifCondition(condition) .doAction(action); installScript("onCall"+className+"."+methodName+"."+where, builder.build()); } /** * Inject a fault (i.e. Exception) to be thrown upon the invocation of the specified Class.method() * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param fault The type of Exception to be throw. If a checked exception, must be declared thrown by the specified method. * @param faultArgs Optional constructor arguments for the Exception. * @throws Exception in case of failure. */ public void injectFault(Class clazz, String methodName, Class fault, Object[] faultArgs) throws Exception { String className = clazz.getCanonicalName(); String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_faultinjection"; StringBuilder actionBuilder = new StringBuilder(); actionBuilder.append("setTriggering(false), debug(\"firing "+ruleName+"\", $0), "); actionBuilder.append("throw new "+fault.getCanonicalName()+"("); if(faultArgs != null) { for(int i = 0; i < faultArgs.length; i++) { String argClassName = faultArgs[i].getClass().getCanonicalName(); boolean requireQuotes = true; if(argClassName.startsWith("java.lang.") && !argClassName.equals("java.lang.String")) { requireQuotes = false; } if(requireQuotes) { actionBuilder.append("\""); } actionBuilder.append(faultArgs[i]); if(requireQuotes) { actionBuilder.append("\""); } if(i != faultArgs.length-1) { actionBuilder.append(", "); } } } actionBuilder.append(")"+"\n"); RuleConstructor.ClassClause builderClassClause = RuleConstructor.createRule(ruleName); RuleConstructor.MethodClause builderMethodClause = isInterface(className) ? builderClassClause.onInterface(className) : builderClassClause.onClass(className); RuleConstructor builder = builderMethodClause .inMethod(methodName) .atEntry() .helper(helperClass) .ifTrue() .doAction(actionBuilder.toString()); installScript("fault"+className+"."+methodName, builder.build()); } /** * Inject a Rule to kill the target JVM upon exit of the specified Class.method() * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @throws Exception in case of failure. */ public void crashAtMethodExit(Class clazz, String methodName) throws Exception { crashAtMethodExit(clazz.getCanonicalName(), methodName); } /** * Inject a Rule to kill the target JVM upon exit of the specified Class.method() * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @throws Exception in case of failure. */ public void crashAtMethodExit(String className, String methodName) throws Exception { crashAtMethod(className, methodName, "EXIT"); } /** * Inject a Rule to kill the target JVM upon entry to the specified Class.method() * * @param clazz The Class in which the injection point resides. * @param methodName The method which should be intercepted. * @throws Exception in case of failure. */ public void crashAtMethodEntry(Class clazz, String methodName) throws Exception { crashAtMethodEntry(clazz.getCanonicalName(), methodName); } /** * Inject a Rule to kill the target JVM upon entry to the specified Class.method() * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @throws Exception in case of failure. */ public void crashAtMethodEntry(String className, String methodName) throws Exception { crashAtMethod(className, methodName, "ENTRY"); } /** * Inject a Rule to kill the target JVM at a given point within the specified Class.method() * * @param className The name of the Class in which the injection point resides. * @param methodName The method which should be intercepted. * @param atInjection the injection point e.g. "ENTRY". * @throws Exception in case of failure. */ public void crashAtMethod(String className, String methodName, String atInjection) throws Exception { String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_crashat"+atInjection; String action = "debug(\"killing JVM\"), killJVM()"; RuleConstructor.ClassClause builderClassClause = RuleConstructor.createRule(ruleName); RuleConstructor.MethodClause builderMethodClause = isInterface(className) ? builderClassClause.onInterface(className) : builderClassClause.onClass(className); RuleConstructor builder = builderMethodClause .inMethod(methodName) .at(atInjection) .helper(helperClass) .ifTrue() .doAction(action); installScript("crash"+className+"."+methodName+"."+atInjection, builder.build()); } /** * Pass the assembled script to the remote JVM, either via. the Submit or, if redirection is in effect, to a file * which will then be read at restart of the remote JVM. Keep a local handle on the script, such that it can be * removed on request. * * @param scriptName The name of the script. Should be unique. * @param scriptString The text of the script i.e. one or more Rules. * @throws Exception in case of failure. */ public void installScript(String scriptName, String scriptString) throws Exception { System.out.println("installing: "+scriptString); if(scriptString.length() > 0) { if(redirectedSubmissionsFile == null) { ScriptText scriptText = new ScriptText(scriptName, scriptString); List scriptTexts = new LinkedList(); scriptTexts.add(scriptText); submit.addScripts(scriptTexts); installedScripts.addAll(scriptTexts); } else { appendToFile(redirectedSubmissionsFile, scriptString); ScriptText installedScriptText = null; ScriptText updatedScriptText = null; for(ScriptText scriptText : installedScripts) { if(scriptText.getFileName().equals(redirectedSubmissionsFile.getCanonicalPath())) { installedScriptText = scriptText; } } if(installedScriptText != null) { installedScripts.remove(installedScriptText); updatedScriptText = new ScriptText(installedScriptText.getFileName(), installedScriptText.getText()+scriptString); } else { updatedScriptText = new ScriptText(redirectedSubmissionsFile.getCanonicalPath(), scriptString); } installedScripts.add(updatedScriptText); } } } /** * Installing rule based on definition available by building {@link RuleConstructor}. * * @param builder rule builder with a rule definition to be installed as script * @return name of script that rule was installed under * @throws Exception in case of failure */ public String installRule(RuleConstructor builder) throws Exception { String scriptName = builder.getRuleName(); installScript(scriptName, builder.build()); return scriptName; } /** *

* Removing particular script from the remote byteman agent. *

* If you submitted a rule directly to remote JVM then the scriptName * is the name under the script was installed. *

* If you used {@link #setRedirectedSubmissionsFile(File)} to define * a file where the rule will be written then this method won't work * and you will get an {@link IllegalStateException}. * * @param scriptName name of script that should be removed * @throws Exception in case that script can't be removed */ public void removeScript(String scriptName) throws Exception { ScriptText script = findInstalledScript(scriptName); if(script == null) { throw new IllegalStateException("Script name " + scriptName + " can't be removed as " + "was not found in list of installed scripts"); } submit.deleteScripts(Arrays.asList(script)); installedScripts.remove(script); } /** * Removing particular script installed as a rule by {@link RuleConstructor}. * * @param builder a rule defining a script to be removed * @throws Exception in case of failure */ public void removeRule(RuleConstructor builder) throws Exception { String scriptName = builder.getRuleName(); removeScript(scriptName); } /** * Flush the local cache of scripts and proxies to remote instrumented classes. * Useful to reset local state when a remote JVM is crashed and hence reset. * * @throws Exception in case of failure. */ public synchronized void removeLocalState() throws Exception { for(String instrumentedClassName : instrumentedClasses.keySet()) { unpublish(instrumentedClassName); } instrumentedClasses.clear(); installedScripts.clear(); } /** * Flush any instrumentation for the given class in the remote system and clean up the local cache. * * @throws Exception in case of failure. */ public void removeAllInstrumentation() throws Exception { submit.deleteScripts(installedScripts); removeLocalState(); } /** * Write the given text to the end of the file. * * @param file a file name. * @param rule the text to append to the file. * @throws Exception in case of failure. */ private void appendToFile(File file, String rule) throws Exception { Writer writer = new FileWriter(file, true); writer.write(rule); writer.close(); } /** * Create a local communication endpoint for the the given class. * * @param className the class to create the wrapper for. * @return a local handle for accessing trace information received from the remote instrumentation. * @throws Exception in case of failure. */ private synchronized InstrumentedClass publish(String className) throws Exception { if(instrumentedClasses.containsKey(className)) { return instrumentedClasses.get(className); } InstrumentedClass instrumentedClass = new InstrumentedClass(className); RemoteInterface stub = (RemoteInterface) UnicastRemoteObject.exportObject(instrumentedClass, 0); registry.rebind(className, stub); instrumentedClasses.put(className, instrumentedClass); return instrumentedClass; } /** * Remove the local communication endpoint for the given class. * * @param className the class to remove. * @throws Exception in case of failure. */ private void unpublish(String className) throws Exception { registry.unbind(className); } /** * Trying to load a class and if successful then check if class is interface. * If it's then returns true. In all other cases returns false. */ private boolean isInterface(String className) { Class clazz = null; try { clazz = Class.forName(className); } catch (Exception e) { // can't find class in the class loader } if(clazz == null) { try { clazz = Thread.currentThread().getContextClassLoader().loadClass(className); } catch (Exception e) { // can't find class in the TCCL } } if(clazz != null) { return clazz.isInterface(); } else { return false; } } /** * Looping through installed scripts and checking if scriptName * is there. If so returns it otherwise returns null. */ private ScriptText findInstalledScript(String scriptName) { for(ScriptText installedScript: installedScripts) { if(installedScript.getFileName().equals(scriptName)) return installedScript; } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy