blob: fe481a3114afa9aff7fb56c787e8534d7c1ad5d6 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}