| /******************************************************************************* |
| * Copyright (c) 2000, 2006 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.text.tests.performance; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.net.InetAddress; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import junit.framework.Assert; |
| |
| import com.sun.jdi.ClassType; |
| import com.sun.jdi.Method; |
| import com.sun.jdi.ObjectReference; |
| import com.sun.jdi.VMDisconnectedException; |
| import com.sun.jdi.VirtualMachine; |
| import com.sun.jdi.VirtualMachineManager; |
| import com.sun.jdi.connect.AttachingConnector; |
| import com.sun.jdi.connect.Connector; |
| import com.sun.jdi.connect.IllegalConnectorArgumentsException; |
| import com.sun.jdi.event.BreakpointEvent; |
| import com.sun.jdi.event.Event; |
| import com.sun.jdi.event.EventIterator; |
| import com.sun.jdi.event.EventQueue; |
| import com.sun.jdi.event.EventSet; |
| import com.sun.jdi.event.VMDeathEvent; |
| import com.sun.jdi.request.BreakpointRequest; |
| import com.sun.jdi.request.EventRequest; |
| |
| import org.eclipse.test.internal.performance.InternalDimensions; |
| import org.eclipse.test.internal.performance.InternalPerformanceMeter; |
| import org.eclipse.test.internal.performance.PerformanceTestPlugin; |
| import org.eclipse.test.internal.performance.data.DataPoint; |
| import org.eclipse.test.internal.performance.data.Sample; |
| import org.eclipse.test.internal.performance.data.Scalar; |
| import org.eclipse.jdi.Bootstrap; |
| |
| /** |
| * To use this performance meter add the following VM arguments: |
| * <code>-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,address=7777,suspend=n,server=y -Declipse.perf.debugPort=7777</code>. |
| * Try a different port if 7777 does not work. |
| * Because the performance meter uses the VM's debugging facility, it cannot be |
| * debugged itself. A {@link org.eclipse.test.performance.Performance#getNullPerformanceMeter()} |
| * could be used while debugging clients. |
| * |
| * @since 3.1 |
| */ |
| public class InvocationCountPerformanceMeter extends InternalPerformanceMeter { |
| |
| /** |
| * An event reader that continuously reads and handles events coming |
| * from the VM. |
| */ |
| public class EventReader implements Runnable { |
| |
| /** Event queue */ |
| private EventQueue fEventQueue; |
| |
| /** Background thread */ |
| private Thread fThread; |
| |
| /** <code>true</code> if the reader should stop */ |
| private boolean fIsStopping= false; |
| |
| /** |
| * Creates a new event reader that will read from the given |
| * event queue. |
| * |
| * @param queue the event queue |
| */ |
| public EventReader(EventQueue queue) { |
| fEventQueue= queue; |
| } |
| |
| /** |
| * Start the thread that reads events. |
| */ |
| public void start() { |
| fThread= new Thread(this); |
| fThread.setDaemon(true); |
| synchronized (fThread) { |
| try { |
| fThread.start(); |
| fThread.wait(); |
| } catch (InterruptedException x) { |
| x.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * Tells the reader loop that it should stop. |
| */ |
| public void stop() { |
| fIsStopping= true; |
| if (fThread != null) { |
| try { |
| fThread.interrupt(); |
| fThread.join(); |
| fThread= null; |
| } catch (InterruptedException x) { |
| x.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * Continuously reads and handles events that are coming from |
| * the event queue. |
| */ |
| public void run() { |
| try { |
| synchronized (fThread) { |
| enableBreakpoints(); |
| fThread.notifyAll(); |
| } |
| while (!fIsStopping) { |
| try { |
| EventSet eventSet; |
| if (fTimeout == -1) |
| eventSet= fEventQueue.remove(); |
| else { |
| eventSet= fEventQueue.remove(fTimeout); |
| if (eventSet == null) { |
| fIsStopping= true; |
| System.out.println("Event reader timed out"); |
| return; |
| } |
| } |
| |
| EventIterator iterator= eventSet.eventIterator(); |
| while (iterator.hasNext()) { |
| Event event= iterator.nextEvent(); |
| if (event instanceof BreakpointEvent) |
| handleBreakpointEvent((BreakpointEvent) event); |
| if (event instanceof VMDeathEvent) { |
| fIsStopping= true; |
| System.out.println("VM unexpectedly died"); //$NON-NLS-1$ |
| } |
| } |
| |
| eventSet.resume(); |
| } catch (InterruptedException x) { |
| if (fIsStopping) |
| return; |
| System.out.println("Event reader loop unexpectedly interrupted"); //$NON-NLS-1$ |
| x.printStackTrace(); |
| return; |
| } catch (VMDisconnectedException x) { |
| System.out.println("VM unexpectedly disconnected"); //$NON-NLS-1$ |
| x.printStackTrace(); |
| return; |
| } |
| } |
| } finally { |
| disableBreakpoints(); |
| fVM.resume(); |
| } |
| } |
| |
| /** |
| * Enables all breakpoint request. |
| */ |
| private void enableBreakpoints() { |
| for (int i= 0; i < fBreakpointRequests.length; i++) |
| fBreakpointRequests[i].enable(); |
| } |
| |
| /** |
| * Disables all breakpoint request. |
| */ |
| private void disableBreakpoints() { |
| for (int i= 0; i < fBreakpointRequests.length; i++) |
| fBreakpointRequests[i].disable(); |
| } |
| |
| /** |
| * Handles the given breakpoint event. |
| * |
| * @param event the breakpoint event |
| */ |
| private void handleBreakpointEvent(BreakpointEvent event) { |
| fInvocationCount++; |
| if (fVerbose) |
| try { |
| ObjectReference thisObject= event.thread().frame(0).thisObject(); |
| Method method= event.location().method(); |
| String classKey= method.declaringType().name() + "#" + method.name() + method.signature(); //$NON-NLS-1$ |
| String instanceKey= fCollectInstanceResults ? (thisObject.referenceType().name() + " (id=" + thisObject.uniqueID() + ")") : "all instances"; //$NON-NLS-1$ //$NON-NLS-2$ |
| fResults.update(classKey, instanceKey); |
| } catch (Exception x) { |
| x.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * Invocation count results. |
| */ |
| public static class Results { |
| |
| /** The result map */ |
| private Map fResultsMap= new HashMap(); |
| |
| /** |
| * Updates the results for the given pair of keys. |
| * |
| * @param key1 the first key |
| * @param key2 the second key |
| */ |
| public void update(Object key1, Object key2) { |
| int value; |
| Map results; |
| if (fResultsMap.containsKey(key1)) { |
| results= (Map) fResultsMap.get(key1); |
| if (results.containsKey(key2)) |
| value= ((Integer) results.get(key2)).intValue(); |
| else |
| value= 0; |
| } else { |
| results= new HashMap(); |
| fResultsMap.put(key1, results); |
| value= 0; |
| } |
| results.put(key2, new Integer(value + 1)); |
| } |
| |
| /** |
| * Clears the results. |
| */ |
| public void clear() { |
| fResultsMap.clear(); |
| } |
| |
| /** |
| * Prints the results. |
| */ |
| public void print() { |
| for (Iterator iter= fResultsMap.keySet().iterator(); iter.hasNext();) |
| print(iter.next()); |
| } |
| |
| /** |
| * Prints the results for the given first key. |
| * |
| * @param key1 the first key |
| */ |
| public void print(Object key1) { |
| System.out.println(key1.toString() + ":"); //$NON-NLS-1$ |
| Map results= ((Map) fResultsMap.get(key1)); |
| for (Iterator iter= results.keySet().iterator(); iter.hasNext();) { |
| Object key2= iter.next(); |
| System.out.println("\t" + key2 + ": " + ((Integer) results.get(key2)).intValue()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| } |
| |
| /** System property that specifies the debugging port */ |
| public static final String DEBUG_PORT_PROPERTY= "eclipse.perf.debugPort"; //$NON-NLS-1$ |
| |
| /** Default debugging port when the system property cannot be interpreted as an integer */ |
| public static final int DEBUG_PORT_DEFAULT= 7777; |
| |
| /** |
| * Debugging port |
| * <p> |
| * TODO: Fetch the debug port with |
| * <code>Platform.getCommandLineArgs()</code> or |
| * <code>org.eclipse.core.runtime.adaptor.EnvironmentInfo.getDefault().getCommandLineArgs()</code> |
| * (the latter may be necessary because it also includes low-level |
| * arguments). |
| * </p> |
| */ |
| private static final int PORT= intValueOf(System.getProperty(DEBUG_PORT_PROPERTY), DEBUG_PORT_DEFAULT); |
| |
| /** Empty array of methods */ |
| private static final java.lang.reflect.Method[] NO_METHODS= new java.lang.reflect.Method[0]; |
| |
| /** Empty array of constructors */ |
| private static final Constructor[] NO_CONSTRUCTORS= new Constructor[0]; |
| |
| /** Results */ |
| private Results fResults= new Results(); |
| |
| /** Virtual machine */ |
| private VirtualMachine fVM; |
| |
| /** Event reader */ |
| private EventReader fEventReader; |
| |
| /** Methods from which to count the invocations */ |
| private java.lang.reflect.Method[] fMethods; |
| |
| /** Constructors from which to count the invocations */ |
| private Constructor[] fConstructors; |
| |
| /** Timestamp */ |
| private long fStartTime; |
| |
| /** Total number of invocations */ |
| private long fInvocationCount; |
| |
| /** All breakpoint requests */ |
| private BreakpointRequest[] fBreakpointRequests; |
| |
| /** <code>true</code> iff additional information should be collected and printed */ |
| private boolean fVerbose; |
| |
| /** <code>true</code> iff additional information should be collected per instance */ |
| private boolean fCollectInstanceResults= true; |
| |
| /** Timeout after which the event reader aborts when no event occurred, <code>-1</code> for infinite */ |
| private long fTimeout= -1; |
| |
| /** |
| * Initialize the performance meter to count the number of invocation of |
| * the given methods and constructors. |
| * |
| * @param scenarioId the scenario id |
| * @param methods the methods |
| * @param constructors the constructors |
| */ |
| public InvocationCountPerformanceMeter(String scenarioId, java.lang.reflect.Method[] methods, Constructor[] constructors) { |
| super(scenarioId); |
| Assert.assertNotNull("Could not create performance meter: check the command line arguments (see InvocationCountPerformanceMeter for details)", System.getProperty(DEBUG_PORT_PROPERTY)); |
| |
| fMethods= methods; |
| fConstructors= constructors; |
| fStartTime= System.currentTimeMillis(); |
| fVerbose= PerformanceTestPlugin.getDBLocation() == null || System.getProperty(VERBOSE_PERFORMANCE_METER_PROPERTY) != null; |
| } |
| |
| /** |
| * Initialize the performance meter to count the number of invocation of |
| * the given methods. |
| * |
| * @param scenarioId the scenario id |
| * @param methods the methods |
| */ |
| public InvocationCountPerformanceMeter(String scenarioId, java.lang.reflect.Method[] methods) { |
| this(scenarioId, methods, NO_CONSTRUCTORS); |
| } |
| |
| /** |
| * Initialize the performance meter to count the number of invocation of |
| * the given constructors. |
| * |
| * @param scenarioId the scenario id |
| * @param constructors the constructors |
| */ |
| public InvocationCountPerformanceMeter(String scenarioId, Constructor[] constructors) { |
| this(scenarioId, NO_METHODS, constructors); |
| fCollectInstanceResults= false; |
| } |
| |
| /* |
| * @see org.eclipse.test.performance.PerformanceMeter#start() |
| */ |
| public void start() { |
| try { |
| String localhost = InetAddress.getLocalHost().getCanonicalHostName(); |
| attach(localhost, PORT); //$NON-NLS-1$ |
| |
| List requests= new ArrayList(); |
| for (int i= 0; i < fMethods.length; i++) |
| requests.add(createBreakpointRequest(fMethods[i])); |
| for (int i= 0; i < fConstructors.length; i++) |
| requests.add(createBreakpointRequest(fConstructors[i])); |
| fBreakpointRequests= (BreakpointRequest[]) requests.toArray(new BreakpointRequest[requests.size()]); |
| |
| fEventReader= new EventReader(fVM.eventQueue()); |
| fEventReader.start(); |
| } catch (IOException x) { |
| x.printStackTrace(); |
| } catch (IllegalConnectorArgumentsException x) { |
| x.printStackTrace(); |
| } finally { |
| Assert.assertNotNull("Could not start performance meter, hints:\n1) check the command line arguments (see InvocationCountPerformanceMeter for details)\n2) use a different port number", fEventReader); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.test.performance.PerformanceMeter#stop() |
| */ |
| public void stop() { |
| if (fEventReader != null) { |
| fEventReader.stop(); |
| fEventReader= null; |
| } |
| |
| if (fVM != null) { |
| deleteBreakpointRequests(); |
| detach(); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.test.performance.PerformanceMeter#commit() |
| */ |
| public void commit() { |
| super.commit(); |
| if (fVerbose) { |
| System.out.println("Detailed results:"); //$NON-NLS-1$ |
| fResults.print(); |
| System.out.println(); |
| System.out.println("--------------------------------------------------"); //$NON-NLS-1$ |
| System.out.println(); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.test.performance.PerformanceMeter#dispose() |
| */ |
| public void dispose() { |
| super.dispose(); |
| if (fVM != null || fEventReader != null) |
| stop(); |
| fResults= null; |
| fMethods= null; |
| fConstructors= null; |
| } |
| |
| /* |
| * @see org.eclipse.test.internal.performance.InternalPerformanceMeter#getSample() |
| */ |
| public Sample getSample() { |
| Map map= new HashMap(1); |
| map.put(InternalDimensions.INVOCATION_COUNT, new Scalar(InternalDimensions.INVOCATION_COUNT, fInvocationCount)); |
| DataPoint[] dataPoints= new DataPoint[] { new DataPoint(AFTER, map) }; |
| return new Sample(getScenarioName(), fStartTime, null, dataPoints); |
| } |
| |
| /** |
| * Attaches to the given host and port. |
| * |
| * @param host the host |
| * @param port the port |
| * @throws IOException |
| * @throws IllegalConnectorArgumentsException |
| */ |
| private void attach(String host, int port) throws IOException, IllegalConnectorArgumentsException { |
| VirtualMachineManager manager= Bootstrap.virtualMachineManager(); |
| List connectors= manager.attachingConnectors(); |
| AttachingConnector connector= (AttachingConnector) connectors.get(0); |
| Map args= connector.defaultArguments(); |
| |
| ((Connector.Argument) args.get("port")).setValue(String.valueOf(port)); //$NON-NLS-1$ |
| ((Connector.Argument) args.get("hostname")).setValue(host); //$NON-NLS-1$ |
| fVM= connector.attach(args); |
| } |
| |
| /** |
| * Detaches from the VM. |
| */ |
| private void detach() { |
| fVM.dispose(); |
| fVM= null; |
| } |
| |
| /** |
| * Creates a breakpoint request on entry of the given method. |
| * |
| * @param method the method |
| * @return the breakpoint request |
| */ |
| private BreakpointRequest createBreakpointRequest(java.lang.reflect.Method method) { |
| return createBreakpointRequest(getMethod(method.getDeclaringClass().getName(), method.getName(), getJNISignature(method))); |
| } |
| |
| /** |
| * Creates a breakpoint request on entry of the given constructor. |
| * |
| * @param constructor the method |
| * @return the breakpoint request |
| */ |
| private BreakpointRequest createBreakpointRequest(Constructor constructor) { |
| return createBreakpointRequest(getMethod(constructor.getDeclaringClass().getName(), "<init>", getJNISignature(constructor))); |
| } |
| |
| /** |
| * Creates a breakpoint request on entry of the given method. |
| * |
| * @param method the method |
| * @return the breakpoint request |
| */ |
| private BreakpointRequest createBreakpointRequest(Method method) { |
| BreakpointRequest request= fVM.eventRequestManager().createBreakpointRequest(method.location()); |
| request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); |
| return request; |
| } |
| |
| /** |
| * Returns the JDI method for the given class, name and signature. |
| * |
| * @param className fully-qualified class name |
| * @param name method name |
| * @param signature JNI-style signature |
| * @return the JDI method |
| */ |
| private Method getMethod(String className, String name, String signature) { |
| ClassType type= (ClassType) fVM.classesByName(className).get(0); |
| return type.concreteMethodByName(name, signature); |
| } |
| |
| /** |
| * Deletes all breakpoint requests. |
| */ |
| private void deleteBreakpointRequests() { |
| try { |
| fVM.eventRequestManager().deleteAllBreakpoints(); |
| } catch (VMDisconnectedException x) { |
| /* |
| * No need to let the test fail at this point since |
| * next step is to disconnect from the VM. |
| */ |
| System.out.println("VM unexpectedly disconnected"); //$NON-NLS-1$ |
| x.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Returns the JNI-style signature of the given method. See |
| * http://java.sun.com/j2se/1.4.2/docs/guide/jpda/jdi/com/sun/jdi/doc-files/signature.html |
| * |
| * @param method the method |
| * @return the JNI style signature |
| */ |
| private String getJNISignature(java.lang.reflect.Method method) { |
| return getJNISignature(method.getParameterTypes()) + getJNISignature(method.getReturnType()); |
| } |
| |
| /** |
| * Returns the JNI-style signature of the given constructor. See |
| * http://java.sun.com/j2se/1.4.2/docs/guide/jpda/jdi/com/sun/jdi/doc-files/signature.html |
| * |
| * @param constructor the constructor |
| * @return the JNI style signature |
| */ |
| private String getJNISignature(Constructor constructor) { |
| return getJNISignature(constructor.getParameterTypes()) + "V"; |
| } |
| |
| /** |
| * Returns the JNI-style signature of the given parameter types. See |
| * http://java.sun.com/j2se/1.4.2/docs/guide/jpda/jdi/com/sun/jdi/doc-files/signature.html |
| * |
| * @param paramTypes the parameter types |
| * @return the JNI style signature |
| */ |
| private String getJNISignature(Class[] paramTypes) { |
| StringBuffer signature= new StringBuffer(); |
| signature.append('('); |
| for (int i = 0; i < paramTypes.length; ++i) |
| signature.append(getJNISignature(paramTypes[i])); |
| signature.append(')'); |
| return signature.toString(); |
| } |
| |
| /** |
| * Returns the JNI-style signature of the given class. See |
| * http://java.sun.com/j2se/1.4.2/docs/guide/jpda/jdi/com/sun/jdi/doc-files/signature.html |
| * |
| * @param clazz the class |
| * @return the JNI style signature |
| */ |
| private String getJNISignature(Class clazz) { |
| String qualifiedName= getName(clazz); |
| StringBuffer signature= new StringBuffer(); |
| |
| int index= qualifiedName.indexOf('[') + 1; |
| while (index > 0) { |
| index= qualifiedName.indexOf('[', index) + 1; |
| signature.append('['); |
| } |
| |
| int nameEndOffset= qualifiedName.indexOf('['); |
| if (nameEndOffset < 0) |
| nameEndOffset= qualifiedName.length(); |
| |
| // Check for primitive types |
| String name= qualifiedName.substring(0, nameEndOffset); |
| if (name.equals("byte")) { //$NON-NLS-1$ |
| signature.append('B'); |
| return signature.toString(); |
| } else if (name.equals("boolean")) { //$NON-NLS-1$ |
| signature.append('Z'); |
| return signature.toString(); |
| } else if (name.equals("int")) { //$NON-NLS-1$ |
| signature.append('I'); |
| return signature.toString(); |
| } else if (name.equals("double")) { //$NON-NLS-1$ |
| signature.append('D'); |
| return signature.toString(); |
| } else if (name.equals("short")) { //$NON-NLS-1$ |
| signature.append('S'); |
| return signature.toString(); |
| } else if (name.equals("char")) { //$NON-NLS-1$ |
| signature.append('C'); |
| return signature.toString(); |
| } else if (name.equals("long")) { //$NON-NLS-1$ |
| signature.append('J'); |
| return signature.toString(); |
| } else if (name.equals("float")) { //$NON-NLS-1$ |
| signature.append('F'); |
| return signature.toString(); |
| } else if (name.equals("void")) { //$NON-NLS-1$ |
| signature.append('V'); |
| return signature.toString(); |
| } |
| |
| // Class type |
| signature.append('L'); |
| signature.append(name.replace('.','/')); |
| signature.append(';'); |
| return signature.toString(); |
| } |
| |
| /** |
| * Returns the given class' name |
| * |
| * @param clazz the class |
| * @return the name |
| */ |
| private String getName(Class clazz) { |
| if (clazz.isArray()) |
| return getName(clazz.getComponentType()) + "[]"; //$NON-NLS-1$ |
| return clazz.getName(); |
| } |
| |
| /** |
| * Returns the integer value of the given string unless the string |
| * cannot be interpreted as such, in this case the given default is |
| * returned. |
| * |
| * @param stringValue the string to be interpreted as integer |
| * @param defaultValue the default integer value |
| * @return the integer value |
| */ |
| private static int intValueOf(String stringValue, int defaultValue) { |
| try { |
| if (stringValue != null) |
| return Integer.valueOf(stringValue).intValue(); |
| } catch (NumberFormatException e) { |
| // use default |
| } |
| return defaultValue; |
| } |
| |
| /** |
| * Returns the timeout after which the event reader aborts when no event |
| * occurred, <code>-1</code> for infinite. |
| * <p> |
| * For debugging purposes. |
| * </p> |
| * <p> |
| * For debugging purposes. |
| * </p> |
| * |
| * @return the timeout after which the event reader aborts when no event |
| * occurred, <code>-1</code> for infinite |
| */ |
| public long getTimeout() { |
| return fTimeout; |
| } |
| |
| /** |
| * Sets the timeout after which the event reader aborts when no event |
| * occurred, <code>-1</code> for infinite. |
| * <p> |
| * For debugging purposes. |
| * </p> |
| * |
| * @param timeout the timeout after which the event reader aborts when |
| * no event occurred, <code>-1</code> for infinite |
| */ |
| public void setTimeout(long timeout) { |
| fTimeout= timeout; |
| } |
| } |