From 300c501eb762a4eed531a971a078d2b53d540a0a Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Thu, 13 Aug 2026 14:26:16 +0200 Subject: [PATCH] Measure action registration in KeyBindingServicePerformanceTest CommandsPerformanceTest covers binding resolution, the keystroke to command direction inside JFace. Nothing covered the workbench side, where a part registers its actions with the key binding service while it is being created. AbstractTextEditor does that dozens of times per editor, so it is on the editor open path, and it had no measurement at all. Register and unregister are timed separately and the action count is a parameter, so the growth of the cost with the number of actions is visible rather than only its value at one arbitrary size. Registering 200 actions takes about 0.3ms in total, which puts a number on a path that previously could only be guessed at. --- .../KeyBindingServicePerformanceTest.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/KeyBindingServicePerformanceTest.java diff --git a/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/KeyBindingServicePerformanceTest.java b/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/KeyBindingServicePerformanceTest.java new file mode 100644 index 00000000000..20baa309b38 --- /dev/null +++ b/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/KeyBindingServicePerformanceTest.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2026 Vogella GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.tests.performance; + +import static org.eclipse.ui.tests.harness.util.UITestUtil.openTestWindow; +import static org.eclipse.ui.tests.performance.UIPerformanceTestUtil.exercise; +import static org.eclipse.ui.tests.performance.UIPerformanceTestUtil.reportTimings; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.eclipse.core.commands.Category; +import org.eclipse.core.commands.Command; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.ui.IKeyBindingService; +import org.eclipse.ui.IPageLayout; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.tests.harness.util.CloseTestWindowsRule; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Measures how long it takes a part to register its actions with the key + * binding service, which is what every editor does while it is being created. + *

+ * The action count is a parameter so that the growth of the registration cost + * with the number of actions is visible, rather than only its value at one + * arbitrary size. + */ +@SuppressWarnings("deprecation") // IKeyBindingService is the API under measurement +@RunWith(Parameterized.class) +public class KeyBindingServicePerformanceTest { + + private static final String CATEGORY_ID = "org.eclipse.ui.tests.performance.keyBindingCategory"; + + private static final String COMMAND_ID_PREFIX = "org.eclipse.ui.tests.performance.keyBindingCommand"; + + /** AbstractTextEditor registers on the order of the larger counts here. */ + private static final int MAX_ACTION_COUNT = 200; + + private static final int WARMUP_ROUNDS = 5; + + private static final int MIN_ROUNDS = 20; + + private static final int MAX_ROUNDS = 200; + + private static final int MAX_MEASURE_TIME_MS = 5000; + + @ClassRule + public static final UIPerformanceTestRule uiPerformanceTestRule = new UIPerformanceTestRule(); + + @Rule + public final CloseTestWindowsRule closeTestWindows = new CloseTestWindowsRule(); + + private final int actionCount; + + private IKeyBindingService keyBindingService; + + private List actions; + + @Parameters(name = "{index}: {0} actions") + public static Collection data() { + return Arrays.asList(new Object[][] { { 25 }, { 50 }, { 100 }, { MAX_ACTION_COUNT } }); + } + + public KeyBindingServicePerformanceTest(int actionCount) { + this.actionCount = actionCount; + } + + @Before + public void setUp() throws Exception { + IWorkbenchWindow window = openTestWindow(UIPerformanceTestRule.PERSPECTIVE1); + ICommandService commandService = window.getWorkbench().getService(ICommandService.class); + Category category = commandService.getCategory(CATEGORY_ID); + if (!category.isDefined()) { + category.define("Key Binding Service Performance", null); + } + for (int i = 0; i < actionCount; i++) { + Command command = commandService.getCommand(COMMAND_ID_PREFIX + i); + if (!command.isDefined()) { + command.define("Key Binding Performance Command " + i, null, category); + } + } + + IViewPart view = window.getActivePage().showView(IPageLayout.ID_OUTLINE); + keyBindingService = view.getSite().getKeyBindingService(); + assertNotNull("No key binding service for the test part", keyBindingService); + + actions = new ArrayList<>(actionCount); + for (int i = 0; i < actionCount; i++) { + actions.add(new CommandAction(COMMAND_ID_PREFIX + i)); + } + } + + @Test + public void test() throws Exception { + for (int i = 0; i < WARMUP_ROUNDS; i++) { + registerAll(); + unregisterAll(); + } + + List registerTimes = new ArrayList<>(); + List unregisterTimes = new ArrayList<>(); + + exercise(() -> { + registerTimes.add(registerAll()); + unregisterTimes.add(unregisterAll()); + }, MIN_ROUNDS, MAX_ROUNDS, MAX_MEASURE_TIME_MS); + + reportTimings("KeyBindingService register " + actionCount + " actions", registerTimes); + reportTimings("KeyBindingService unregister " + actionCount + " actions", unregisterTimes); + } + + private long registerAll() { + long before = System.nanoTime(); + for (IAction action : actions) { + keyBindingService.registerAction(action); + } + return System.nanoTime() - before; + } + + private long unregisterAll() { + long before = System.nanoTime(); + for (IAction action : actions) { + keyBindingService.unregisterAction(action); + } + return System.nanoTime() - before; + } + + /** Distinct command id per action, as a part's actions have. */ + private static final class CommandAction extends Action { + CommandAction(String commandId) { + setId(commandId); + setActionDefinitionId(commandId); + } + } +}