diff --git a/.gitignore b/.gitignore index cd3f85dfba..60a6a1d3aa 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,6 @@ core/coverage/coverage-report application/coverage/coverage-report # Ignore the fetched JS libraries -application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/jslibs/ application/org.openjdk.jmc.flightrecorder.graphview/src/main/resources/jslibs/ application/org.openjdk.jmc.flightrecorder.heatmap/src/main/resources/jslibs/ application/org.openjdk.jmc.flightrecorder.dependencyview/src/main/resources/jslibs/ diff --git a/agent/pom.xml b/agent/pom.xml index 0e5c6e9257..b7cbefe5d2 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -31,26 +31,25 @@ 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. --> - 4.0.0 org.openjdk.jmc - ${revision}${changelist} agent + ${revision}${changelist} jar JDK Mission Control Agent The JMC agent allows users to add JFR instrumentation declaratively to a running program. The agent can, for example, be used to add flight recorder events to third party code for which the source is not available. - http://jdk.java.net/jmc + https://jdk.java.net/jmc Universal Permissive License Version 1.0 http://oss.oracle.com/licenses/upl repo - Copyright (c) 2018, 2022, Oracle and/or its affiliates. Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. + Copyright (c) 2018, 2023, Oracle and/or its affiliates. Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. @@ -59,28 +58,41 @@ JIRA - https://bugs.openjdk.java.net/projects/JMC/issues + https://bugs.openjdk.org/projects/JMC/issues jmc dev - http://mail.openjdk.java.net/mailman/listinfo/jmc-dev - http://mail.openjdk.java.net/mailman/listinfo/jmc-dev - http://mail.openjdk.java.net/pipermail/jmc-dev/ + https://mail.openjdk.org/mailman/listinfo/jmc-dev + https://mail.openjdk.org/mailman/listinfo/jmc-dev + https://mail.openjdk.org/pipermail/jmc-dev/ + 1.0.1 -SNAPSHOT + UTF-8 + UTF-8 11 11 + ${project.basedir}/../configuration + + 3.3.0 + 2.34.0 + 3.2.0 + 3.3.1 + 3.11.0 + 3.1.0 + 3.3.0 + 3.4.1 + 3.1.2 + 3.1.1 + 1.6.13 + 3.1.0 + 8.0.1 4.13.2 - 3.2.2 - ${project.basedir}/../configuration/checkstyle/checkstyle.xml - UTF-8 - 1.26.0 - ${basedir}/../configuration/ide/eclipse/formatting/formatting.xml ${scmConnection} @@ -123,12 +135,12 @@ org.apache.maven.plugins maven-clean-plugin - 3.2.0 + ${maven.clean.version} org.apache.maven.plugins maven-resources-plugin - 3.2.0 + ${maven.resources.version} copy-resources @@ -153,12 +165,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + ${maven.compiler.version} org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M7 + ${maven.surefire.version} --add-opens java.base/jdk.internal.misc=ALL-UNNAMED -XX:+FlightRecorder @@ -174,12 +186,12 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.2 + ${maven.jar.version} org.apache.maven.plugins maven-shade-plugin - 3.3.0 + ${maven.shade.version} @@ -226,7 +238,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.0.0-M7 + ${maven.failsafe.version} test-permissions @@ -312,12 +324,12 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + ${maven.install.version} org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + ${nexus.staging.plugin.version} true true @@ -327,17 +339,17 @@ org.apache.maven.plugins maven-checkstyle-plugin ${maven.checkstyle.version} + + ${jmc.config.path}/checkstyle/checkstyle.xml + true + UTF-8 + true + true + validate validate - - ${checkstyle.config.path} - true - UTF-8 - true - true - check @@ -347,7 +359,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + ${maven.gpg.version} @@ -372,11 +384,20 @@ - ${spotless.config.path} + ${jmc.config.path}/ide/eclipse/formatting/formatting.xml 4.8.0 + + + check + validate + + check + + + diff --git a/application/org.openjdk.jmc.feature.flightrecorder/feature.xml b/application/org.openjdk.jmc.feature.flightrecorder/feature.xml index 8f402d408e..21baaf5224 100644 --- a/application/org.openjdk.jmc.feature.flightrecorder/feature.xml +++ b/application/org.openjdk.jmc.feature.flightrecorder/feature.xml @@ -1,6 +1,6 @@ + + + + + + + + + diff --git a/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/map@2x.png b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/map@2x.png new file mode 100644 index 0000000000..a813e45fcc Binary files /dev/null and b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/map@2x.png differ diff --git a/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom.png b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom.png new file mode 100644 index 0000000000..b687df49ed Binary files /dev/null and b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom.png differ diff --git a/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom.svg b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom.svg new file mode 100644 index 0000000000..36932f20a1 --- /dev/null +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom.svg @@ -0,0 +1,65 @@ + + + + + + + + + diff --git a/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom@2x.png b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom@2x.png new file mode 100644 index 0000000000..f3ca8d13ee Binary files /dev/null and b/application/org.openjdk.jmc.flightrecorder.flamegraph/icons/reset-zoom@2x.png differ diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/plugin.properties b/application/org.openjdk.jmc.flightrecorder.flamegraph/plugin.properties similarity index 91% rename from application/org.openjdk.jmc.flightrecorder.flameview/plugin.properties rename to application/org.openjdk.jmc.flightrecorder.flamegraph/plugin.properties index c7ef7cf95b..982b541335 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/plugin.properties +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/plugin.properties @@ -1,6 +1,6 @@ # -# Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. -# Copyright (c) 2019, Datadog, Inc. All rights reserved. +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, Datadog, Inc. All rights reserved. # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -31,4 +31,4 @@ # 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. # -FLAME_VIEW_NAME=Flame View +FLAME_GRAPH_VIEW_NAME=Flame Graph diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/plugin.xml b/application/org.openjdk.jmc.flightrecorder.flamegraph/plugin.xml similarity index 86% rename from application/org.openjdk.jmc.flightrecorder.flameview/plugin.xml rename to application/org.openjdk.jmc.flightrecorder.flamegraph/plugin.xml index 450ed8caa5..220d2d7122 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/plugin.xml +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/plugin.xml @@ -1,7 +1,7 @@ + + 4.0.0 + + org.openjdk.jmc + missioncontrol.application + ${revision}${changelist} + + org.openjdk.jmc.flightrecorder.flamegraph + eclipse-plugin + + + ${basedir}/../../configuration/ide/eclipse/formatting/formatting.xml + ${basedir}/../../configuration/ide/eclipse/formatting/formattingjs.xml + + diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/FlameviewImages.java b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/FlamegraphImages.java similarity index 84% rename from application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/FlameviewImages.java rename to application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/FlamegraphImages.java index d1e1059acc..e7d7c909f6 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/FlameviewImages.java +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/FlamegraphImages.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, 2020, Datadog, Inc. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Datadog, Inc. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -31,14 +31,14 @@ * 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.flameview; +package org.openjdk.jmc.flightrecorder.flamegraph; /** * Class that holds reference to the icons and images used by the Flight Recorder plug-in */ -public final class FlameviewImages { - +public final class FlamegraphImages { public static final String ICON_FLAME_FLIP = "flameflip.png"; //$NON-NLS-1$ public static final String ICON_ICICLE_FLIP = "icicleflip.png"; //$NON-NLS-1$ - + public static final String ICON_RESET_ZOOM = "reset-zoom.png"; //$NON-NLS-1$ + public static final String ICON_MINIMAP = "map.png"; //$NON-NLS-1$ } diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/Messages.java b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/Messages.java similarity index 56% rename from application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/Messages.java rename to application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/Messages.java index e56e98e187..c781c74943 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/Messages.java +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/Messages.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, 2020, Datadog, Inc. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Datadog, Inc. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -31,13 +31,13 @@ * 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.flameview; +package org.openjdk.jmc.flightrecorder.flamegraph; import java.util.MissingResourceException; import java.util.ResourceBundle; public class Messages { - private static final String BUNDLE_NAME = "org.openjdk.jmc.flightrecorder.flameview.messages"; //$NON-NLS-1$ + private static final String BUNDLE_NAME = "org.openjdk.jmc.flightrecorder.flamegraph.messages"; //$NON-NLS-1$ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); @@ -48,22 +48,8 @@ public class Messages { public static final String FLAMEVIEW_SAVE_FLAME_GRAPH_AS = "FLAMEVIEW_SAVE_FLAME_GRAPH_AS"; //$NON-NLS-1$ public static final String FLAMEVIEW_JPEG_IMAGE = "FLAMEVIEW_JPEG_IMAGE"; //$NON-NLS-1$ public static final String FLAMEVIEW_PNG_IMAGE = "FLAMEVIEW_PNG_IMAGE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_STACKTRACE_NOT_AVAILABLE = "FLAMEVIEW_SELECT_STACKTRACE_NOT_AVAILABLE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_ROOT_NODE_EVENT = "FLAMEVIEW_SELECT_ROOT_NODE_EVENT"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_ROOT_NODE_EVENTS = "FLAMEVIEW_SELECT_ROOT_NODE_EVENTS"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_ROOT_NODE_TYPE = "FLAMEVIEW_SELECT_ROOT_NODE_TYPE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_ROOT_NODE_TYPES = "FLAMEVIEW_SELECT_ROOT_NODE_TYPES"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_ROOT_NODE = "FLAMEVIEW_SELECT_ROOT_NODE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_TITLE_EVENT_MORE_DELIMITER = "FLAMEVIEW_SELECT_TITLE_EVENT_MORE_DELIMITER"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_TITLE_EVENT_PATTERN = "FLAMEVIEW_SELECT_TITLE_EVENT_PATTERN"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TABLE_EVENT_PATTERN = "FLAMEVIEW_SELECT_HTML_TABLE_EVENT_PATTERN"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TABLE_EVENT_REST_PATTERN = "FLAMEVIEW_SELECT_HTML_TABLE_EVENT_REST_PATTERN"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_MORE = "FLAMEVIEW_SELECT_HTML_MORE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TABLE_COUNT = "FLAMEVIEW_SELECT_HTML_TABLE_COUNT"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE = "FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE = "FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES = "FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES"; //$NON-NLS-1$ - public static final String FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION = "FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION"; //$NON-NLS-1$ + public static final String FLAMEVIEW_TOGGLE_MINIMAP = "FLAMEVIEW_TOGGLE_MINIMAP"; //$NON-NLS-1$ + public static final String FLAMEVIEW_RESET_ZOOM = "FLAMEVIEW_RESET_ZOOM"; //$NON-NLS-1$ private Messages() { } diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/MessagesUtils.java b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/MessagesUtils.java similarity index 79% rename from application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/MessagesUtils.java rename to application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/MessagesUtils.java index 3600a0bd6a..fae2d8af7d 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/MessagesUtils.java +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/MessagesUtils.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, 2020, Datadog, Inc. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Datadog, Inc. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -31,7 +31,7 @@ * 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.flameview; +package org.openjdk.jmc.flightrecorder.flamegraph; import java.text.MessageFormat; @@ -49,24 +49,24 @@ public static String getStacktraceMessage(String key) { } /** - * Localized Flameview Message + * Localized Flamegraph Message * * @param key - * flameview message + * flamegraph message * @param values * message values * @return message */ - public static String getFlameviewMessage(String key, Object ... values) { + public static String getFlamegraphMessage(String key, Object ... values) { if (values == null || values.length == 0) { - return getFlameviewMessage(key); + return getFlamegraphMessage(key); } else { - return MessageFormat.format(getFlameviewMessage(key), values); + return MessageFormat.format(getFlamegraphMessage(key), values); } } - private static String getFlameviewMessage(String key) { - return org.openjdk.jmc.flightrecorder.flameview.Messages.getString(key); + private static String getFlamegraphMessage(String key) { + return org.openjdk.jmc.flightrecorder.flamegraph.Messages.getString(key); } } diff --git a/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/views/FlamegraphSwingView.java b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/views/FlamegraphSwingView.java new file mode 100644 index 0000000000..3736d5c5b3 --- /dev/null +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/views/FlamegraphSwingView.java @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Datadog, Inc. 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 http://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.flightrecorder.flamegraph.views; + +import io.github.bric3.fireplace.core.ui.Colors; +import io.github.bric3.fireplace.flamegraph.ColorMapper; +import io.github.bric3.fireplace.flamegraph.DimmingFrameColorProvider; +import io.github.bric3.fireplace.flamegraph.FlamegraphImage; +import io.github.bric3.fireplace.flamegraph.FlamegraphView; +import io.github.bric3.fireplace.flamegraph.FrameBox; +import io.github.bric3.fireplace.flamegraph.FrameFontProvider; +import io.github.bric3.fireplace.flamegraph.FrameModel; +import io.github.bric3.fireplace.flamegraph.FrameTextsProvider; +import io.github.bric3.fireplace.flamegraph.animation.ZoomAnimation; +import io.github.bric3.fireplace.swt_awt.EmbeddingComposite; +import io.github.bric3.fireplace.swt_awt.SWT_AWTBridge; +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.imageio.ImageIO; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ResourceLocator; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.window.DefaultToolTip; +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IViewSite; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.ViewPart; +import org.openjdk.jmc.common.item.IAttribute; +import org.openjdk.jmc.common.item.IItemCollection; +import org.openjdk.jmc.common.item.ItemCollectionToolkit; +import org.openjdk.jmc.common.item.ItemFilters; +import org.openjdk.jmc.common.unit.IQuantity; +import org.openjdk.jmc.common.util.FormatToolkit; +import org.openjdk.jmc.common.util.Pair; +import org.openjdk.jmc.flightrecorder.flamegraph.FlamegraphImages; +import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator; +import org.openjdk.jmc.flightrecorder.stacktrace.tree.Node; +import org.openjdk.jmc.flightrecorder.stacktrace.tree.StacktraceTreeModel; +import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; +import org.openjdk.jmc.flightrecorder.ui.common.AttributeSelection; +import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; +import org.openjdk.jmc.ui.CoreImages; +import org.openjdk.jmc.ui.common.util.AdapterUtil; +import org.openjdk.jmc.ui.handlers.MCContextMenuManager; +import org.openjdk.jmc.ui.misc.DisplayToolkit; + +import static java.util.Collections.emptySet; +import static java.util.Collections.reverseOrder; +import static java.util.Map.Entry.comparingByValue; +import static java.util.stream.Collectors.toMap; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_FLAME_GRAPH; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_ICICLE_GRAPH; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_JPEG_IMAGE; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_PNG_IMAGE; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_PRINT; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_RESET_ZOOM; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_SAVE_AS; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_SAVE_FLAME_GRAPH_AS; +import static org.openjdk.jmc.flightrecorder.flamegraph.Messages.FLAMEVIEW_TOGGLE_MINIMAP; +import static org.openjdk.jmc.flightrecorder.flamegraph.MessagesUtils.getFlamegraphMessage; + +public class FlamegraphSwingView extends ViewPart implements ISelectionListener { + private static final String DIR_ICONS = "icons/"; //$NON-NLS-1$ + private static final String PLUGIN_ID = "org.openjdk.jmc.flightrecorder.flamegraph"; //$NON-NLS-1$ + private static final String ATTRIBUTE_SELECTION_SEPARATOR_ID = "AttrSelectionSep"; //$NON-NLS-1$ + private static final int MODEL_EXECUTOR_THREADS_NUMBER = 3; + private static final ExecutorService MODEL_EXECUTOR = Executors.newFixedThreadPool(MODEL_EXECUTOR_THREADS_NUMBER, + new ThreadFactory() { + private final ThreadGroup group = new ThreadGroup("FlamegraphModelCalculationGroup"); //$NON-NLS-1$ + private final AtomicInteger counter = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + var t = new Thread(group, r, "FlamegraphModelCalculation-" + counter.getAndIncrement()); //$NON-NLS-1$ + t.setDaemon(true); + return t; + } + }); + + private FrameSeparator frameSeparator; + private EmbeddingComposite embeddingComposite; + private FlamegraphView flamegraphView; + private ExportAction[] exportActions; + private boolean threadRootAtTop = true; + private boolean icicleViewActive = true; + private IItemCollection currentItems; + private volatile ModelState modelState = ModelState.NONE; + private ModelRebuildRunnable modelRebuildRunnable; + private IAttribute currentAttribute; + private AttributeSelection attributeSelection; + private IToolBarManager toolBar; + + private enum GroupActionType { + THREAD_ROOT(Messages.STACKTRACE_VIEW_THREAD_ROOT, IAction.AS_RADIO_BUTTON, CoreImages.THREAD), + LAST_FRAME(Messages.STACKTRACE_VIEW_LAST_FRAME, IAction.AS_RADIO_BUTTON, CoreImages.METHOD_NON_OPTIMIZED), + ICICLE_GRAPH(getFlamegraphMessage(FLAMEVIEW_ICICLE_GRAPH), IAction.AS_RADIO_BUTTON, flamegraphImageDescriptor( + FlamegraphImages.ICON_ICICLE_FLIP)), + FLAME_GRAPH(getFlamegraphMessage(FLAMEVIEW_FLAME_GRAPH), IAction.AS_RADIO_BUTTON, flamegraphImageDescriptor( + FlamegraphImages.ICON_FLAME_FLIP)); + + private final String message; + private final int action; + private final ImageDescriptor imageDescriptor; + + private GroupActionType(String message, int action, ImageDescriptor imageDescriptor) { + this.message = message; + this.action = action; + this.imageDescriptor = imageDescriptor; + } + } + + private enum ModelState { + NOT_STARTED, STARTED, FINISHED, NONE; + } + + private class GroupByAction extends Action { + private final GroupActionType actionType; + + GroupByAction(GroupActionType actionType) { + super(actionType.message, actionType.action); + this.actionType = actionType; + setToolTipText(actionType.message); + setImageDescriptor(actionType.imageDescriptor); + setChecked(GroupActionType.THREAD_ROOT.equals(actionType) == threadRootAtTop); + } + + @Override + public void run() { + boolean newValue = isChecked() == GroupActionType.THREAD_ROOT.equals(actionType); + if (newValue != threadRootAtTop) { + threadRootAtTop = newValue; + triggerRebuildTask(currentItems); + } + } + } + + private class ViewModeAction extends Action { + private final GroupActionType actionType; + + ViewModeAction(GroupActionType actionType) { + super(actionType.message, actionType.action); + this.actionType = actionType; + setToolTipText(actionType.message); + setImageDescriptor(actionType.imageDescriptor); + setChecked(GroupActionType.ICICLE_GRAPH.equals(actionType) == icicleViewActive); + } + + @Override + public void run() { + icicleViewActive = GroupActionType.ICICLE_GRAPH.equals(actionType); + SwingUtilities.invokeLater(() -> flamegraphView + .setMode(icicleViewActive ? FlamegraphView.Mode.ICICLEGRAPH : FlamegraphView.Mode.FLAMEGRAPH)); + } + } + + private class ToggleMinimapAction extends Action { + private ToggleMinimapAction() { + super(getFlamegraphMessage(FLAMEVIEW_TOGGLE_MINIMAP), IAction.AS_CHECK_BOX); + setToolTipText(getFlamegraphMessage(FLAMEVIEW_TOGGLE_MINIMAP)); + setImageDescriptor(flamegraphImageDescriptor(FlamegraphImages.ICON_MINIMAP)); + + setChecked(false); + } + + @Override + public void run() { + boolean toggleMinimap = !flamegraphView.isShowMinimap(); + SwingUtilities.invokeLater(() -> flamegraphView.setShowMinimap(toggleMinimap)); + setChecked(toggleMinimap); + } + } + + private class ResetZoomAction extends Action { + private ResetZoomAction() { + super(getFlamegraphMessage(FLAMEVIEW_RESET_ZOOM), IAction.AS_PUSH_BUTTON); + setToolTipText(getFlamegraphMessage(FLAMEVIEW_RESET_ZOOM)); + setImageDescriptor(flamegraphImageDescriptor(FlamegraphImages.ICON_RESET_ZOOM)); + } + + @Override + public void run() { + SwingUtilities.invokeLater(() -> flamegraphView.resetZoom()); + } + } + + private enum ExportActionType { + SAVE_AS(getFlamegraphMessage(FLAMEVIEW_SAVE_AS), IAction.AS_PUSH_BUTTON, PlatformUI.getWorkbench() + .getSharedImages().getImageDescriptor(ISharedImages.IMG_ETOOL_SAVEAS_EDIT), PlatformUI.getWorkbench() + .getSharedImages().getImageDescriptor(ISharedImages.IMG_ETOOL_SAVEAS_EDIT_DISABLED)), + PRINT(getFlamegraphMessage(FLAMEVIEW_PRINT), IAction.AS_PUSH_BUTTON, PlatformUI.getWorkbench().getSharedImages() + .getImageDescriptor(ISharedImages.IMG_ETOOL_PRINT_EDIT), PlatformUI.getWorkbench().getSharedImages() + .getImageDescriptor(ISharedImages.IMG_ETOOL_PRINT_EDIT_DISABLED)); + + private final String message; + private final int action; + private final ImageDescriptor imageDescriptor; + private final ImageDescriptor disabledImageDescriptor; + + private ExportActionType(String message, int action, ImageDescriptor imageDescriptor, + ImageDescriptor disabledImageDescriptor) { + this.message = message; + this.action = action; + this.imageDescriptor = imageDescriptor; + this.disabledImageDescriptor = disabledImageDescriptor; + } + } + + private class ExportAction extends Action { + private final ExportActionType actionType; + + private ExportAction(ExportActionType actionType) { + super(actionType.message, actionType.action); + this.actionType = actionType; + setToolTipText(actionType.message); + setImageDescriptor(actionType.imageDescriptor); + setDisabledImageDescriptor(actionType.disabledImageDescriptor); + } + + @Override + public void run() { + switch (actionType) { + case SAVE_AS: + Executors.newSingleThreadExecutor().execute(FlamegraphSwingView.this::saveFlamegraph); + break; + case PRINT: + // not supported + break; + } + } + } + + private static class ModelRebuildRunnable implements Runnable { + + private final FlamegraphSwingView view; + private final IItemCollection items; + private final IAttribute attribute; + private volatile boolean isInvalid; + + private ModelRebuildRunnable(FlamegraphSwingView view, IItemCollection items, IAttribute attribute) { + this.view = view; + this.items = items; + this.attribute = attribute; + } + + private void setInvalid() { + this.isInvalid = true; + } + + @Override + public void run() { + view.modelState = ModelState.STARTED; + if (isInvalid) { + return; + } + var filteredItems = items; + if (attribute != null) { + filteredItems = filteredItems.apply(ItemFilters.hasAttribute(attribute)); + } + var treeModel = new StacktraceTreeModel(filteredItems, view.frameSeparator, !view.threadRootAtTop, + attribute); + if (isInvalid) { + return; + } + var rootFrameDescription = createRootNodeDescription(items); + var frameBoxList = convert(treeModel); + if (!isInvalid) { + view.modelState = ModelState.FINISHED; + view.setModel(items, frameBoxList, rootFrameDescription); + DisplayToolkit.inDisplayThread().execute(() -> { + var attributeList = AttributeSelection.extractAttributes(items); + String attrName = attribute != null ? attribute.getName() : null; + view.createAttributeSelection(attrName, attributeList); + }); + } + } + + private static List> convert(StacktraceTreeModel model) { + var nodes = new ArrayList>(); + + FrameBox.flattenAndCalculateCoordinate(nodes, model.getRoot(), Node::getChildren, Node::getCumulativeWeight, + node -> node.getChildren().stream().mapToDouble(Node::getCumulativeWeight).sum(), 0.0d, 1.0d, 0); + + return nodes; + } + + private static String createRootNodeDescription(IItemCollection items) { + var freq = eventTypeFrequency(items); + // root => 51917 events of 1 type: Method Profiling Sample[51917], + long totalEvents = freq.values().stream().mapToLong(Long::longValue).sum(); + if (totalEvents == 0) { + return "Stack Trace not available"; + } + var description = new StringBuilder(totalEvents + " event(s) of " + freq.size() + " type(s): "); + int i = 0; + for (var e : freq.entrySet()) { + description.append(e.getKey()).append("[").append(e.getValue()).append("]"); + if (i < freq.size() - 1 && i < 3) { + description.append(", "); + } + if (i >= 3) { + description.append(", ..."); + break; + } + i++; + } + + return description.toString(); + } + + private static Map eventTypeFrequency(IItemCollection items) { + var eventCountByType = new HashMap(); + for (var eventIterable : items) { + if (eventIterable.getItemCount() == 0) { + continue; + } + eventCountByType.compute(eventIterable.getType().getName(), + (k, v) -> (v == null ? 0 : v) + eventIterable.getItemCount()); + } + // sort the map in ascending order of values + return eventCountByType.entrySet().stream().sorted(reverseOrder(comparingByValue())) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + } + + private void createAttributeSelection(String attrName, Collection>> items) { + if (attributeSelection != null) { + toolBar.remove(attributeSelection.getId()); + } + attributeSelection = new AttributeSelection(items, attrName, this::getCurrentAttribute, + this::setCurrentAttribute, () -> triggerRebuildTask(currentItems)); + toolBar.insertAfter(ATTRIBUTE_SELECTION_SEPARATOR_ID, attributeSelection); + toolBar.update(true); + } + + @Override + public void init(IViewSite site, IMemento memento) throws PartInitException { + super.init(site, memento); + frameSeparator = new FrameSeparator(FrameSeparator.FrameCategorization.METHOD, false); + + var siteMenu = site.getActionBars().getMenuManager(); + { + siteMenu.add(new Separator(MCContextMenuManager.GROUP_TOP)); + siteMenu.add(new Separator(MCContextMenuManager.GROUP_VIEWER_SETUP)); + } + + toolBar = site.getActionBars().getToolBarManager(); + { + toolBar.add(new ResetZoomAction()); + toolBar.add(new ToggleMinimapAction()); + + toolBar.add(new Separator()); + + var groupByFlamegraphActions = new ViewModeAction[] {new ViewModeAction(GroupActionType.FLAME_GRAPH), + new ViewModeAction(GroupActionType.ICICLE_GRAPH)}; + Stream.of(groupByFlamegraphActions).forEach(toolBar::add); + + toolBar.add(new Separator()); + + var groupByActions = new GroupByAction[] {new GroupByAction(GroupActionType.LAST_FRAME), + new GroupByAction(GroupActionType.THREAD_ROOT)}; + Stream.of(groupByActions).forEach(toolBar::add); + + toolBar.add(new Separator()); + + exportActions = new ExportAction[] {new ExportAction(ExportActionType.SAVE_AS)}; + Stream.of(exportActions).forEach((action) -> action.setEnabled(false)); + Stream.of(exportActions).forEach(toolBar::add); + + toolBar.add(new Separator(ATTRIBUTE_SELECTION_SEPARATOR_ID)); + createAttributeSelection(null, Collections.emptyList()); + } + + getSite().getPage().addSelectionListener(this); + } + + @Override + public void dispose() { + getSite().getPage().removeSelectionListener(this); + super.dispose(); + } + + @Override + public void createPartControl(Composite parent) { + var container = new SashForm(parent, SWT.HORIZONTAL); + embeddingComposite = new EmbeddingComposite(container); + container.setMaximizedControl(embeddingComposite); + + // done here to avoid SWT complain about wrong thread + var bgColorAwtColor = SWT_AWTBridge.toAWTColor(container.getBackground()); + + var tooltip = new StyledToolTip(embeddingComposite, ToolTip.NO_RECREATE, true); + { + tooltip.setPopupDelay(500); + tooltip.setShift(new Point(10, 5)); + + embeddingComposite.addListener(SWT.MouseExit, event -> Display.getDefault().timerExec(300, tooltip::hide)); + } + + embeddingComposite.init(() -> { + var panel = new JPanel(new BorderLayout()); + { + var searchControl = createSearchControl(); + searchControl.setBackground(bgColorAwtColor); + panel.add(searchControl, BorderLayout.NORTH); + } + { + flamegraphView = createFlameGraph(embeddingComposite, tooltip); + new ZoomAnimation().install(flamegraphView); + + flamegraphView.component.setBackground(bgColorAwtColor); + panel.add(flamegraphView.component, BorderLayout.CENTER); + } + panel.setBackground(bgColorAwtColor); + return panel; + }); + } + + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + if (selection instanceof IStructuredSelection) { + var first = ((IStructuredSelection) selection).getFirstElement(); + var items = AdapterUtil.getAdapter(first, IItemCollection.class); + if (items == null) { + triggerRebuildTask(ItemCollectionToolkit.build(Stream.empty())); + } else if (!items.equals(currentItems)) { + triggerRebuildTask(items); + } + } + } + + @Override + public void setFocus() { + embeddingComposite.setFocus(); + } + + private JComponent createSearchControl() { + var searchField = new JTextField("", 60); + + searchField.addActionListener(e -> { + var searched = searchField.getText(); + if (searched.isBlank() && flamegraphView != null) { + flamegraphView.highlightFrames(emptySet(), searched); + return; + } + + CompletableFuture.runAsync(() -> { + try { + if (flamegraphView == null) { + return; + } + var matches = flamegraphView.getFrames().stream().filter(frame -> { + var method = frame.actualNode.getFrame().getMethod(); + return (method.getMethodName().contains(searched) + || method.getType().getTypeName().contains(searched) + || method.getType().getPackage().getName() != null + && method.getType().getPackage().getName().contains(searched)) + || method.getType().getPackage().getModule() != null + && method.getType().getPackage().getModule().getName() != null + && method.getType().getPackage().getModule().getName().contains(searched) + || method.getFormalDescriptor().replace('/', '.').contains(searched); + }).collect(Collectors.toCollection(() -> Collections.newSetFromMap(new IdentityHashMap<>()))); + flamegraphView.highlightFrames(matches, searched); + } catch (Exception ex) { + ex.printStackTrace(); + } + }); + }); + var panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + panel.add(new JLabel(getFlamegraphMessage("FLAMEVIEW_SEARCH"))); + panel.add(searchField); + return panel; + } + + private FlamegraphView createFlameGraph(Composite owner, DefaultToolTip tooltip) { + var fg = new FlamegraphView(); + fg.putClientProperty(FlamegraphView.SHOW_STATS, false); + fg.setShowMinimap(false); + + fg.setRenderConfiguration( + FrameTextsProvider.of( + frame -> frame.isRoot() ? "" : frame.actualNode.getFrame().getHumanReadableShortString(), + frame -> frame.isRoot() ? "" + : FormatToolkit.getHumanReadable(frame.actualNode.getFrame().getMethod(), false, false, + false, false, true, false), + frame -> frame.isRoot() ? "" : frame.actualNode.getFrame().getMethod().getMethodName()), + new DimmingFrameColorProvider<>(frame -> ColorMapper.ofObjectHashUsing(Colors.Palette.DATADOG.colors()) + .apply(frame.actualNode.getFrame().getMethod().getType().getPackage())), + FrameFontProvider.defaultFontProvider()); + + fg.setHoverListener((frameBox, frameRect, mouseEvent) -> { + // This code knows too much about Flamegraph but given tooltips + // will probably evolve it may be too early to refactor it + var scrollPane = (JScrollPane) mouseEvent.getComponent(); + var canvas = scrollPane.getViewport().getView(); + + var pointOnCanvas = SwingUtilities.convertPoint(scrollPane, mouseEvent.getPoint(), canvas); + pointOnCanvas.y = frameRect.y + frameRect.height; + var componentPoint = SwingUtilities.convertPoint(canvas, pointOnCanvas, flamegraphView.component); + + if (frameBox.isRoot()) { + return; + } + + var method = frameBox.actualNode.getFrame().getMethod(); + + var escapedMethod = frameBox.actualNode.getFrame().getHumanReadableShortString().replace("<", "<") + .replace(">", ">"); + var sb = new StringBuilder().append("

").append("").append(escapedMethod).append("
"); + + var packageName = method.getType().getPackage(); + if (packageName != null) { + sb.append(packageName).append("
"); + } + sb.append("


Weight: ").append(frameBox.actualNode.getCumulativeWeight()).append("
") + .append("Type: ").append(frameBox.actualNode.getFrame().getType()).append("
"); + + var bci = frameBox.actualNode.getFrame().getBCI(); + if (bci != null) { + sb.append("BCI: ").append(bci).append("
"); + } + var frameLineNumber = frameBox.actualNode.getFrame().getFrameLineNumber(); + if (frameLineNumber != null) { + sb.append("Line number: ").append(frameLineNumber).append("
"); + } + sb.append("

"); + var text = sb.toString(); + + Display.getDefault().asyncExec(() -> { + var control = Display.getDefault().getCursorControl(); + + if (Objects.equals(owner, control)) { + tooltip.setText(text); + + tooltip.hide(); + tooltip.show(SWT_AWTBridge.toSWTPoint(componentPoint)); + } + }); + }); + + return fg; + } + + private void triggerRebuildTask(IItemCollection items) { + // Release old model calculation before building a new + if (modelRebuildRunnable != null) { + modelRebuildRunnable.setInvalid(); + } + + currentItems = items; + modelState = ModelState.NOT_STARTED; + modelRebuildRunnable = new ModelRebuildRunnable(this, items, currentAttribute); + if (!modelRebuildRunnable.isInvalid) { + MODEL_EXECUTOR.execute(modelRebuildRunnable); + } + } + + private IAttribute getCurrentAttribute() { + return currentAttribute; + } + + private void setCurrentAttribute(IAttribute attr) { + currentAttribute = attr; + } + + private void setModel( + final IItemCollection items, final List> flatFrameList, String rootFrameDescription) { + if (ModelState.FINISHED.equals(modelState) && items.equals(currentItems)) { + SwingUtilities.invokeLater(() -> { + flamegraphView.setModel(new FrameModel<>(rootFrameDescription, + (frameA, frameB) -> Objects.equals(frameA.actualNode.getFrame(), frameB.actualNode.getFrame()), + flatFrameList)); + + Display.getDefault().asyncExec(() -> { + if (embeddingComposite.isDisposed()) { + return; + } + Stream.of(exportActions).forEach((action) -> action.setEnabled(!flatFrameList.isEmpty())); + }); + }); + } + } + + private void saveFlamegraph() { + var future = new CompletableFuture(); + + DisplayToolkit.inDisplayThread().execute(() -> { + var fd = new FileDialog(embeddingComposite.getShell(), SWT.SAVE); + fd.setText(getFlamegraphMessage(FLAMEVIEW_SAVE_FLAME_GRAPH_AS)); + fd.setFilterNames(new String[]{ + getFlamegraphMessage(FLAMEVIEW_PNG_IMAGE), + getFlamegraphMessage(FLAMEVIEW_JPEG_IMAGE) + }); + fd.setFilterExtensions(new String[]{"*.png", "*.jpg"}); //$NON-NLS-1$ //$NON-NLS-2$ + fd.setFileName("flame_graph"); //$NON-NLS-1$ + fd.setOverwrite(true); + if (fd.open() == null) { + future.cancel(true); + return; + } + + var fileName = fd.getFileName().toLowerCase(); + // FIXME: FileDialog filterIndex returns -1 (https://bugs.eclipse.org/bugs/show_bug.cgi?id=546256) + if (!fileName.endsWith(".jpg") && !fileName.endsWith(".jpeg") && !fileName.endsWith(".png")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + future.completeExceptionally(new UnsupportedOperationException("Unsupported image format")); //$NON-NLS-1$ + return; + } + future.complete(Paths.get(fd.getFilterPath(), fd.getFileName())); + }); + + Supplier generator = () -> { + var fgImage = new FlamegraphImage<>( + FrameTextsProvider.of( + frame -> frame.isRoot() ? "" : frame.actualNode.getFrame().getHumanReadableShortString(), //$NON-NLS-1$ + frame -> frame.isRoot() ? "" //$NON-NLS-1$ + : FormatToolkit.getHumanReadable(frame.actualNode.getFrame().getMethod(), false, + false, false, false, true, false), + frame -> frame.isRoot() ? "" : frame.actualNode.getFrame().getMethod().getMethodName()), //$NON-NLS-1$ + new DimmingFrameColorProvider( + frame -> ColorMapper.ofObjectHashUsing(Colors.Palette.DATADOG.colors()) + .apply(frame.actualNode.getFrame().getMethod().getType().getPackage())), + FrameFontProvider.defaultFontProvider()); + + return fgImage.generate(flamegraphView.getFrameModel(), flamegraphView.getMode(), 2000); + }; + + Optional.of(future) + .map(f -> { + try { + return f.get(); + } catch (CancellationException e) { + // noop : model calculation is canceled when is still running + } catch (InterruptedException | ExecutionException e) { + FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to save flame graph", e); //$NON-NLS-1$ + } + return null; + }) + .ifPresent(destinationPath -> { + // make spotbugs happy about NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE + var type = Optional.ofNullable(destinationPath.getFileName()) + .map(p -> p.toString().toLowerCase()) + .map(f -> + switch (f.substring(f.lastIndexOf('.') + 1)) { //$NON-NLS-1$ + case "jpeg", "jpg" -> //$NON-NLS-1$ //$NON-NLS-2$ + "jpg"; //$NON-NLS-1$ + case "png" -> //$NON-NLS-1$ + "png"; //$NON-NLS-1$ + default -> null; + } + ).orElseThrow(() -> new IllegalStateException("Unhandled type for " + destinationPath)); + + try (var os = new BufferedOutputStream(Files.newOutputStream(destinationPath))) { + var renderImg = generator.get(); + + var img = switch (type) { + case "png" -> renderImg; + case "jpg" -> { + // JPG does not have an alpha channel, and ImageIO.write will simply write a 0 byte file + // to workaround this it is required to copy the image to a BufferedImage without alpha channel + var newBufferedImage = new BufferedImage( + renderImg.getWidth(), + renderImg.getHeight(), + BufferedImage.TYPE_INT_RGB + ); + renderImg.copyData(newBufferedImage.getRaster()); + + yield newBufferedImage; + } + default -> throw new IllegalStateException("Type is checked above"); + }; + + + ImageIO.write( + img, + type, + os + ); + } catch (IOException e) { + FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to save flame graph", e); //$NON-NLS-1$ + } + }); + } + + private static ImageDescriptor flamegraphImageDescriptor(String iconName) { + return ResourceLocator.imageDescriptorFromBundle(PLUGIN_ID, DIR_ICONS + iconName).orElse(null); //$NON-NLS-1$ + } +} diff --git a/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/views/StyledToolTip.java b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/views/StyledToolTip.java new file mode 100644 index 0000000000..c4422d302b --- /dev/null +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/java/org/openjdk/jmc/flightrecorder/flamegraph/views/StyledToolTip.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Datadog, Inc. 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 http://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.flightrecorder.flamegraph.views; + +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.window.DefaultToolTip; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.forms.widgets.FormText; + +/** + * This tool tip extends the Jface implementation and relies on the {@link FormText} control to + * render the text. + * + * @author brice.dutheil + * @see FormText + */ +public class StyledToolTip extends DefaultToolTip { + public StyledToolTip(Control control) { + super(control); + } + + public StyledToolTip(Control control, int style, boolean manualActivation) { + super(control, style, manualActivation); + } + + @Override + protected Composite createToolTipContentArea(Event event, Composite parent) { + final Composite container = setDefaultLayout(new Composite(parent, SWT.NULL), event); + GridLayoutFactory.fillDefaults().margins(2, 2).generateLayout(container); + FormText formText = setDefaultLayout(new FormText(container, SWT.NONE), event); + + String pseudoHtml = getText(event); + + formText.setText(pseudoHtml, true, false); + return parent; + } + + private T setDefaultLayout(T control, Event event) { + control.setBackground(getBackgroundColor(event)); + control.setForeground(getForegroundColor(event)); + control.setFont(getFont(event)); + return control; + } +} diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages.properties b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages.properties similarity index 92% rename from application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages.properties rename to application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages.properties index daf81e0ee8..090a260e29 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages.properties +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages.properties @@ -1,6 +1,6 @@ # -# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. -# Copyright (c) 2020, Datadog, Inc. All rights reserved. +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, Datadog, Inc. All rights reserved. # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -38,6 +38,7 @@ FLAMEVIEW_PRINT=Print FLAMEVIEW_SAVE_FLAME_GRAPH_AS=Save flame graph as... FLAMEVIEW_JPEG_IMAGE=JPEG image FLAMEVIEW_PNG_IMAGE=PNG image +FLAMEVIEW_SEARCH=Search: FLAMEVIEW_SELECT_STACKTRACE_NOT_AVAILABLE=Stack Trace not available FLAMEVIEW_SELECT_ROOT_NODE_EVENT=event FLAMEVIEW_SELECT_ROOT_NODE_EVENTS=events @@ -54,3 +55,5 @@ FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE=Event Type FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE=Package FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES=Samples FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION=Description +FLAMEVIEW_TOGGLE_MINIMAP=Toggle minimap +FLAMEVIEW_RESET_ZOOM=Reset zoom diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages_ja.properties b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages_ja.properties similarity index 92% rename from application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages_ja.properties rename to application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages_ja.properties index daf81e0ee8..090a260e29 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages_ja.properties +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages_ja.properties @@ -1,6 +1,6 @@ # -# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. -# Copyright (c) 2020, Datadog, Inc. All rights reserved. +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, Datadog, Inc. All rights reserved. # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -38,6 +38,7 @@ FLAMEVIEW_PRINT=Print FLAMEVIEW_SAVE_FLAME_GRAPH_AS=Save flame graph as... FLAMEVIEW_JPEG_IMAGE=JPEG image FLAMEVIEW_PNG_IMAGE=PNG image +FLAMEVIEW_SEARCH=Search: FLAMEVIEW_SELECT_STACKTRACE_NOT_AVAILABLE=Stack Trace not available FLAMEVIEW_SELECT_ROOT_NODE_EVENT=event FLAMEVIEW_SELECT_ROOT_NODE_EVENTS=events @@ -54,3 +55,5 @@ FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE=Event Type FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE=Package FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES=Samples FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION=Description +FLAMEVIEW_TOGGLE_MINIMAP=Toggle minimap +FLAMEVIEW_RESET_ZOOM=Reset zoom diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages_zh_CN.properties b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages_zh_CN.properties similarity index 92% rename from application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages_zh_CN.properties rename to application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages_zh_CN.properties index daf81e0ee8..090a260e29 100644 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/org/openjdk/jmc/flightrecorder/flameview/messages_zh_CN.properties +++ b/application/org.openjdk.jmc.flightrecorder.flamegraph/src/main/resources/org/openjdk/jmc/flightrecorder/flamegraph/messages_zh_CN.properties @@ -1,6 +1,6 @@ # -# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. -# Copyright (c) 2020, Datadog, Inc. All rights reserved. +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, Datadog, Inc. All rights reserved. # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -38,6 +38,7 @@ FLAMEVIEW_PRINT=Print FLAMEVIEW_SAVE_FLAME_GRAPH_AS=Save flame graph as... FLAMEVIEW_JPEG_IMAGE=JPEG image FLAMEVIEW_PNG_IMAGE=PNG image +FLAMEVIEW_SEARCH=Search: FLAMEVIEW_SELECT_STACKTRACE_NOT_AVAILABLE=Stack Trace not available FLAMEVIEW_SELECT_ROOT_NODE_EVENT=event FLAMEVIEW_SELECT_ROOT_NODE_EVENTS=events @@ -54,3 +55,5 @@ FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE=Event Type FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE=Package FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES=Samples FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION=Description +FLAMEVIEW_TOGGLE_MINIMAP=Toggle minimap +FLAMEVIEW_RESET_ZOOM=Reset zoom diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/META-INF/MANIFEST.MF b/application/org.openjdk.jmc.flightrecorder.flameview/META-INF/MANIFEST.MF deleted file mode 100644 index bce5f033f9..0000000000 --- a/application/org.openjdk.jmc.flightrecorder.flameview/META-INF/MANIFEST.MF +++ /dev/null @@ -1,16 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Localization: plugin -Bundle-Name: FlameView -Bundle-SymbolicName: org.openjdk.jmc.flightrecorder.flameview;singleton:=true -Bundle-Version: 9.0.0.qualifier -Require-Bundle: org.openjdk.jmc.flightrecorder, - org.openjdk.jmc.flightrecorder.serializers, - org.openjdk.jmc.flightrecorder.ui, - org.openjdk.jmc.flightrecorder.rules, - org.openjdk.jmc.common -Bundle-RequiredExecutionEnvironment: JavaSE-17 -Bundle-Vendor: Oracle Corporation -Automatic-Module-Name: org.openjdk.jmc.flightrecorder.ext.flameview -Export-Package: org.openjdk.jmc.flightrecorder.flameview, - org.openjdk.jmc.flightrecorder.flameview.views diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/pom.xml b/application/org.openjdk.jmc.flightrecorder.flameview/pom.xml deleted file mode 100644 index b2c4cbace4..0000000000 --- a/application/org.openjdk.jmc.flightrecorder.flameview/pom.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - - 4.0.0 - - org.openjdk.jmc - missioncontrol.application - ${revision}${changelist} - - org.openjdk.jmc.flightrecorder.flameview - eclipse-plugin - - - 1.6.7 - process-resources - ${project.basedir}/src/main/resources/jslibs - ${basedir}/../../configuration/ide/eclipse/formatting/formatting.xml - ${basedir}/../../configuration/ide/eclipse/formatting/formattingjs.xml - - - - - - com.googlecode.maven-download-plugin - download-maven-plugin - ${download.maven.plugin.version} - - - d3-flamegraph-css - ${download-maven-plugin.phase} - - wget - - - https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css - false - ${download-maven-plugin.output} - - - - download-d3 - ${download-maven-plugin.phase} - - wget - - - https://d3js.org/d3.v7.min.js - false - ${download-maven-plugin.output} - - - - d3-flamegraph-tooltip-js - ${download-maven-plugin.phase} - - wget - - - https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph-tooltip.js - false - ${download-maven-plugin.output} - - - - d3-flamegraph-js - ${download-maven-plugin.phase} - - wget - - - https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.js - false - ${download-maven-plugin.output} - - - - - - - - diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/views/FlameGraphView.java b/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/views/FlameGraphView.java deleted file mode 100644 index 9ea762a0db..0000000000 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/java/org/openjdk/jmc/flightrecorder/flameview/views/FlameGraphView.java +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, 2023, Datadog, Inc. 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 http://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.flightrecorder.flameview.views; - -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_FLAME_GRAPH; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_ICICLE_GRAPH; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_JPEG_IMAGE; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_PNG_IMAGE; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_PRINT; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SAVE_AS; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SAVE_FLAME_GRAPH_AS; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SELECT_HTML_TABLE_COUNT; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE; -import static org.openjdk.jmc.flightrecorder.flameview.Messages.FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES; -import static org.openjdk.jmc.flightrecorder.flameview.MessagesUtils.getFlameviewMessage; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.IToolBarManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.resource.ResourceLocator; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.browser.Browser; -import org.eclipse.swt.browser.BrowserFunction; -import org.eclipse.swt.browser.ProgressAdapter; -import org.eclipse.swt.browser.ProgressEvent; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.events.MenuDetectEvent; -import org.eclipse.swt.events.MenuDetectListener; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.ImageLoader; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.ui.IMemento; -import org.eclipse.ui.ISelectionListener; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.IViewSite; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.part.ViewPart; -import org.openjdk.jmc.common.item.IAttribute; -import org.openjdk.jmc.common.item.IItemCollection; -import org.openjdk.jmc.common.item.ItemCollectionToolkit; -import org.openjdk.jmc.common.item.ItemFilters; -import org.openjdk.jmc.common.unit.IQuantity; -import org.openjdk.jmc.common.util.Pair; -import org.openjdk.jmc.common.util.StringToolkit; -import org.openjdk.jmc.flightrecorder.flameview.FlameviewImages; -import org.openjdk.jmc.flightrecorder.serializers.json.FlameGraphJsonSerializer; -import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator; -import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator.FrameCategorization; -import org.openjdk.jmc.flightrecorder.stacktrace.tree.StacktraceTreeModel; -import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; -import org.openjdk.jmc.flightrecorder.ui.common.AttributeSelection; -import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; -import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; -import org.openjdk.jmc.ui.CoreImages; -import org.openjdk.jmc.ui.common.util.AdapterUtil; -import org.openjdk.jmc.ui.handlers.MCContextMenuManager; -import org.openjdk.jmc.ui.misc.DisplayToolkit; - -public class FlameGraphView extends ViewPart implements ISelectionListener { - private static final String DIR_ICONS = "icons/"; //$NON-NLS-1$ - private static final String PLUGIN_ID = "org.openjdk.jmc.flightrecorder.flameview"; //$NON-NLS-1$ - private static final String TABLE_COLUMN_COUNT = getFlameviewMessage(FLAMEVIEW_SELECT_HTML_TABLE_COUNT); - private static final String TABLE_COLUMN_EVENT_TYPE = getFlameviewMessage(FLAMEVIEW_SELECT_HTML_TABLE_EVENT_TYPE); - private static final String TOOLTIP_PACKAGE = getFlameviewMessage(FLAMEVIEW_SELECT_HTML_TOOLTIP_PACKAGE); - private static final String TOOLTIP_SAMPLES = getFlameviewMessage(FLAMEVIEW_SELECT_HTML_TOOLTIP_SAMPLES); - private static final String TOOLTIP_DESCRIPTION = getFlameviewMessage(FLAMEVIEW_SELECT_HTML_TOOLTIP_DESCRIPTION); - private static final String ATTRIBUTE_SELECTION_ID = "AttributeSelection"; //$NON-NLS-1$ - private static final String ATTRIBUTE_SELECTION_SEP_ID = "AttrSelectionSep"; //$NON-NLS-1$ - private static final String HTML_PAGE; - static { - // from: https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css - String cssD3Flamegraph = "jslibs/d3-flamegraph.css"; - // from: https://d3js.org/d3.v7.min.js - String jsD3 = "jslibs/d3.v7.min.js"; - // from: https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph-tooltip.js - String jsD3Tip = "jslibs/d3-flamegraph-tooltip.js"; - // from: https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.js - String jsD3FlameGraph = "jslibs/d3-flamegraph.js"; - // jmc flameview coloring, tooltip and other functions - String jsFlameviewName = "flameview.js"; - String cssFlameview = "flameview.css"; - - String jsD3Lib = loadLibraries(jsD3, jsD3FlameGraph, jsD3Tip); - String styleheets = loadLibraries(cssD3Flamegraph, cssFlameview); - String jsFlameviewColoring = fileContent(jsFlameviewName); - - String magnifierIcon = getIconBase64(ImageConstants.ICON_MAGNIFIER); - - // formatter arguments for the template: %1 - CSSs stylesheets, - // %2 - Search Icon Base64, %3 - 3rd party scripts, %4 - Flameview Coloring, - HTML_PAGE = String.format(fileContent("page.template"), styleheets, magnifierIcon, jsD3Lib, - jsFlameviewColoring); - } - - private static final int MODEL_EXECUTOR_THREADS_NUMBER = 3; - private static final ExecutorService MODEL_EXECUTOR = Executors.newFixedThreadPool(MODEL_EXECUTOR_THREADS_NUMBER, - new ThreadFactory() { - private ThreadGroup group = new ThreadGroup("FlameGraphModelCalculationGroup"); - private AtomicInteger counter = new AtomicInteger(); - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, "FlameGraphModelCalculation-" + counter.getAndIncrement()); - t.setDaemon(true); - return t; - } - }); - private FrameSeparator frameSeparator; - - private Browser browser; - private SashForm container; - private GroupByAction[] groupByActions; - private GroupByFlameviewAction[] groupByFlameviewActions; - private ExportAction[] exportActions; - private boolean threadRootAtTop = true; - private boolean icicleViewActive = true; - private IItemCollection currentItems; - private volatile ModelState modelState = ModelState.NONE; - private ModelRebuildRunnable modelRebuildRunnable; - private IAttribute currentAttribute; - private AttributeSelection attributeSelection; - private IToolBarManager toolBar; - - private enum GroupActionType { - THREAD_ROOT(Messages.STACKTRACE_VIEW_THREAD_ROOT, IAction.AS_RADIO_BUTTON, CoreImages.THREAD), - LAST_FRAME(Messages.STACKTRACE_VIEW_LAST_FRAME, IAction.AS_RADIO_BUTTON, CoreImages.METHOD_NON_OPTIMIZED), - ICICLE_GRAPH(getFlameviewMessage(FLAMEVIEW_ICICLE_GRAPH), IAction.AS_RADIO_BUTTON, flameviewImageDescriptor( - FlameviewImages.ICON_ICICLE_FLIP)), - FLAME_GRAPH(getFlameviewMessage(FLAMEVIEW_FLAME_GRAPH), IAction.AS_RADIO_BUTTON, flameviewImageDescriptor( - FlameviewImages.ICON_FLAME_FLIP)); - - private final String message; - private final int action; - private final ImageDescriptor imageDescriptor; - - private GroupActionType(String message, int action, ImageDescriptor imageDescriptor) { - this.message = message; - this.action = action; - this.imageDescriptor = imageDescriptor; - } - } - - private enum ModelState { - NOT_STARTED, STARTED, FINISHED, NONE; - } - - private class GroupByAction extends Action { - private final GroupActionType actionType; - - GroupByAction(GroupActionType actionType) { - super(actionType.message, actionType.action); - this.actionType = actionType; - setToolTipText(actionType.message); - setImageDescriptor(actionType.imageDescriptor); - setChecked(GroupActionType.THREAD_ROOT.equals(actionType) == threadRootAtTop); - } - - @Override - public void run() { - boolean newValue = isChecked() == GroupActionType.THREAD_ROOT.equals(actionType); - if (newValue != threadRootAtTop) { - threadRootAtTop = newValue; - triggerRebuildTask(currentItems); - } - } - } - - private class GroupByFlameviewAction extends Action { - private final GroupActionType actionType; - - GroupByFlameviewAction(GroupActionType actionType) { - super(actionType.message, actionType.action); - this.actionType = actionType; - setToolTipText(actionType.message); - setImageDescriptor(actionType.imageDescriptor); - setChecked(GroupActionType.ICICLE_GRAPH.equals(actionType) == icicleViewActive); - } - - @Override - public void run() { - icicleViewActive = GroupActionType.ICICLE_GRAPH.equals(actionType); - browser.execute(String.format("icicleView(%s);", icicleViewActive)); - } - } - - private enum ExportActionType { - SAVE_AS(getFlameviewMessage(FLAMEVIEW_SAVE_AS), IAction.AS_PUSH_BUTTON, PlatformUI.getWorkbench() - .getSharedImages().getImageDescriptor(ISharedImages.IMG_ETOOL_SAVEAS_EDIT), PlatformUI.getWorkbench() - .getSharedImages().getImageDescriptor(ISharedImages.IMG_ETOOL_SAVEAS_EDIT_DISABLED)), - PRINT(getFlameviewMessage(FLAMEVIEW_PRINT), IAction.AS_PUSH_BUTTON, PlatformUI.getWorkbench().getSharedImages() - .getImageDescriptor(ISharedImages.IMG_ETOOL_PRINT_EDIT), PlatformUI.getWorkbench().getSharedImages() - .getImageDescriptor(ISharedImages.IMG_ETOOL_PRINT_EDIT_DISABLED)); - - private final String message; - private final int action; - private final ImageDescriptor imageDescriptor; - private final ImageDescriptor disabledImageDescriptor; - - private ExportActionType(String message, int action, ImageDescriptor imageDescriptor, - ImageDescriptor disabledImageDescriptor) { - this.message = message; - this.action = action; - this.imageDescriptor = imageDescriptor; - this.disabledImageDescriptor = disabledImageDescriptor; - } - } - - private class ExportAction extends Action { - private final ExportActionType actionType; - - private ExportAction(ExportActionType actionType) { - super(actionType.message, actionType.action); - this.actionType = actionType; - setToolTipText(actionType.message); - setImageDescriptor(actionType.imageDescriptor); - setDisabledImageDescriptor(actionType.disabledImageDescriptor); - } - - @Override - public void run() { - switch (actionType) { - case SAVE_AS: - Executors.newSingleThreadExecutor().execute(FlameGraphView.this::saveFlameGraph); - break; - case PRINT: - browser.execute("window.print()"); //$NON-NLS-1$ - break; - } - } - } - - private static class ModelRebuildRunnable implements Runnable { - - private final FlameGraphView view; - private final IItemCollection items; - private final IAttribute attribute; - private volatile boolean isInvalid; - - private ModelRebuildRunnable(FlameGraphView view, IItemCollection items, IAttribute attribute) { - this.view = view; - this.items = items; - this.attribute = attribute; - } - - private void setInvalid() { - this.isInvalid = true; - } - - @Override - public void run() { - view.modelState = ModelState.STARTED; - if (isInvalid) { - return; - } - IItemCollection filteredItems = items; - if (attribute != null) { - filteredItems = filteredItems.apply(ItemFilters.hasAttribute(attribute)); - } - StacktraceTreeModel treeModel = new StacktraceTreeModel(filteredItems, view.frameSeparator, - !view.threadRootAtTop, attribute); - if (isInvalid) { - return; - } - String flameGraphJson = FlameGraphJsonSerializer.toJson(treeModel); - - if (isInvalid) { - return; - } else { - view.modelState = ModelState.FINISHED; - DisplayToolkit.inDisplayThread().execute(() -> { - view.setModel(items, flameGraphJson); - - List>> attrList = AttributeSelection.extractAttributes(items); - String attrName = attribute != null ? attribute.getName() : null; - view.createAttributeSelection(attrName, attrList); - }); - } - } - } - - private void createAttributeSelection(String attrName, Collection>> items) { - if (attributeSelection != null) { - toolBar.remove(attributeSelection.getId()); - } - attributeSelection = new AttributeSelection(items, attrName, this::getCurrentAttribute, - this::setCurrentAttribute, () -> triggerRebuildTask(currentItems)); - toolBar.insertAfter(ATTRIBUTE_SELECTION_SEP_ID, attributeSelection); - toolBar.update(true); - } - - @Override - public void init(IViewSite site, IMemento memento) throws PartInitException { - super.init(site, memento); - frameSeparator = new FrameSeparator(FrameCategorization.METHOD, false); - groupByActions = new GroupByAction[] {new GroupByAction(GroupActionType.LAST_FRAME), - new GroupByAction(GroupActionType.THREAD_ROOT)}; - groupByFlameviewActions = new GroupByFlameviewAction[] {new GroupByFlameviewAction(GroupActionType.FLAME_GRAPH), - new GroupByFlameviewAction(GroupActionType.ICICLE_GRAPH)}; - exportActions = new ExportAction[] {new ExportAction(ExportActionType.SAVE_AS), - new ExportAction(ExportActionType.PRINT)}; - Stream.of(exportActions).forEach((action) -> action.setEnabled(false)); - - // methodFormatter = new MethodFormatter(null, () -> viewer.refresh()); - IMenuManager siteMenu = site.getActionBars().getMenuManager(); - siteMenu.add(new Separator(MCContextMenuManager.GROUP_TOP)); - siteMenu.add(new Separator(MCContextMenuManager.GROUP_VIEWER_SETUP)); - // addOptions(siteMenu); - toolBar = site.getActionBars().getToolBarManager(); - - Stream.of(groupByFlameviewActions).forEach(toolBar::add); - toolBar.add(new Separator()); - Stream.of(groupByActions).forEach(toolBar::add); - toolBar.add(new Separator()); - Stream.of(exportActions).forEach(toolBar::add); - - toolBar.add(new Separator(ATTRIBUTE_SELECTION_SEP_ID)); - createAttributeSelection(null, Collections.emptyList()); - - getSite().getPage().addSelectionListener(this); - } - - private IAttribute getCurrentAttribute() { - return currentAttribute; - } - - private void setCurrentAttribute(IAttribute attr) { - currentAttribute = attr; - } - - @Override - public void dispose() { - getSite().getPage().removeSelectionListener(this); - super.dispose(); - } - - @Override - public void createPartControl(Composite parent) { - container = new SashForm(parent, SWT.HORIZONTAL); - browser = new Browser(container, SWT.NONE); - container.setMaximizedControl(browser); - browser.addMenuDetectListener(new MenuDetectListener() { - @Override - public void menuDetected(MenuDetectEvent e) { - e.doit = false; - } - }); - } - - @Override - public void setFocus() { - browser.setFocus(); - } - - @Override - public void saveState(IMemento memento) { - } - - @Override - public void selectionChanged(IWorkbenchPart part, ISelection selection) { - if (selection instanceof IStructuredSelection) { - Object first = ((IStructuredSelection) selection).getFirstElement(); - IItemCollection items = AdapterUtil.getAdapter(first, IItemCollection.class); - if (items == null) { - triggerRebuildTask(ItemCollectionToolkit.build(Stream.empty())); - } else if (!items.equals(currentItems)) { - triggerRebuildTask(items); - } - } - } - - private void triggerRebuildTask(IItemCollection items) { - // Release old model calculation before building a new - if (modelRebuildRunnable != null) { - modelRebuildRunnable.setInvalid(); - } - - currentItems = items; - modelState = ModelState.NOT_STARTED; - modelRebuildRunnable = new ModelRebuildRunnable(this, items, currentAttribute); - if (!modelRebuildRunnable.isInvalid) { - MODEL_EXECUTOR.execute(modelRebuildRunnable); - } - } - - private void setModel(final IItemCollection items, final String json) { - if (ModelState.FINISHED.equals(modelState) && items.equals(currentItems) && !browser.isDisposed()) { - setViewerInput(json); - } - } - - private void setViewerInput(String json) { - Stream.of(exportActions).forEach((action) -> action.setEnabled(false)); - browser.setText(HTML_PAGE); - browser.addListener(SWT.Resize, event -> { - browser.execute("resizeFlameGraph();"); - }); - - browser.addProgressListener(new ProgressAdapter() { - private boolean loaded = false; - - @Override - public void changed(ProgressEvent event) { - if (loaded) { - browser.removeProgressListener(this); - } - } - - @Override - public void completed(ProgressEvent event) { - browser.execute(String.format("configureTooltipText('%s', '%s', '%s', '%s', '%s');", TABLE_COLUMN_COUNT, - TABLE_COLUMN_EVENT_TYPE, TOOLTIP_PACKAGE, TOOLTIP_SAMPLES, TOOLTIP_DESCRIPTION)); - browser.execute(String.format("processGraph(%s, %s);", json, icicleViewActive)); - Stream.of(exportActions).forEach((action) -> action.setEnabled(true)); - loaded = true; - } - }); - } - - private void saveFlameGraph() { - CompletableFuture future = new CompletableFuture<>(); - String[] destination = new String[2]; - - DisplayToolkit.inDisplayThread().execute(() -> { - FileDialog fd = new FileDialog(browser.getShell(), SWT.SAVE); - fd.setText(getFlameviewMessage(FLAMEVIEW_SAVE_FLAME_GRAPH_AS)); - fd.setFilterNames( - new String[] {getFlameviewMessage(FLAMEVIEW_JPEG_IMAGE), getFlameviewMessage(FLAMEVIEW_PNG_IMAGE)}); - fd.setFilterExtensions(new String[] {"*.jpg", "*.png"}); //$NON-NLS-1$ //$NON-NLS-2$ - fd.setFileName("flame_graph"); //$NON-NLS-1$ - fd.setOverwrite(true); - if (fd.open() == null) { - future.cancel(true); - return; - } - - String type; - String fileName = fd.getFileName().toLowerCase(); - // FIXME: FileDialog filterIndex returns -1 (https://bugs.eclipse.org/bugs/show_bug.cgi?id=546256) - if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) { //$NON-NLS-1$ //$NON-NLS-2$ - type = "image/jpeg"; //$NON-NLS-1$ - } else if (fileName.endsWith(".png")) { //$NON-NLS-1$ - type = "image/png"; //$NON-NLS-1$ - } else { - future.completeExceptionally(new UnsupportedOperationException("Unsupported image format")); //$NON-NLS-1$ - return; - } - destination[0] = fd.getFilterPath(); - destination[1] = fd.getFileName(); - - String callback = "_saveFlameGraphCallback"; //$NON-NLS-1$ - new BrowserFunction(browser, callback) { - @Override - public Object function(Object[] arguments) { - if (arguments.length > 1) { - future.completeExceptionally(new RuntimeException((String) arguments[1])); - return null; - } - future.complete((String) arguments[0]); - - super.dispose(); - return null; - } - }; - - browser.execute("exportFlameGraph('" + type + "', '" + callback + "')"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - }); - - try { - String b64 = future.get(); - byte[] bytes = Base64.getDecoder().decode(b64); - FileOutputStream fos = new FileOutputStream(new File(destination[0], destination[1])); - fos.write(bytes); - fos.close(); - } catch (CancellationException e) { - // noop : model calculation is canceled when is still running - } catch (InterruptedException | ExecutionException | IOException e) { - FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to save flame graph", e); //$NON-NLS-1$ - } - } - - private static String loadLibraries(String ... libs) { - if (libs == null || libs.length == 0) { - return ""; - } else { - return Stream.of(libs).map(FlameGraphView::fileContent).collect(Collectors.joining("\n")); - } - } - - private static String fileContent(String fileName) { - try { - return StringToolkit.readString(FlameGraphView.class.getClassLoader().getResourceAsStream(fileName)); - } catch (IOException e) { - FlightRecorderUI.getDefault().getLogger().log(Level.WARNING, - MessageFormat.format("Could not load script \"{0}\",\"{1}\"", fileName, e.getMessage())); //$NON-NLS-1$ - return ""; - } - } - - private static ImageDescriptor flameviewImageDescriptor(String iconName) { - return ResourceLocator.imageDescriptorFromBundle(PLUGIN_ID, DIR_ICONS + iconName).orElse(null); //$NON-NLS-1$ - } - - private static String getIconBase64(String iconName) { - Image image = FlightRecorderUI.getDefault().getImage(iconName); - if (image == null) { - return ""; - } else { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageLoader loader = new ImageLoader(); - loader.data = new ImageData[] {image.getImageData()}; - loader.save(baos, SWT.IMAGE_PNG); - return Base64.getEncoder().encodeToString(baos.toByteArray()); - } - } -} diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/js/flameview.css b/application/org.openjdk.jmc.flightrecorder.flameview/src/main/js/flameview.css deleted file mode 100644 index ecb2516b9e..0000000000 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/js/flameview.css +++ /dev/null @@ -1,39 +0,0 @@ -input { - font-family: Helvetica, Arial, Verdana, sans-serif; - font-size: 12px; - margin-right: 2px; -} - -svg, table { - width: 100%; -} - -table { - margin-top: 4px; -} - -table, th, td { - border-collapse: collapse; - border: 1px solid grey; -} - - -th, td { - text-align: left; - padding-left: 4px; - padding-right: 4px; - padding-top: 2px; - padding-bottom: 2px; -} - -.tdLabel { - width: 60px; - color: white; - text-align: left; -} - -.tdCount { - width: 60px; - color: white; - text-align: right; -} \ No newline at end of file diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/js/flameview.js b/application/org.openjdk.jmc.flightrecorder.flameview/src/main/js/flameview.js deleted file mode 100644 index ea69fb6efa..0000000000 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/js/flameview.js +++ /dev/null @@ -1,221 +0,0 @@ - -/* - Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. - Copyright (c) 2020, Datadog, Inc. 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 http://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. - */ - -String.prototype.hashCode = function() { - var hash = 0; - if (this.length === 0) return hash; - for (var i = 0; i < this.length; i++) { - var char = this.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; - } - return hash; -}; - -const htmlTagBr = "\u003Cbr\u002F\u003E"; -const rootPackageColor = "darkred"; -const invalidPackageColor = "snow"; -const packageJavaColorLightGray = "lightgray"; -const packageComSunAndJdkColorDarkGray = "darkgray"; -const packageSunDarkColorGray = "gray"; -const packageRestValueHMax = 360; -const packageRestValueH = 0; -const packageRestSLValues = [42, 53]; -const packageConsideredDepth = 3; -const packageMarkerJava = "java"; -const packageMarkerSun = "sun"; -const packageMarkerComSunAndJdk = "comSunAndJdk"; -const packageMarkerRest = "rest"; -const packagesIdentifierMap = new Map().set("java.", packageMarkerJava).set("sun.", packageMarkerSun).set("com.sun.", - packageMarkerComSunAndJdk).set("jdk.", packageMarkerComSunAndJdk); -const packageColorMap = new Map().set("", rootPackageColor); -const specialCharactersMap = new Map().set('#', '\x23').set('$', '\x24').set('(', '\x28').set(')', '\x29') - .set(',', '\x2c').set('-', '\x2d').set('.', '\x2e').set('<', '\x3c').set('>', '\x3e').set('[', '\x5b') - .set(']', '\x5d').set('_', '\x5f').set('{', '\x7b').set('|', '\x7c').set('}', '\x7d').set('~', '\x7e'); - -const colorByPackage = function(p) { - if (p === undefined) { - return invalidPackageColor; - } else { - const packageNameStrip = stripPackageName(p); - const packageSelectedColor = packageColorMap.get(packageNameStrip); - if (packageSelectedColor === undefined) { - const packageMarkerSelected = getPackageMarker(packageNameStrip); - const packageNameStripHash = packageNameStrip.hashCode(); - switch (packageMarkerSelected) { - case packageMarkerJava: - packageColorMap.set(packageNameStrip, packageJavaColorLightGray); - break; - case packageMarkerComSunAndJdk: - packageColorMap.set(packageNameStrip, packageComSunAndJdkColorDarkGray); - break; - case packageMarkerSun: - packageColorMap.set(packageNameStrip, packageSunDarkColorGray); - break; - case packageMarkerRest: - const packageRestSelectedColor = createHslColorString(adjustHslPropertyByHash(packageNameStripHash, packageRestValueH, packageRestValueHMax), packageRestSLValues[0], packageRestSLValues[1]); - packageColorMap.set(packageNameStrip, packageRestSelectedColor); - break; - } - return packageColorMap.get(packageNameStrip); - } else { - return packageSelectedColor; - } - } -}; - -const getPackageMarker = function(p) { - for(let k of packagesIdentifierMap.keys()){ - if(p.startsWith(k)){ - return packagesIdentifierMap.get(k); - } - } - return packageMarkerRest; -}; - -const stripPackageName = function(p) { - const splitString = p.split("\u002E"); - const number = Math.min(splitString.length, packageConsideredDepth); - return splitString.slice(0, number).join("\u002E"); -}; - -const adjustHslPropertyByHash = function (hash, min, max) { - const proposedValue = hash % (max - min) + min; - return Math.min(proposedValue, max); -}; - -const createHslColorString = function(h,s,l) { - return "hsl\u0028" + h + "\u002c " + s + "\u0025\u002c " + l + "\u0025\u0029"; -}; - -const colorCell = function(d) { - if (textToSearch !== "" && (evaluateSearchElement(d.data.p) || evaluateSearchElement(d.data.n))) { - return "magenta"; - } else { - return colorByPackage(d.data.p); - } -}; - -const evaluateSearchElement = function(text) { - var adjustTextToSearch = removeSpecialCharacters(textToSearch); - return text !== undefined && removeSpecialCharacters(text).includes(adjustTextToSearch); -}; - -const removeSpecialCharacters = function(text) { - return Array.prototype.map.call(text.trim().toLowerCase(), element => { - if (specialCharactersMap.has(element)) { - return specialCharactersMap.get(element); - } else { - return element; - }}).join(''); -}; - - -const adjustTip = function(d) { - var tipMessage = "".concat(d.data.n, htmlTagBr); - - if (nodeContainsChildren(d.data)) { - if (d.data.d && d.data.d.includes("|")) { - tipMessage += createRootTable(d.data.d); - } else { - tipMessage += createNodeTipTable(d.data); - } - } - - return tipMessage; -} - -const nodeContainsChildren = function(data) { - return Array.isArray(data.c) && data.c.length; -} - -const createNodeTipTable = function(data) { - var table = "".concat(tagOpen("table class='d3-flame-graph-tip'"), tagOpen("tbody")) - if (data.d === undefined) { - table = table.concat(addTableRow(tootlipPackage, data.p, "tdLabel"), - addTableRow(tootlipSamples, data.v, "tdLabel")); - } else { - table += addTableRow(tootlipDescription, data.d, "tdCount"); - } - return table.concat(tagClose("tbody"), tagClose("table")); -} - -const createRootTable = function(input) { - var table = ""; - var tableRows = input.split("|"); - table = table.concat(tagOpen("table class='d3-flame-graph-tip'"), createTableHeader(), tagOpen("tbody")); - var prevCount = 0; - for(var i=0; i < tableRows.length - 1; i++) { - const rowValue = tableRows[i].split(":"); - table += addTableRow(parseInt(rowValue[0]), rowValue[1], "tdCount"); - } - table = table.concat(tagClose("tbody"), tagClose("table")); - return table; -} - -const tagOpen = function(tag, cssClass) { - var result = "\u003C" + tag; - if (cssClass === undefined) { - result +="\u003E"; - } else { - var cssExtended = " class='" + cssClass + "' \u003E"; - result += cssExtended; - } - return result; -} -const tagClose = function(tag) { - return "\u003C\u002F "+ tag + "\u003E"; -} - -const addTableRow = function(eventCount, eventName, cssStartTd) { - return tableTr(tableTd(eventCount, cssStartTd), tableTd(eventName)); -} - -const createTableHeader = function() { - return tagOpen("thead").concat(tableTr(tableTh(tooltipTableThCount, "tdLabel"), tableTh(tooltipTableThEventType)),tagClose("thead")); -} - -const tableTh = function(value, css) { - return tagOpen("th", css).concat(value, tagClose("th")); -} - -const tableTd = function(value, css) { - return tagOpen("td", css).concat(value, tagClose("td")); -} - -const tableTr = function(...elements) { - return tagOpen("tr").concat(elements.join(""), tagClose("tr")); -} - diff --git a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/page.template b/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/page.template deleted file mode 100644 index 67bc3c7534..0000000000 --- a/application/org.openjdk.jmc.flightrecorder.flameview/src/main/resources/page.template +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - -
- - - - - diff --git a/application/org.openjdk.jmc.rjmx.services.jfr/src/main/java/org/openjdk/jmc/rjmx/services/jfr/internal/FlightRecorderServiceV2.java b/application/org.openjdk.jmc.rjmx.services.jfr/src/main/java/org/openjdk/jmc/rjmx/services/jfr/internal/FlightRecorderServiceV2.java index 3af2ba0f36..154223e8ed 100644 --- a/application/org.openjdk.jmc.rjmx.services.jfr/src/main/java/org/openjdk/jmc/rjmx/services/jfr/internal/FlightRecorderServiceV2.java +++ b/application/org.openjdk.jmc.rjmx.services.jfr/src/main/java/org/openjdk/jmc/rjmx/services/jfr/internal/FlightRecorderServiceV2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -104,6 +104,10 @@ public String getVersion() { } private boolean isDynamicFlightRecorderSupported(IConnectionHandle handle) { + if (ConnectionToolkit.isSubstrateVM(handle)) { + // JFR may not have been built into the native image. Check that FlightRecorderMXBean is accessible from the MBean server. + return isAvailable(handle); + } // All OpenJDK versions of JFR support dynamic enablement of JFR, so if there are no commercial features in play // all is A-OK. return !cfs.hasCommercialFeatures() || (ConnectionToolkit.isHotSpot(handle) @@ -111,7 +115,10 @@ private boolean isDynamicFlightRecorderSupported(IConnectionHandle handle) { } private boolean isFlightRecorderDisabled(IConnectionHandle handle) { - if (cfs != null && cfs.hasCommercialFeatures()) { + if (ConnectionToolkit.isSubstrateVM(handle)) { + // For native image, commercial features may be available but disabled while JFR is still enabled + return !isAvailable(handle); + } else if (cfs != null && cfs.hasCommercialFeatures()) { return !cfs.isCommercialFeaturesEnabled() || JVMSupportToolkit.isFlightRecorderDisabled(handle, false); } else { return JVMSupportToolkit.isFlightRecorderDisabled(handle, false); @@ -128,7 +135,7 @@ public FlightRecorderServiceV2(IConnectionHandle handle) throws ConnectionExcept if (!isDynamicFlightRecorderSupported(handle) && isFlightRecorderDisabled(handle)) { throw new ServiceNotAvailableException(""); //$NON-NLS-1$ } - if (JVMSupportToolkit.isFlightRecorderDisabled(handle, true)) { + if (!ConnectionToolkit.isSubstrateVM(handle) && JVMSupportToolkit.isFlightRecorderDisabled(handle, true)) { throw new ServiceNotAvailableException(""); //$NON-NLS-1$ } connection = handle; diff --git a/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/ConnectionToolkit.java b/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/ConnectionToolkit.java index 5ee2678923..3fe9e5eecb 100644 --- a/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/ConnectionToolkit.java +++ b/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/ConnectionToolkit.java @@ -365,6 +365,20 @@ public static boolean isHotSpot(IConnectionHandle connectionHandle) { return vmName != null && JavaVMVersionToolkit.isHotspotJVMName(vmName); } + /** + * Returns {@code true} if the connection handle is connected to a Substrate VM, {@code false} + * otherwise. This method requires the connection handle to be connected. + * + * @param connectionHandle + * the connection handle to check. + * @return {@code true} if the connection handle is connected to a Substrate VM, {@code false} + * otherwise. + */ + public static boolean isSubstrateVM(IConnectionHandle connectionHandle) { + String vmName = getVMName(connectionHandle); + return vmName != null && JavaVMVersionToolkit.isSubstrateVMName(vmName); + } + /** * Returns {@code true} if the connection handle is associated with an Oracle built JVM, * {@code false} otherwise. If the information is already present in the {@link JVMDescriptor}, diff --git a/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/JVMSupportToolkit.java b/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/JVMSupportToolkit.java index 180794a239..a9444976d5 100644 --- a/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/JVMSupportToolkit.java +++ b/application/org.openjdk.jmc.rjmx/src/main/java/org/openjdk/jmc/rjmx/JVMSupportToolkit.java @@ -33,6 +33,7 @@ package org.openjdk.jmc.rjmx; import javax.management.MBeanServerConnection; +import javax.management.ObjectName; import org.openjdk.jmc.common.jvm.JVMDescriptor; import org.openjdk.jmc.common.jvm.JVMType; @@ -47,6 +48,8 @@ */ public final class JVMSupportToolkit { + private final static String JFR_MBEAN_OBJECT_NAME = "jdk.management.jfr:type=FlightRecorder"; //$NON-NLS-1$ + private JVMSupportToolkit() { throw new IllegalArgumentException("Don't instantiate this toolkit"); //$NON-NLS-1$ } @@ -66,7 +69,7 @@ public static String[] checkConsoleSupport(IConnectionHandle connection) { if (ConnectionToolkit.isJRockit(connection)) { title = Messages.JVMSupport_TITLE_JROCKIT_NOT_SUPPORTED; message = Messages.JVMSupport_MESSAGE_JROCKIT_NOT_SUPPORTED; - } else if (!ConnectionToolkit.isHotSpot(connection)) { + } else if (!ConnectionToolkit.isHotSpot(connection) && !ConnectionToolkit.isSubstrateVM(connection)) { title = Messages.JVMSupport_TITLE_UNKNOWN_JVM; message = Messages.JVMSupport_MESSAGE_UNKNOWN_JVM; } else if (!ConnectionToolkit.isJavaVersionAboveOrEqual(connection, @@ -98,8 +101,12 @@ public static boolean hasFlightRecorder(IConnectionHandle connection) { } MBeanServerConnection server = connection.getServiceOrNull(MBeanServerConnection.class); try { - HotspotManagementToolkit.getVMOption(server, "FlightRecorder"); - return true; + if (ConnectionToolkit.isSubstrateVM(connection)) { + return server.isRegistered(new ObjectName(JFR_MBEAN_OBJECT_NAME)); + } else { + HotspotManagementToolkit.getVMOption(server, "FlightRecorder"); + return true; + } } catch (Exception e) { // RuntimeMBeanException thrown if FlightRecorder is not present return false; } @@ -179,6 +186,9 @@ public static String checkFlightRecorderSupport(IServerHandle handle, boolean sh if (jvmInfo.getJvmType() == JVMType.UNKNOWN) { return null; } + if (jvmInfo.getJvmType() == JVMType.SUBSTRATE) { + return null; + } if (jvmInfo.getJvmType() != JVMType.HOTSPOT) { return getJfrNonHotSpotNotSupported(shortMessage); } diff --git a/application/pom.xml b/application/pom.xml index a0d935a2db..9e6288715d 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ @@ -84,11 +82,15 @@ jdp ${project.version} + + org.openjdk.jmc + testlib + ${project.version} + org.openjdk.jmc common.test - test-jar test ${project.version} diff --git a/core/org.openjdk.jmc.common/pom.xml b/core/org.openjdk.jmc.common/pom.xml index af3ba194bb..f767f08025 100644 --- a/core/org.openjdk.jmc.common/pom.xml +++ b/core/org.openjdk.jmc.common/pom.xml @@ -1,6 +1,6 @@ 9.0.0 -SNAPSHOT + scm:git:git://github.com/openjdk/jmc.git + 2018 UTF-8 UTF-8 META-INF - ${session.executionRootDirectory}/../configuration/checkstyle/checkstyle.xml - ${project.basedir}/../configuration/ide/eclipse/formatting/formatting.xml + ${project.basedir}/../configuration 17 17 - 0.8.10 - 3.3.0 + + 3.1.0 + 3.3.0 2.34.0 - 3.3.0 1.0 3.3.1 + 1.5.0 + 3.11.0 3.1.0 + 3.1.0 + 3.3.0 3.3.0 3.5.0 - 3.1.0 - 1.5.0 - 3.1.0 + 3.1.1 3.1.1 - 3.11.0 - 3.1.0 + 3.1.0 + 0.8.10 + + 4.13.2 - http://jdk.java.net/jmc + https://jdk.java.net/jmc Universal Permissive License Version 1.0 @@ -90,14 +94,14 @@ JIRA - https://bugs.openjdk.java.net/projects/JMC/issues + https://bugs.openjdk.org/projects/JMC/issues jmc dev - http://mail.openjdk.java.net/mailman/listinfo/jmc-dev - http://mail.openjdk.java.net/mailman/listinfo/jmc-dev - http://mail.openjdk.java.net/pipermail/jmc-dev/ + https://mail.openjdk.org/mailman/listinfo/jmc-dev + https://mail.openjdk.org/mailman/listinfo/jmc-dev + https://mail.openjdk.org/pipermail/jmc-dev/ @@ -110,7 +114,7 @@ jmc JDK Mission Control - jmc-dev@openjdk.java.net + jmc-dev@openjdk.org https://www.oracle.com/java/technologies/jdk-mission-control.html @@ -122,6 +126,7 @@ org.openjdk.jmc.flightrecorder.serializers org.openjdk.jmc.flightrecorder.writer org.openjdk.jmc.jdp + org.openjdk.jmc.testlib tests @@ -142,6 +147,15 @@ ${snapshot.repo} + + + + junit + junit + ${junit.version} + + + @@ -155,26 +169,13 @@
- - com.diffplug.spotless - spotless-maven-plugin - ${spotless.version} - - - - ${spotless.config.path} - 4.8.0 - - - - org.apache.maven.plugins maven-resources-plugin ${maven.resources.version} - copy-resources + copy-license process-resources copy-resources @@ -216,11 +217,6 @@ maven-compiler-plugin ${maven.compiler.version} - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven.javadoc.version} - org.apache.maven.plugins maven-surefire-plugin @@ -285,11 +281,33 @@ - 17 + ${maven.compiler.source} + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + ${jmc.config.path}/ide/eclipse/formatting/formatting.xml + 4.8.0 + + + + + + check + validate + + check + + + + org.commonjava.maven.plugins directory-maven-plugin @@ -352,20 +370,20 @@ org.apache.maven.plugins maven-checkstyle-plugin ${maven.checkstyle.version} + + ${jmc.config.path}/checkstyle/checkstyle.xml + true + UTF-8 + true + true + - validate - validate - - ${checkstyle.config.path} - true - UTF-8 - true - true - - - check - + check + validate + + check + @@ -385,6 +403,7 @@ org.apache.maven.plugins maven-javadoc-plugin + ${maven.javadoc.version} attach-javadocs @@ -394,7 +413,8 @@ - 8 + ${maven.compiler.source} + src/main/java true -Xdoclint:all @@ -410,6 +430,11 @@ + + org.apache.maven.plugins + maven-install-plugin + ${maven.install.version} + org.apache.maven.plugins maven-gpg-plugin diff --git a/core/tests/org.openjdk.jmc.common.test/.classpath b/core/tests/org.openjdk.jmc.common.test/.classpath index 7658f8f9ce..79f4c7eab6 100644 --- a/core/tests/org.openjdk.jmc.common.test/.classpath +++ b/core/tests/org.openjdk.jmc.common.test/.classpath @@ -17,7 +17,6 @@ - diff --git a/core/tests/org.openjdk.jmc.common.test/.project b/core/tests/org.openjdk.jmc.common.test/.project index 135d3a9c14..7123541a0e 100644 --- a/core/tests/org.openjdk.jmc.common.test/.project +++ b/core/tests/org.openjdk.jmc.common.test/.project @@ -10,16 +10,6 @@ - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - org.eclipse.m2e.core.maven2Builder @@ -28,7 +18,6 @@ org.eclipse.m2e.core.maven2Nature - org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature diff --git a/core/tests/org.openjdk.jmc.common.test/META-INF/MANIFEST.MF b/core/tests/org.openjdk.jmc.common.test/META-INF/MANIFEST.MF deleted file mode 100644 index 1970c2cb9b..0000000000 --- a/core/tests/org.openjdk.jmc.common.test/META-INF/MANIFEST.MF +++ /dev/null @@ -1,11 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-RequiredExecutionEnvironment: JavaSE-17 -Bundle-Name: Common Test -Bundle-SymbolicName: org.openjdk.jmc.common.test;singleton:=true -Bundle-Version: 9.0.0.qualifier -Bundle-Vendor: Oracle Corporation -Require-Bundle: org.openjdk.jmc.common, - org.openjdk.jmc.testlib, - org.junit -Automatic-Module-Name: org.openjdk.jmc.common.test diff --git a/core/tests/org.openjdk.jmc.common.test/build.properties b/core/tests/org.openjdk.jmc.common.test/build.properties deleted file mode 100644 index 1ed349fa48..0000000000 --- a/core/tests/org.openjdk.jmc.common.test/build.properties +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright (c) 2018, 2022, Oracle and/or its affiliates. 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 http://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. -# -source.. = src/main/java/,\ - src/main/resources/ -output.. = target/classes/ -bin.includes = META-INF/,\ - . -pde.match.rule.bundle=compatible diff --git a/core/tests/org.openjdk.jmc.common.test/pom.xml b/core/tests/org.openjdk.jmc.common.test/pom.xml index 03b96cccb1..2966fdad5a 100644 --- a/core/tests/org.openjdk.jmc.common.test/pom.xml +++ b/core/tests/org.openjdk.jmc.common.test/pom.xml @@ -1,6 +1,6 @@ + + org.pushing-pixels:radiance-animation:${radiance.version} + true + true + + radiance-animation + radiance-animation + org.pushingpixels.radiance.animation.api.* + +