diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..0cd87fae44 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ +# JDK Mission Control (JMC) - Agent Instructions + +## Project Overview +- Java Mission Control (JMC) is an OpenJDK project with core libraries and an Eclipse RCP UI. +- Main areas: + - `core/` - Headless Java 17 libraries (can run standalone, read recordings from JDK 7+) + - `org.openjdk.jmc.common` - Core APIs and utilities + - `org.openjdk.jmc.flightrecorder` - JFR parsing and analysis + - `org.openjdk.jmc.flightrecorder.rules` - Rule framework + - `org.openjdk.jmc.flightrecorder.rules.jdk` - JDK-specific analysis rules + - `org.openjdk.jmc.testlib` - Test utilities + - `application/` - Eclipse RCP UI components (Java 21) + - `agent/` - JFR Agent for bytecode instrumentation + +## Build & Test +- `./build.sh --installCore` - Install core libraries +- `./build.sh --packageJmc` - Package full JMC application +- `./build.sh --test` - Run standard tests +- `./build.sh --testUi` - Run UI tests +- `mvn verify -Dtest.includes=**/*TestName*` - Run specific tests +- `mvn verify -Dspotbugs.skip=true` - Skip SpotBugs during verification + +### Build Scripts +- `scripts/runcoretests.sh` - Run core library tests +- `scripts/runapptests.sh` - Run application tests +- `scripts/runagenttests.sh` - Run agent tests +- `scripts/startp2.sh` - Start local P2 repository for application build + +### Eclipse Platform Profiles +Build against specific Eclipse platform versions using Maven profiles: +```bash +mvn verify -P 2024-12 +``` + +## Java Version & Toolchains +- **Java Version**: JDK 21 for application build, JDK 17 for core components +- Requires both JDK 17 and JDK 21 configured in `~/.m2/toolchains.xml`. + +Example `~/.m2/toolchains.xml`: +```xml + + + jdk + + JavaSE-17 + 17 + + + /path/to/jdk17 + + + + jdk + + JavaSE-21 + 21 + + + /path/to/jdk21 + + + +``` + +## Code Style & Formatting +- Java formatting follows the Eclipse profile in `configuration/ide/eclipse/formatting/formatting.xml`. +- Java cleanup rules are in `configuration/ide/eclipse/formatting/clean-up-with-formatting.xml`. +- JavaScript formatting follows `configuration/ide/eclipse/formatting/formattingjs.xml`. +- Key formatter settings (from the Eclipse profiles): + - Tabs for indentation (`tabulation.char=tab`, size 4). + - Line length 120 (`lineSplit=120`). + - Javadoc/comment line length 100 (`comment.line_length=100`). +- Avoid star imports; remove unused imports. +- **Naming**: Follow Eclipse/Java standard conventions, be consistent. + +## Logging / Error Handling +- Use `Level.FINE` for expected exceptions (e.g., `CancellationException`). +- Use `Level.SEVERE` for unexpected failures. +- Filter cancellations from error logs during model rebuilding. +- Check SpotBugs exceptions for guidance. + +## Commit Messages +- Format: `JIRA_NUMBER: Commit message` (example: `6789: Fix bug`). +- Issue tracker: https://bugs.openjdk.org/projects/JMC/issues + +## Copyright Headers +- All modified files must have the current year in the copyright header. +- CI validates this via `scripts/checkcopyrightyear.sh`. +- Affected file types: `*.java`, `*.htm`, `pom.xml`, `*.properties`. + +## Static Analysis +- **Checkstyle**: Enforces no star imports, no redundant/unused imports (`configuration/checkstyle/checkstyle.xml`). +- **SpotBugs**: Static bug detection with exclusions in `configuration/spotbugs/spotbugs-exclude.xml`. +- **Spotless**: Code formatting enforced during Maven validate phase. + +## Internationalization (i18n) +- Core modules: Place `messages.properties`, `messages_ja.properties`, `messages_zh.properties` in `internal` packages. +- Application modules: Use separate l10n plugin modules (e.g., `org.openjdk.jmc.*.ja`, `org.openjdk.jmc.*.zh_CN`). +- Access strings via `Messages.getString(Messages.MESSAGE_KEY)`. + +## Writing Flight Recorder Rules +Rules analyze JFR recordings and provide recommendations to users. + +### Creating a New Rule +1. Extend `AbstractRule`: +```java +public class MyRule extends AbstractRule { + public MyRule() { + super("MyRuleId", Messages.getString(Messages.MY_RULE_NAME), + JfrRuleTopics.TOPIC_NAME, CONFIGURATION_ATTRIBUTES, + RESULT_ATTRIBUTES, Collections.emptyMap()); + } + + @Override + protected IResult getResult(IItemCollection items, IPreferenceValueProvider vp, + IResultValueProvider rp) { + // Analyze items using ItemFilters, Aggregators, etc. + return ResultBuilder.createFor(this, vp) + .setSeverity(Severity.get(score)) + .setSummary("Summary message") + .setExplanation("Detailed explanation") + .build(); + } +} +``` + +2. Register the rule in `META-INF/services/org.openjdk.jmc.flightrecorder.rules.IRule`: +``` +org.openjdk.jmc.flightrecorder.rules.jdk.mypackage.MyRule +``` + +### Core Item Processing APIs +- `IItemCollection` - Collection of JFR events to query +- `IItemFilter` - Filter events (use `ItemFilters` factory) +- `IAggregator` - Aggregate values (use `Aggregators` factory) +- `IAttribute` - Access event attributes (see `JdkAttributes`) +- `RulesToolkit` - Utility methods for rule implementations + +## Testing +- Unit tests in `src/test/java` using JUnit 4. +- Test resources in `src/test/resources`. +- JDP multicast tests are automatically skipped on macOS. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc94eb7ef0..5235d2c186 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ message should be "6789: Commit message". The mailing list for JDK Mission Control is `jmc-dev@openjdk.java.net`. See for instructions -on how to subscribe of if you want to read the archives. +on how to subscribe or if you want to read the archives. ## Issues @@ -45,4 +45,3 @@ If not, ask for an invite in the jmc-dev mailing list. If you have a question or need help, please send an email to our mailing list `jmc-dev@openjdk.java.net` or stop by the JMC slack channel. - diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index bd4ccad0aa..9ae18cd268 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,4 +1,4 @@ -#Contributors +# Contributors A lot of people have been involved with Mission Control over the years. This document is a thank you to everyone who has ever contributed to @@ -31,4 +31,4 @@ List of contributors, approximately in order of time spent on project: * Suchita Chaturvedi * Guru Hb * Sharath Ballal -* Peter Boström \ No newline at end of file +* Peter Boström diff --git a/README.md b/README.md index fc861c571b..c3227b9783 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mission Control is an open source production time profiling and diagnostics tool for Java. -Builds of Mission Control can currently be found in the Oracle JDK on supported platforms and in the Eclipse marketplace. +Builds of Mission Control can currently be found in the Oracle JDK on supported platforms and in the Eclipse Marketplace. For more information on Mission Control, see https://www.oracle.com/missioncontrol. @@ -217,7 +217,7 @@ Prerequisites for building Mission Control: 2. Install a JDK 21 distribution and make sure that it too is declared in the local maven toolchain. -3. Install Maven (version 3.5.x. or above) +3. Install Maven (version 3.5.x or above) On Linux or macOS you can use the `build.sh` script to build JMC: ``` @@ -268,7 +268,7 @@ If maven reports a toolchain error, e.g. : [ERROR] Please make sure you define the required toolchains in your ~/.m2/toolchains.xml file. ``` -Create or amend the local maven toolchain file by pointing to the right/any JDK 17. +Create or amend the local maven toolchain file by pointing to any JDK 17 installation.
~/.m2/toolchains.xml @@ -346,7 +346,7 @@ mvn verify -P uitests -Dspotbugs.skip=true ``` ## Filtering Test Runs -Aside from the from the simple -test Maven flag test classes that should be run/not run can be specified by means of the system properties "test.includes" and/or "test.excludes". Multiple patterns can be specified by comma separation. +Aside from the simple -test Maven flag, test classes that should be run/not run can be specified by means of the system properties "test.includes" and/or "test.excludes". Multiple patterns can be specified by comma separation. For example: @@ -422,7 +422,7 @@ There is another update site for the Eclipse plug-ins, providing plug-ins for ru application/org.openjdk.jmc.updatesite.ide/target/ ``` -To install it into Eclipe, simply open Eclipse and select Help | Install New Software... In the dialog, click Add... and then click the Archive... button. Select the built update site, e.g. +To install it into Eclipse, simply open Eclipse and select Help | Install New Software... In the dialog, click Add... and then click the Archive... button. Select the built update site, e.g. ```bash application/org.openjdk.jmc.updatesite.ide/target/org.openjdk.jmc.updatesite.ide-10.0.0-SNAPSHOT.zip diff --git a/application/coverage/pom.xml b/application/coverage/pom.xml index 3b0a7b32a7..b86a865921 100644 --- a/application/coverage/pom.xml +++ b/application/coverage/pom.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + diff --git a/application/org.openjdk.jmc.ui.websocket/pom.xml b/application/org.openjdk.jmc.ui.websocket/pom.xml new file mode 100644 index 0000000000..1bca6528d9 --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.openjdk.jmc + missioncontrol.application + ${revision}${changelist} + + org.openjdk.jmc.ui.websocket + eclipse-plugin + + ${project.basedir}/../../configuration + + diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/websocket/WebsocketServer.java b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/MCWebsocketServer.java similarity index 72% rename from application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/websocket/WebsocketServer.java rename to application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/MCWebsocketServer.java index f8356e7922..d49334a4cc 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/websocket/WebsocketServer.java +++ b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/MCWebsocketServer.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2021, 2025, Datadog, Inc. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Datadog, Inc. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -31,9 +31,10 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.openjdk.jmc.flightrecorder.ui.websocket; +package org.openjdk.jmc.ui.websocket; import java.io.IOException; +import java.nio.channels.ClosedChannelException; import java.time.Duration; import java.util.HashMap; import java.util.List; @@ -45,13 +46,12 @@ import java.util.logging.Level; import java.util.stream.Collectors; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.ee9.servlet.ServletContextHandler; import org.eclipse.jetty.ee9.websocket.api.Session; import org.eclipse.jetty.ee9.websocket.api.WebSocketAdapter; import org.eclipse.jetty.ee9.websocket.server.config.JettyWebSocketServletContainerInitializer; -import org.eclipse.jetty.ee9.websocket.servlet.WebSocketUpgradeFilter; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; import org.openjdk.jmc.common.item.IItemCollection; import org.openjdk.jmc.flightrecorder.serializers.dot.DotSerializer; import org.openjdk.jmc.flightrecorder.serializers.json.FlameGraphJsonSerializer; @@ -60,14 +60,8 @@ import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator.FrameCategorization; import org.openjdk.jmc.flightrecorder.stacktrace.graph.StacktraceGraphModel; import org.openjdk.jmc.flightrecorder.stacktrace.tree.StacktraceTreeModel; -import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; - -public class WebsocketServer { - - private static int MAX_MESSAGE_SIZE = 1024 * 1024 * 1024; - private static int IDLE_TIMEOUT_MINUTES = 5; - private final int port; +public class MCWebsocketServer { private Server server; private List handlers = new CopyOnWriteArrayList<>(); private List treeHandlers = new CopyOnWriteArrayList<>(); @@ -75,16 +69,11 @@ public class WebsocketServer { private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private IItemCollection currentSelection = null; - public WebsocketServer(int port) { - this.port = port; - executorService.execute(() -> startServer()); + public MCWebsocketServer(int port) { + executorService.execute(() -> startServer(port)); } - public int getPort() { - return port; - } - - private void startServer() { + public void startServer(int port) { server = new Server(); ServerConnector connector = new ServerConnector(server); connector.setHost("127.0.0.1"); @@ -96,24 +85,22 @@ private void startServer() { server.setHandler(context); JettyWebSocketServletContainerInitializer.configure(context, (servletContext, container) -> { - container.setMaxBinaryMessageSize(MAX_MESSAGE_SIZE); - container.setIdleTimeout(Duration.ofMinutes(IDLE_TIMEOUT_MINUTES)); + container.setMaxBinaryMessageSize(Long.MAX_VALUE); + container.setIdleTimeout(Duration.ofMinutes(Long.MAX_VALUE)); container.addMapping("/events/*", (req, resp) -> { - // try to send the current selection when the client connects - // for simplicity, we serialise for every new connection - String eventsJson = WebsocketServer.toEventsJsonString(currentSelection); + String eventsJson = MCWebsocketServer.toEventsJsonString(currentSelection); WebsocketConnectionHandler handler = new WebsocketConnectionHandler(eventsJson); handlers.add(handler); return handler; }); container.addMapping("/tree/*", (req, resp) -> { - String treeJson = WebsocketServer.toTreeModelJsonString(currentSelection); + String treeJson = MCWebsocketServer.toTreeModelJsonString(currentSelection); WebsocketConnectionHandler handler = new WebsocketConnectionHandler(treeJson); treeHandlers.add(handler); return handler; }); container.addMapping("/graph/*", (req, resp) -> { - String dot = WebsocketServer.toGraphModelDotString(currentSelection); + String dot = MCWebsocketServer.toGraphModelDotString(currentSelection); WebsocketConnectionHandler handler = new WebsocketConnectionHandler(dot); graphHandlers.add(handler); return handler; @@ -121,13 +108,9 @@ private void startServer() { }); try { - WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); - FlightRecorderUI.getDefault().getLogger().log(Level.INFO, - "Starting websocket server listening on port " + port); server.start(); - server.join(); } catch (Exception e) { - FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to start websocket server", e); + WebsocketPlugin.getLogger().log(Level.SEVERE, "Failed to start websocket server", e); } } @@ -139,15 +122,15 @@ public void notifyAll(IItemCollection events) { } private void notifyAllEventHandlers(IItemCollection events) { - handlers = notifyAllHandlers(events, handlers, WebsocketServer::toEventsJsonString); + handlers = notifyAllHandlers(events, handlers, MCWebsocketServer::toEventsJsonString); } private void notifyAllGraphHandlers(IItemCollection events) { - graphHandlers = notifyAllHandlers(events, graphHandlers, WebsocketServer::toGraphModelDotString); + graphHandlers = notifyAllHandlers(events, graphHandlers, MCWebsocketServer::toGraphModelDotString); } private void notifyAllTreeHandlers(IItemCollection events) { - treeHandlers = notifyAllHandlers(events, treeHandlers, WebsocketServer::toTreeModelJsonString); + treeHandlers = notifyAllHandlers(events, treeHandlers, MCWebsocketServer::toTreeModelJsonString); } private static String toEventsJsonString(IItemCollection items) { @@ -187,15 +170,8 @@ private List notifyAllHandlers( return handlers; } - public void shutdown() { - try { - FlightRecorderUI.getDefault().getLogger().log(Level.INFO, - "Stopping websocket server listening on port " + port); - server.stop(); - // TODO: see if we need to cleanup executor service and thread - } catch (Exception e) { - FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to stop websocket server", e); - } + public void shutdown() throws Exception { + server.stop(); } private static class WebsocketConnectionHandler extends WebSocketAdapter { @@ -207,12 +183,12 @@ private static class WebsocketConnectionHandler extends WebSocketAdapter { public void sendMessage(String message) { if (getSession() != null && isConnected()) { - FlightRecorderUI.getDefault().getLogger().log(Level.INFO, + WebsocketPlugin.getLogger().log(Level.INFO, "Sending message to " + getSession().getRemoteAddress().toString()); try { getSession().getRemote().sendString(message); } catch (IOException e) { - FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to send websocket message", e); + WebsocketPlugin.getLogger().log(Level.SEVERE, "Failed to send websocket message", e); } } } @@ -220,15 +196,14 @@ public void sendMessage(String message) { @Override public void onWebSocketConnect(Session sess) { super.onWebSocketConnect(sess); - FlightRecorderUI.getDefault().getLogger().log(Level.INFO, - "Socket connected to " + sess.getRemoteAddress().toString()); + WebsocketPlugin.getLogger().log(Level.INFO, "Socket connected to " + sess.getRemoteAddress().toString()); try { if (firstMessage != null) { getSession().getRemote().sendString(firstMessage); firstMessage = null; } } catch (IOException e) { - FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to show outline view", e); + WebsocketPlugin.getLogger().log(Level.SEVERE, "Failed to show outline view", e); } } @@ -240,16 +215,18 @@ public void onWebSocketText(String message) { @Override public void onWebSocketClose(int statusCode, String reason) { super.onWebSocketClose(statusCode, reason); - FlightRecorderUI.getDefault().getLogger().log(Level.INFO, "Socket closed: [" + statusCode + "] " + reason); + WebsocketPlugin.getLogger().log(Level.INFO, "Socket closed: [" + statusCode + "] " + reason); } @Override public void onWebSocketError(Throwable cause) { super.onWebSocketError(cause); - if (cause.getCause() instanceof TimeoutException) { - FlightRecorderUI.getDefault().getLogger().log(Level.INFO, "Websocket timed out"); + if (cause instanceof TimeoutException) { + WebsocketPlugin.getLogger().log(Level.INFO, "Websocket timed out"); + } else if (cause instanceof ClosedChannelException) { + WebsocketPlugin.getLogger().log(Level.INFO, "Websocket channel has closed"); } else { - FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Websocket error", cause); + WebsocketPlugin.getLogger().log(Level.SEVERE, "Websocket error", cause); } } } diff --git a/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/WebsocketPlugin.java b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/WebsocketPlugin.java new file mode 100644 index 0000000000..ac7a9ab1a5 --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/WebsocketPlugin.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.ui.websocket; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.IStartup; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.openjdk.jmc.common.item.IItemCollection; +import org.openjdk.jmc.ui.websocket.preferences.PreferenceConstants; +import org.osgi.framework.BundleContext; + +public class WebsocketPlugin extends AbstractUIPlugin implements IStartup { + + public final static String PLUGIN_ID = "org.openjdk.jmc.ui.websocket"; //$NON-NLS-1$ + private static final Logger LOGGER = Logger.getLogger(PLUGIN_ID); + + private static WebsocketPlugin plugin; + private MCWebsocketServer server; + + public WebsocketPlugin() { + } + + public static WebsocketPlugin getDefault() { + return plugin; + } + + public static Logger getLogger() { + return LOGGER; + } + + public void start(BundleContext bundleContext) throws Exception { + super.start(bundleContext); + this.getPreferenceStore().addPropertyChangeListener(preferenceChangeListener); + plugin = this; + startServer(getCryostatPort()); + LOGGER.log(Level.INFO, "JMC Websocket Server is live!"); + } + + public void stop(BundleContext bundleContext) throws Exception { + if (server != null) { + server.shutdown(); + server = null; + } + plugin = null; + super.stop(bundleContext); + } + + public void notifyAll(IItemCollection events) { + if (server != null) { + server.notifyAll(events); + } + } + + private void startServer(int port) { + if (getServerEnabled()) { + server = new MCWebsocketServer(port); + } + } + + private void stopServer() { + if (server != null) { + try { + server.shutdown(); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error shutting down the Jetty WebSocket server", e); + } + } + } + + private IPropertyChangeListener preferenceChangeListener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(PreferenceConstants.P_SERVER_ENABLED)) { + if (getServerEnabled()) { + startServer(getCryostatPort()); + } else { + stopServer(); + } + } + if (event.getProperty().equals(PreferenceConstants.P_SERVER_PORT) && getServerEnabled()) { + stopServer(); + startServer(getCryostatPort()); + } + } + }; + + private int getCryostatPort() { + return this.getPreferenceStore().getInt(PreferenceConstants.P_SERVER_PORT); + } + + public boolean getServerEnabled() { + return this.getPreferenceStore().getBoolean(PreferenceConstants.P_SERVER_ENABLED); + } + + @Override + public void earlyStartup() { + // do nothing, we're only implementing IStartup to force a load of the plugin + } +} diff --git a/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/Messages.java b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/Messages.java new file mode 100644 index 0000000000..60b35bc07c --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/Messages.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.ui.websocket.preferences; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.openjdk.jmc.ui.websocket.preferences.messages"; //$NON-NLS-1$ + + public static String WebsocketPreferencePage_DESCRIPTION; + public static String WebsocketPreferencePage_ENABLE; + public static String WebsocketPreferencePage_PORT; + + static { + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/PreferenceConstants.java b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/PreferenceConstants.java new file mode 100644 index 0000000000..ed7ef192a2 --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/PreferenceConstants.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.ui.websocket.preferences; + +public class PreferenceConstants { + public static final String P_SERVER_ENABLED = "websocket.server.enabled"; + public static final String P_SERVER_PORT = "websocket.server.port"; + public static final boolean DEFAULT_ENABLED = false; + public static final int DEFAULT_PORT = 8029; +} diff --git a/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/PreferenceInitializer.java b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/PreferenceInitializer.java new file mode 100644 index 0000000000..fb8335ff2e --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/PreferenceInitializer.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.ui.websocket.preferences; + +import org.openjdk.jmc.ui.websocket.WebsocketPlugin; +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; + +public class PreferenceInitializer extends AbstractPreferenceInitializer { + @Override + public void initializeDefaultPreferences() { + IPreferenceStore store = WebsocketPlugin.getDefault().getPreferenceStore(); + store.setDefault(PreferenceConstants.P_SERVER_PORT, PreferenceConstants.DEFAULT_PORT); + store.setDefault(PreferenceConstants.P_SERVER_ENABLED, PreferenceConstants.DEFAULT_ENABLED); + } +} diff --git a/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/WebsocketPreferencePage.java b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/WebsocketPreferencePage.java new file mode 100644 index 0000000000..b7fc4ccf44 --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/src/main/java/org/openjdk/jmc/ui/websocket/preferences/WebsocketPreferencePage.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.ui.websocket.preferences; + +import org.openjdk.jmc.ui.websocket.WebsocketPlugin; +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +public class WebsocketPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + + private IntegerFieldEditor portField; + + public WebsocketPreferencePage() { + super(GRID); + setPreferenceStore(WebsocketPlugin.getDefault().getPreferenceStore()); + setDescription(Messages.WebsocketPreferencePage_DESCRIPTION); + } + + @Override + public void init(IWorkbench workbench) { + } + + @Override + protected void createFieldEditors() { + addField(new BooleanFieldEditor(PreferenceConstants.P_SERVER_ENABLED, Messages.WebsocketPreferencePage_ENABLE, + getFieldEditorParent())); + portField = new IntegerFieldEditor(PreferenceConstants.P_SERVER_PORT, Messages.WebsocketPreferencePage_PORT, + getFieldEditorParent()); + addField(portField); + enableWebsocketFields(isWebsocketPluginEnabled()); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(FieldEditor.VALUE)) { + FieldEditor editor = (FieldEditor) event.getSource(); + if (PreferenceConstants.P_SERVER_ENABLED.equals(editor.getPreferenceName())) { + enableWebsocketFields((boolean) event.getNewValue()); + } + } + } + + private boolean isWebsocketPluginEnabled() { + return WebsocketPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.P_SERVER_ENABLED); + } + + private void enableWebsocketFields(boolean enable) { + portField.setEnabled(enable, getFieldEditorParent()); + } +} diff --git a/application/org.openjdk.jmc.ui.websocket/src/main/resources/org/openjdk/jmc/ui/websocket/preferences/messages.properties b/application/org.openjdk.jmc.ui.websocket/src/main/resources/org/openjdk/jmc/ui/websocket/preferences/messages.properties new file mode 100644 index 0000000000..19710c4b5d --- /dev/null +++ b/application/org.openjdk.jmc.ui.websocket/src/main/resources/org/openjdk/jmc/ui/websocket/preferences/messages.properties @@ -0,0 +1,3 @@ +WebsocketPreferencePage_DESCRIPTION=Settings for the JMC Websocket Server +WebsocketPreferencePage_ENABLE=Enable +WebsocketPreferencePage_PORT=WebSocket Port for JFR transfer: diff --git a/application/pom.xml b/application/pom.xml index fbd9cc8b2d..f96e309eeb 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ + + diff --git a/core/license/LICENSE.txt b/core/license/LICENSE.txt index 0134be2728..a84a6f60fe 100644 --- a/core/license/LICENSE.txt +++ b/core/license/LICENSE.txt @@ -1,6 +1,6 @@ Oracle provides this software under a dual license, either the UPL or the BSD: -Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. The Universal Permissive License (UPL), Version 1.0 @@ -40,7 +40,7 @@ SOFTWARE. The BSD 3-Clause License (BSD): -Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/core/license/THIRD_PARTY_LICENSES.txt b/core/license/THIRD_PARTY_LICENSES.txt index 6c78f0f145..af3ffa0a02 100644 --- a/core/license/THIRD_PARTY_LICENSES.txt +++ b/core/license/THIRD_PARTY_LICENSES.txt @@ -47,7 +47,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================================== -## org.lz4:lz4-java@1.8.0 (Apache-2.0) +## at.yawk.lz4:lz4-java@1.10.2 (Apache-2.0) URL for License – http://opensource.org/licenses/Apache-2.0 diff --git a/core/org.openjdk.jmc.common/pom.xml b/core/org.openjdk.jmc.common/pom.xml index a80a8a1b22..157589e1ff 100644 --- a/core/org.openjdk.jmc.common/pom.xml +++ b/core/org.openjdk.jmc.common/pom.xml @@ -1,6 +1,6 @@ - 2.0.3 - 2.0.2 - 2.1.3 + 2.0.5 + 2.0.3 + 2.1.4 1.0.3 1.2.3 - 1.8.0 + 1.10.2 2.2.2 2.0.0 12.1.0 @@ -104,7 +104,7 @@ org.owasp.encoder:encoder:${owasp.encoder.version} - org.lz4:lz4-java:${lz4.version} + at.yawk.lz4:lz4-java:${lz4.version} org.hdrhistogram:HdrHistogram:${hdrhistogram.version} diff --git a/releng/tools/README.md b/releng/tools/README.md index 79136b3ef3..43d00a2e15 100644 --- a/releng/tools/README.md +++ b/releng/tools/README.md @@ -1,2 +1,2 @@ -#Releng Tools -This is the location to put release engineering related tools. \ No newline at end of file +# Releng Tools +This is the location to put release engineering related tools.