001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.tests.performance;
011:
012: import java.util.ArrayList;
013: import java.util.HashSet;
014: import java.util.List;
015: import java.util.Locale;
016: import java.util.Set;
017:
018: import org.eclipse.core.commands.Command;
019: import org.eclipse.core.commands.CommandManager;
020: import org.eclipse.core.commands.ParameterizedCommand;
021: import org.eclipse.core.commands.common.NotDefinedException;
022: import org.eclipse.core.commands.contexts.Context;
023: import org.eclipse.core.commands.contexts.ContextManager;
024: import org.eclipse.jface.bindings.Binding;
025: import org.eclipse.jface.bindings.BindingManager;
026: import org.eclipse.jface.bindings.Scheme;
027: import org.eclipse.jface.bindings.keys.IKeyLookup;
028: import org.eclipse.jface.bindings.keys.KeyBinding;
029: import org.eclipse.jface.bindings.keys.KeyLookupFactory;
030: import org.eclipse.jface.bindings.keys.KeySequence;
031: import org.eclipse.jface.bindings.keys.KeyStroke;
032: import org.eclipse.jface.bindings.keys.ParseException;
033: import org.eclipse.swt.SWT;
034:
035: /**
036: * <p>
037: * Responsible for testing the commands, contexts and bindings architecture.
038: * This test does not rely on the existence of the workbench; it operates purely
039: * on JFace code and lower. See the method comments for descriptions of the
040: * currently supported performance tests.
041: * </p>
042: *
043: * @since 3.1
044: */
045: public final class CommandsPerformanceTest extends BasicPerformanceTest {
046:
047: /**
048: * <p>
049: * Constructs a branch of a context tree. This creates a branch of the given
050: * depth -- remembering the identifiers along the way. This method operates
051: * recursively.
052: * </p>
053: * <p>
054: * TODO This should add a bit of breadth to the tree.
055: * </p>
056: *
057: * @param contextManager
058: * The context manager in which the contexts should be defined;
059: * must not be <code>null</code>.
060: * @param parent
061: * The parent context identifier for the context to be created;
062: * may be <code>null</code>.
063: * @param successors
064: * The number of successors to create. The depth of the branch to
065: * be created. If this number is zero, then a context is created,
066: * but no recursive call is made.
067: * @param activeContextIds
068: * The list of active context identifiers; must not be
069: * <code>null</code>.
070: */
071: private static final void createContext(
072: final ContextManager contextManager, final String parent,
073: final int successors, final List activeContextIds) {
074: final int count = activeContextIds.size();
075: final String contextString = "context" + count;
076: final Context context = contextManager
077: .getContext(contextString);
078: context.define(contextString, contextString, parent);
079: activeContextIds.add(contextString);
080:
081: if (successors == 0) {
082: return;
083: }
084:
085: createContext(contextManager, contextString, successors - 1,
086: activeContextIds);
087: }
088:
089: /**
090: * <p>
091: * Constructs a branch of a scheme tree. This creates a branch of the given
092: * depth -- remembering the schemes along the way. This method operates
093: * recursively.
094: * </p>
095: * <p>
096: * TODO This should add a bit of breadth to the tree.
097: * </p>
098: *
099: * @param bindingManager
100: * The binding manager in which the schemes should be defined;
101: * must not be <code>null</code>.
102: * @param parent
103: * The parent scheme identifier for the scheme to be created; may
104: * be <code>null</code>.
105: * @param successors
106: * The number of successors to create. The depth of the branch to
107: * be created. If this number is zero, then a scheme is created,
108: * but no recursive call is made.
109: * @param schemes
110: * The list of created schemes; must not be <code>null</code>.
111: */
112: private static final void createScheme(
113: final BindingManager bindingManager, final String parent,
114: final int successors, final List schemes) {
115: final int count = schemes.size();
116: final String schemeString = "scheme" + count;
117: final Scheme scheme = bindingManager.getScheme(schemeString);
118: scheme.define(schemeString, schemeString, parent);
119: schemes.add(scheme);
120:
121: if (successors == 0) {
122: return;
123: }
124:
125: createScheme(bindingManager, schemeString, successors - 1,
126: schemes);
127: }
128:
129: /**
130: * The binding manager for the currently running test. <code>null</code>
131: * if no test is running.
132: */
133: private BindingManager bindingManager = null;
134:
135: /**
136: * The command manager for the currently running test. <code>null</code>
137: * if no test is running.
138: */
139: private CommandManager commandManager = null;
140:
141: /**
142: * The context manager for the currently running test. <code>null</code>
143: * if no test is running.
144: */
145: private ContextManager contextManager = null;
146:
147: /**
148: * Constructs an instance of <code>CommandsPerformanceTest</code>.
149: *
150: * @param testName
151: * Test's name.
152: */
153: public CommandsPerformanceTest(final String name) {
154: super (name);
155: }
156:
157: /**
158: * <p>
159: * Sets up a sufficiently complex set of bindings.
160: * </p>
161: * <p>
162: * At the time of writing, Eclipse's key binding set contains about five
163: * hundred bindings. Of these, 140 specify platform information, while only
164: * 5 specify locale information. About 40 are deletion markers. The deepest
165: * point in the context tree is four levels. There are two schemes.
166: * </p>
167: * <p>
168: * The test binding set contains five thousand bindings. About 1400 specify
169: * either locale or platform information. Five hundred are deletion markers.
170: * The deepest point in the context tree is 40 levels. There are twenty
171: * schemes.
172: * </p>
173: * <p>
174: * The depth of the locale and platform tree is the same in both real life
175: * and the test case. It is difficult to imagine why the locale list would
176: * ever be anything but four elements, or why the platform list would ever
177: * be anything but three elements.
178: * </p>
179: *
180: * @throws NotDefinedException
181: * If something went wrong initializing the active scheme.
182: */
183: protected final void doSetUp() throws NotDefinedException,
184: Exception {
185: super .doSetUp();
186:
187: /*
188: * The constants to use in creating the various objects. The platform
189: * locale count must be greater than or equal to the number of deletion
190: * markers. Deletion markers are typically created based on the platform
191: * or locale.
192: */
193: final int contextTreeDepth = 40;
194: final int schemeDepth = 20;
195: final int bindingCount = 5000;
196: final int platformLocaleCount = 1400;
197: final int deletionMarkers = 500;
198: final String currentLocale = Locale.getDefault().toString();
199: final String currentPlatform = SWT.getPlatform();
200:
201: // Set-up a table of modifier keys.
202: final IKeyLookup lookup = KeyLookupFactory.getDefault();
203: final int modifierKeys0 = 0;
204: final int modifierKeys1 = lookup.getAlt();
205: final int modifierKeys2 = lookup.getCommand();
206: final int modifierKeys3 = lookup.getCtrl();
207: final int modifierKeys4 = lookup.getShift();
208: final int modifierKeys5 = lookup.getAlt() | lookup.getCommand();
209: final int modifierKeys6 = lookup.getAlt() | lookup.getCtrl();
210: final int modifierKeys7 = lookup.getAlt() | lookup.getShift();
211: final int modifierKeys8 = lookup.getCommand()
212: | lookup.getCtrl();
213: final int modifierKeys9 = lookup.getCommand()
214: | lookup.getShift();
215: final int modifierKeys10 = lookup.getCtrl() | lookup.getShift();
216: final int modifierKeys11 = lookup.getAlt()
217: | lookup.getCommand() | lookup.getCtrl();
218: final int modifierKeys12 = lookup.getAlt()
219: | lookup.getCommand() | lookup.getShift();
220: final int modifierKeys13 = lookup.getAlt() | lookup.getCtrl()
221: | lookup.getShift();
222: final int modifierKeys14 = lookup.getCommand()
223: | lookup.getCtrl() | lookup.getShift();
224: final int modifierKeys15 = lookup.getAlt()
225: | lookup.getCommand() | lookup.getCtrl()
226: | lookup.getShift();
227: final int[] modifierKeyTable = { modifierKeys0, modifierKeys1,
228: modifierKeys2, modifierKeys3, modifierKeys4,
229: modifierKeys5, modifierKeys6, modifierKeys7,
230: modifierKeys8, modifierKeys9, modifierKeys10,
231: modifierKeys11, modifierKeys12, modifierKeys13,
232: modifierKeys14, modifierKeys15 };
233:
234: // Initialize the command manager.
235: commandManager = new CommandManager();
236:
237: // Initialize the contexts.
238: contextManager = new ContextManager();
239: final List activeContextIds = new ArrayList();
240: createContext(contextManager, null, contextTreeDepth,
241: activeContextIds);
242: contextManager
243: .setActiveContextIds(new HashSet(activeContextIds));
244:
245: // Initialize the schemes.
246: bindingManager = new BindingManager(contextManager,
247: commandManager);
248: final List schemes = new ArrayList();
249: createScheme(bindingManager, null, schemeDepth, schemes);
250: bindingManager.setActiveScheme((Scheme) schemes.get(schemes
251: .size() - 1));
252:
253: // Create the deletion markers.
254: final Binding[] bindings = new Binding[bindingCount];
255: for (int i = 0; i < deletionMarkers; i++) {
256: /*
257: * Set-up the locale and platform. These are based on the numbers
258: * given above.
259: */
260: String locale = null;
261: String platform = null;
262:
263: if (i < platformLocaleCount) {
264: switch (i % 4) {
265: case 0:
266: locale = currentLocale;
267: break;
268: case 1:
269: platform = currentPlatform;
270: break;
271: case 2:
272: locale = "gibberish";
273: break;
274: case 3:
275: platform = "gibberish";
276: break;
277: }
278: }
279:
280: // Build a key sequence.
281: final char character = (char) ('A' + (i % 26));
282: final int modifierKeys = modifierKeyTable[(i / 26)
283: % modifierKeyTable.length];
284: final KeyStroke keyStroke = KeyStroke.getInstance(
285: modifierKeys, character);
286: final KeySequence keySequence = KeySequence
287: .getInstance(keyStroke);
288:
289: // Build the other parameters.
290: final String schemeId = ((Scheme) schemes.get(i
291: % schemes.size())).getId();
292: final String contextId = (String) activeContextIds.get(i
293: % activeContextIds.size());
294: final int type = (i % 2);
295:
296: // Construct the binding.
297: final Binding binding = new KeyBinding(keySequence, null,
298: schemeId, contextId, locale, platform, null, type);
299: bindings[i] = binding;
300: }
301:
302: /*
303: * Now create the regular bindings. By using the same loop structure and
304: * resetting the index to zero, we ensure that the deletion markers will
305: * actually delete something.
306: */
307: for (int i = 0; i < bindingCount - deletionMarkers; i++) {
308: /*
309: * Set-up the locale and platform for those bindings that will not
310: * be used to match the above deletion markers. These are based on
311: * the numbers given above.
312: */
313: String locale = null;
314: String platform = null;
315:
316: if ((i > deletionMarkers) && (i < platformLocaleCount)) {
317: switch (i % 4) {
318: case 0:
319: locale = currentLocale;
320: break;
321: case 1:
322: platform = currentPlatform;
323: break;
324: case 2:
325: locale = "gibberish";
326: break;
327: case 3:
328: platform = "gibberish";
329: break;
330: }
331: }
332:
333: // Build a key sequence.
334: final char character = (char) ('A' + (i % 26));
335: final int modifierKeys = modifierKeyTable[(i / 26)
336: % modifierKeyTable.length];
337: final KeyStroke keyStroke = KeyStroke.getInstance(
338: modifierKeys, character);
339: final KeySequence keySequence = KeySequence
340: .getInstance(keyStroke);
341:
342: // Build the other parameters.
343: final String commandId = "command" + i;
344: final String schemeId = ((Scheme) schemes.get(i
345: % schemes.size())).getId();
346: final String contextId = (String) activeContextIds.get(i
347: % activeContextIds.size());
348: final int type = (i % 2);
349:
350: // Construct the binding.
351: final Command command = commandManager
352: .getCommand(commandId);
353: final ParameterizedCommand parameterizedCommand = new ParameterizedCommand(
354: command, null);
355: final Binding binding = new KeyBinding(keySequence,
356: parameterizedCommand, schemeId, contextId, locale,
357: platform, null, type);
358: bindings[i + deletionMarkers] = binding;
359: }
360: bindingManager.setBindings(bindings);
361: }
362:
363: protected final void doTearDown() throws Exception {
364: bindingManager = null;
365: commandManager = null;
366: contextManager = null;
367: super .doTearDown();
368: }
369:
370: /**
371: * <p>
372: * Tests how long it takes to access the cache if no conditions have
373: * changed. It measures how long it takes to look up the computation from
374: * the cache one million times.
375: * </p>
376: *
377: * @throws ParseException
378: * If "CTRL+F" can't be parsed for some strange reason.
379: */
380: public final void testBindingCacheHitHard() throws ParseException {
381: // Constants
382: final int cacheHits = 1000000;
383: final KeySequence keySequence = KeySequence
384: .getInstance("CTRL+F");
385:
386: // Compute once.
387: bindingManager.getPartialMatches(keySequence);
388:
389: // Time how long it takes to access the cache;
390: startMeasuring();
391: for (int i = 0; i < cacheHits; i++) {
392: bindingManager.getPartialMatches(keySequence);
393: }
394: stopMeasuring();
395: commitMeasurements();
396: assertPerformance();
397: }
398:
399: /**
400: * <p>
401: * Tests how long it takes to access the cache if no conditions have
402: * changed. It measures how long it takes to look up the computation from
403: * the cache one million times. In this test, the look-up is done in reverse --
404: * from command identifier to trigger.
405: * </p>
406: *
407: * @throws ParseException
408: * If "CTRL+F" can't be parsed for some strange reason.
409: */
410: public final void testBindingCacheHitHardReverse()
411: throws ParseException {
412: // Constants
413: final int cacheHits = 1000000;
414: final KeySequence keySequence = KeySequence
415: .getInstance("CTRL+F");
416:
417: // Compute once.
418: bindingManager.getPartialMatches(keySequence);
419:
420: // Time how long it takes to access the cache;
421: startMeasuring();
422: for (int i = 0; i < cacheHits; i++) {
423: bindingManager
424: .getActiveBindingsFor((ParameterizedCommand) null);
425: }
426: stopMeasuring();
427: commitMeasurements();
428: assertPerformance();
429: }
430:
431: /**
432: * <p>
433: * Tests how long it takes to access the cache if the conditions have
434: * changed, but the cache contains a matching entry. It measures how long it
435: * takes to look up the computation from the cache forty thousand times.
436: * </p>
437: *
438: * @throws ParseException
439: * If "CTRL+F" can't be parsed for some strange reason.
440: */
441: public final void testBindingCacheHitSoft() throws ParseException {
442: // Constants
443: final int cacheHits = 10000;
444: final KeySequence keySequence = KeySequence
445: .getInstance("CTRL+F");
446:
447: // Compute once for each context set.
448: final Set contextSet1 = contextManager.getActiveContextIds();
449: bindingManager.getPartialMatches(keySequence);
450: final List contextList = new ArrayList(contextSet1);
451: contextList.remove(contextList.size() - 1);
452: final Set contextSet2 = new HashSet(contextList);
453: contextManager.setActiveContextIds(contextSet2);
454: bindingManager.getPartialMatches(keySequence);
455:
456: // Time how long it takes to access the cache;
457: startMeasuring();
458: for (int i = 0; i < cacheHits; i++) {
459: if ((i % 2) == 0) {
460: contextManager.setActiveContextIds(contextSet1);
461: } else {
462: contextManager.setActiveContextIds(contextSet2);
463: }
464: bindingManager.getPartialMatches(keySequence);
465: }
466: stopMeasuring();
467: commitMeasurements();
468: assertPerformance();
469: }
470:
471: /**
472: * <p>
473: * Tests how long it takes to do a full computation (i.e., a cache miss) on
474: * an exceptionally large set of bindings. The binding set tries to mimick
475: * some of the same properties of a "real" binding set.
476: * </p>
477: *
478: * @throws ParseException
479: * If "CTRL+F" can't be parsed for some strange reason.
480: */
481: public final void testBindingCacheMissLarge() throws ParseException {
482: // Constants
483: final KeySequence keySequence = KeySequence
484: .getInstance("CTRL+F");
485:
486: // Time how long it takes to solve the binding set.
487: startMeasuring();
488: bindingManager.getPartialMatches(keySequence);
489: stopMeasuring();
490: commitMeasurements();
491: assertPerformance();
492: }
493: }
|