| /******************************************************************************* |
| * 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.ui.tests.performance; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| |
| import org.eclipse.core.commands.Command; |
| import org.eclipse.core.commands.CommandManager; |
| import org.eclipse.core.commands.ParameterizedCommand; |
| import org.eclipse.core.commands.common.NotDefinedException; |
| import org.eclipse.core.commands.contexts.Context; |
| import org.eclipse.core.commands.contexts.ContextManager; |
| import org.eclipse.jface.bindings.Binding; |
| import org.eclipse.jface.bindings.BindingManager; |
| import org.eclipse.jface.bindings.Scheme; |
| import org.eclipse.jface.bindings.keys.IKeyLookup; |
| import org.eclipse.jface.bindings.keys.KeyBinding; |
| import org.eclipse.jface.bindings.keys.KeyLookupFactory; |
| import org.eclipse.jface.bindings.keys.KeySequence; |
| import org.eclipse.jface.bindings.keys.KeyStroke; |
| import org.eclipse.jface.bindings.keys.ParseException; |
| import org.eclipse.swt.SWT; |
| |
| /** |
| * <p> |
| * Responsible for testing the commands, contexts and bindings architecture. |
| * This test does not rely on the existence of the workbench; it operates purely |
| * on JFace code and lower. See the method comments for descriptions of the |
| * currently supported performance tests. |
| * </p> |
| * |
| * @since 3.1 |
| */ |
| public final class CommandsPerformanceTest extends BasicPerformanceTest { |
| |
| /** |
| * <p> |
| * Constructs a branch of a context tree. This creates a branch of the given |
| * depth -- remembering the identifiers along the way. This method operates |
| * recursively. |
| * </p> |
| * <p> |
| * TODO This should add a bit of breadth to the tree. |
| * </p> |
| * |
| * @param contextManager |
| * The context manager in which the contexts should be defined; |
| * must not be <code>null</code>. |
| * @param parent |
| * The parent context identifier for the context to be created; |
| * may be <code>null</code>. |
| * @param successors |
| * The number of successors to create. The depth of the branch to |
| * be created. If this number is zero, then a context is created, |
| * but no recursive call is made. |
| * @param activeContextIds |
| * The list of active context identifiers; must not be |
| * <code>null</code>. |
| */ |
| private static final void createContext( |
| final ContextManager contextManager, final String parent, |
| final int successors, final List activeContextIds) { |
| final int count = activeContextIds.size(); |
| final String contextString = "context" + count; |
| final Context context = contextManager.getContext(contextString); |
| context.define(contextString, contextString, parent); |
| activeContextIds.add(contextString); |
| |
| if (successors == 0) { |
| return; |
| } |
| |
| createContext(contextManager, contextString, successors - 1, |
| activeContextIds); |
| } |
| |
| /** |
| * <p> |
| * Constructs a branch of a scheme tree. This creates a branch of the given |
| * depth -- remembering the schemes along the way. This method operates |
| * recursively. |
| * </p> |
| * <p> |
| * TODO This should add a bit of breadth to the tree. |
| * </p> |
| * |
| * @param bindingManager |
| * The binding manager in which the schemes should be defined; |
| * must not be <code>null</code>. |
| * @param parent |
| * The parent scheme identifier for the scheme to be created; may |
| * be <code>null</code>. |
| * @param successors |
| * The number of successors to create. The depth of the branch to |
| * be created. If this number is zero, then a scheme is created, |
| * but no recursive call is made. |
| * @param schemes |
| * The list of created schemes; must not be <code>null</code>. |
| */ |
| private static final void createScheme(final BindingManager bindingManager, |
| final String parent, final int successors, final List schemes) { |
| final int count = schemes.size(); |
| final String schemeString = "scheme" + count; |
| final Scheme scheme = bindingManager.getScheme(schemeString); |
| scheme.define(schemeString, schemeString, parent); |
| schemes.add(scheme); |
| |
| if (successors == 0) { |
| return; |
| } |
| |
| createScheme(bindingManager, schemeString, successors - 1, schemes); |
| } |
| |
| /** |
| * The binding manager for the currently running test. <code>null</code> |
| * if no test is running. |
| */ |
| private BindingManager bindingManager = null; |
| |
| /** |
| * The command manager for the currently running test. <code>null</code> |
| * if no test is running. |
| */ |
| private CommandManager commandManager = null; |
| |
| /** |
| * The context manager for the currently running test. <code>null</code> |
| * if no test is running. |
| */ |
| private ContextManager contextManager = null; |
| |
| /** |
| * Constructs an instance of <code>CommandsPerformanceTest</code>. |
| * |
| * @param testName |
| * Test's name. |
| */ |
| public CommandsPerformanceTest(final String name) { |
| super(name); |
| } |
| |
| /** |
| * <p> |
| * Sets up a sufficiently complex set of bindings. |
| * </p> |
| * <p> |
| * At the time of writing, Eclipse's key binding set contains about five |
| * hundred bindings. Of these, 140 specify platform information, while only |
| * 5 specify locale information. About 40 are deletion markers. The deepest |
| * point in the context tree is four levels. There are two schemes. |
| * </p> |
| * <p> |
| * The test binding set contains five thousand bindings. About 1400 specify |
| * either locale or platform information. Five hundred are deletion markers. |
| * The deepest point in the context tree is 40 levels. There are twenty |
| * schemes. |
| * </p> |
| * <p> |
| * The depth of the locale and platform tree is the same in both real life |
| * and the test case. It is difficult to imagine why the locale list would |
| * ever be anything but four elements, or why the platform list would ever |
| * be anything but three elements. |
| * </p> |
| * |
| * @throws NotDefinedException |
| * If something went wrong initializing the active scheme. |
| */ |
| protected final void doSetUp() throws NotDefinedException, Exception { |
| super.doSetUp(); |
| |
| /* |
| * The constants to use in creating the various objects. The platform |
| * locale count must be greater than or equal to the number of deletion |
| * markers. Deletion markers are typically created based on the platform |
| * or locale. |
| */ |
| final int contextTreeDepth = 40; |
| final int schemeDepth = 20; |
| final int bindingCount = 5000; |
| final int platformLocaleCount = 1400; |
| final int deletionMarkers = 500; |
| final String currentLocale = Locale.getDefault().toString(); |
| final String currentPlatform = SWT.getPlatform(); |
| |
| // Set-up a table of modifier keys. |
| final IKeyLookup lookup = KeyLookupFactory.getDefault(); |
| final int modifierKeys0 = 0; |
| final int modifierKeys1 = lookup.getAlt(); |
| final int modifierKeys2 = lookup.getCommand(); |
| final int modifierKeys3 = lookup.getCtrl(); |
| final int modifierKeys4 = lookup.getShift(); |
| final int modifierKeys5 = lookup.getAlt() | lookup.getCommand(); |
| final int modifierKeys6 = lookup.getAlt() | lookup.getCtrl(); |
| final int modifierKeys7 = lookup.getAlt() | lookup.getShift(); |
| final int modifierKeys8 = lookup.getCommand() | lookup.getCtrl(); |
| final int modifierKeys9 = lookup.getCommand() | lookup.getShift(); |
| final int modifierKeys10 = lookup.getCtrl() | lookup.getShift(); |
| final int modifierKeys11 = lookup.getAlt() | lookup.getCommand() |
| | lookup.getCtrl(); |
| final int modifierKeys12 = lookup.getAlt() | lookup.getCommand() |
| | lookup.getShift(); |
| final int modifierKeys13 = lookup.getAlt() | lookup.getCtrl() |
| | lookup.getShift(); |
| final int modifierKeys14 = lookup.getCommand() | lookup.getCtrl() |
| | lookup.getShift(); |
| final int modifierKeys15 = lookup.getAlt() | lookup.getCommand() |
| | lookup.getCtrl() | lookup.getShift(); |
| final int[] modifierKeyTable = { modifierKeys0, modifierKeys1, |
| modifierKeys2, modifierKeys3, modifierKeys4, modifierKeys5, |
| modifierKeys6, modifierKeys7, modifierKeys8, modifierKeys9, |
| modifierKeys10, modifierKeys11, modifierKeys12, modifierKeys13, |
| modifierKeys14, modifierKeys15 }; |
| |
| // Initialize the command manager. |
| commandManager = new CommandManager(); |
| |
| // Initialize the contexts. |
| contextManager = new ContextManager(); |
| final List activeContextIds = new ArrayList(); |
| createContext(contextManager, null, contextTreeDepth, activeContextIds); |
| contextManager.setActiveContextIds(new HashSet(activeContextIds)); |
| |
| // Initialize the schemes. |
| bindingManager = new BindingManager(contextManager, commandManager); |
| final List schemes = new ArrayList(); |
| createScheme(bindingManager, null, schemeDepth, schemes); |
| bindingManager |
| .setActiveScheme((Scheme) schemes.get(schemes.size() - 1)); |
| |
| // Create the deletion markers. |
| final Binding[] bindings = new Binding[bindingCount]; |
| for (int i = 0; i < deletionMarkers; i++) { |
| /* |
| * Set-up the locale and platform. These are based on the numbers |
| * given above. |
| */ |
| String locale = null; |
| String platform = null; |
| |
| if (i < platformLocaleCount) { |
| switch (i % 4) { |
| case 0: |
| locale = currentLocale; |
| break; |
| case 1: |
| platform = currentPlatform; |
| break; |
| case 2: |
| locale = "gibberish"; |
| break; |
| case 3: |
| platform = "gibberish"; |
| break; |
| } |
| } |
| |
| // Build a key sequence. |
| final char character = (char) ('A' + (i % 26)); |
| final int modifierKeys = modifierKeyTable[(i / 26) |
| % modifierKeyTable.length]; |
| final KeyStroke keyStroke = KeyStroke.getInstance(modifierKeys, |
| character); |
| final KeySequence keySequence = KeySequence.getInstance(keyStroke); |
| |
| // Build the other parameters. |
| final String schemeId = ((Scheme) schemes.get(i % schemes.size())) |
| .getId(); |
| final String contextId = (String) activeContextIds.get(i |
| % activeContextIds.size()); |
| final int type = (i % 2); |
| |
| // Construct the binding. |
| final Binding binding = new KeyBinding(keySequence, null, schemeId, |
| contextId, locale, platform, null, type); |
| bindings[i] = binding; |
| } |
| |
| /* |
| * Now create the regular bindings. By using the same loop structure and |
| * resetting the index to zero, we ensure that the deletion markers will |
| * actually delete something. |
| */ |
| for (int i = 0; i < bindingCount - deletionMarkers; i++) { |
| /* |
| * Set-up the locale and platform for those bindings that will not |
| * be used to match the above deletion markers. These are based on |
| * the numbers given above. |
| */ |
| String locale = null; |
| String platform = null; |
| |
| if ((i > deletionMarkers) && (i < platformLocaleCount)) { |
| switch (i % 4) { |
| case 0: |
| locale = currentLocale; |
| break; |
| case 1: |
| platform = currentPlatform; |
| break; |
| case 2: |
| locale = "gibberish"; |
| break; |
| case 3: |
| platform = "gibberish"; |
| break; |
| } |
| } |
| |
| // Build a key sequence. |
| final char character = (char) ('A' + (i % 26)); |
| final int modifierKeys = modifierKeyTable[(i / 26) |
| % modifierKeyTable.length]; |
| final KeyStroke keyStroke = KeyStroke.getInstance(modifierKeys, |
| character); |
| final KeySequence keySequence = KeySequence.getInstance(keyStroke); |
| |
| // Build the other parameters. |
| final String commandId = "command" + i; |
| final String schemeId = ((Scheme) schemes.get(i % schemes.size())) |
| .getId(); |
| final String contextId = (String) activeContextIds.get(i |
| % activeContextIds.size()); |
| final int type = (i % 2); |
| |
| // Construct the binding. |
| final Command command = commandManager.getCommand(commandId); |
| final ParameterizedCommand parameterizedCommand = new ParameterizedCommand( |
| command, null); |
| final Binding binding = new KeyBinding(keySequence, |
| parameterizedCommand, schemeId, contextId, locale, |
| platform, null, type); |
| bindings[i + deletionMarkers] = binding; |
| } |
| bindingManager.setBindings(bindings); |
| } |
| |
| protected final void doTearDown() throws Exception { |
| bindingManager = null; |
| commandManager = null; |
| contextManager = null; |
| super.doTearDown(); |
| } |
| |
| /** |
| * <p> |
| * Tests how long it takes to access the cache if no conditions have |
| * changed. It measures how long it takes to look up the computation from |
| * the cache one million times. |
| * </p> |
| * |
| * @throws ParseException |
| * If "CTRL+F" can't be parsed for some strange reason. |
| */ |
| public final void testBindingCacheHitHard() throws ParseException { |
| // Constants |
| final int cacheHits = 1000000; |
| final KeySequence keySequence = KeySequence.getInstance("CTRL+F"); |
| |
| // Compute once. |
| bindingManager.getPartialMatches(keySequence); |
| |
| // Time how long it takes to access the cache; |
| startMeasuring(); |
| for (int i = 0; i < cacheHits; i++) { |
| bindingManager.getPartialMatches(keySequence); |
| } |
| stopMeasuring(); |
| commitMeasurements(); |
| assertPerformance(); |
| } |
| |
| /** |
| * <p> |
| * Tests how long it takes to access the cache if no conditions have |
| * changed. It measures how long it takes to look up the computation from |
| * the cache one million times. In this test, the look-up is done in reverse -- |
| * from command identifier to trigger. |
| * </p> |
| * |
| * @throws ParseException |
| * If "CTRL+F" can't be parsed for some strange reason. |
| */ |
| public final void testBindingCacheHitHardReverse() throws ParseException { |
| // Constants |
| final int cacheHits = 1000000; |
| final KeySequence keySequence = KeySequence.getInstance("CTRL+F"); |
| |
| // Compute once. |
| bindingManager.getPartialMatches(keySequence); |
| |
| // Time how long it takes to access the cache; |
| startMeasuring(); |
| for (int i = 0; i < cacheHits; i++) { |
| bindingManager.getActiveBindingsFor((ParameterizedCommand) null); |
| } |
| stopMeasuring(); |
| commitMeasurements(); |
| assertPerformance(); |
| } |
| |
| /** |
| * <p> |
| * Tests how long it takes to access the cache if the conditions have |
| * changed, but the cache contains a matching entry. It measures how long it |
| * takes to look up the computation from the cache forty thousand times. |
| * </p> |
| * |
| * @throws ParseException |
| * If "CTRL+F" can't be parsed for some strange reason. |
| */ |
| public final void testBindingCacheHitSoft() throws ParseException { |
| // Constants |
| final int cacheHits = 10000; |
| final KeySequence keySequence = KeySequence.getInstance("CTRL+F"); |
| |
| // Compute once for each context set. |
| final Set contextSet1 = contextManager.getActiveContextIds(); |
| bindingManager.getPartialMatches(keySequence); |
| final List contextList = new ArrayList(contextSet1); |
| contextList.remove(contextList.size() - 1); |
| final Set contextSet2 = new HashSet(contextList); |
| contextManager.setActiveContextIds(contextSet2); |
| bindingManager.getPartialMatches(keySequence); |
| |
| // Time how long it takes to access the cache; |
| startMeasuring(); |
| for (int i = 0; i < cacheHits; i++) { |
| if ((i % 2) == 0) { |
| contextManager.setActiveContextIds(contextSet1); |
| } else { |
| contextManager.setActiveContextIds(contextSet2); |
| } |
| bindingManager.getPartialMatches(keySequence); |
| } |
| stopMeasuring(); |
| commitMeasurements(); |
| assertPerformance(); |
| } |
| |
| /** |
| * <p> |
| * Tests how long it takes to do a full computation (i.e., a cache miss) on |
| * an exceptionally large set of bindings. The binding set tries to mimick |
| * some of the same properties of a "real" binding set. |
| * </p> |
| * |
| * @throws ParseException |
| * If "CTRL+F" can't be parsed for some strange reason. |
| */ |
| public final void testBindingCacheMissLarge() throws ParseException { |
| // Constants |
| final KeySequence keySequence = KeySequence.getInstance("CTRL+F"); |
| |
| // Time how long it takes to solve the binding set. |
| startMeasuring(); |
| bindingManager.getPartialMatches(keySequence); |
| stopMeasuring(); |
| commitMeasurements(); |
| assertPerformance(); |
| } |
| } |