/* * Copyright 2019 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ package io.flutter.performance; import com.intellij.execution.runners.ExecutionUtil; import com.intellij.icons.AllIcons; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.ToolWindowManagerEx; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SideBorder; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentManager; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import icons.FlutterIcons; import io.flutter.devtools.DevToolsManager; import io.flutter.run.FlutterDevice; import io.flutter.run.FlutterLaunchMode; import io.flutter.run.daemon.FlutterApp; import io.flutter.utils.VmServiceListenerAdapter; import io.flutter.view.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.CompoundBorder; import java.awt.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class FlutterPerformanceView implements Disposable { public static final String TOOL_WINDOW_ID = "Flutter Performance"; private static final Logger LOG = Logger.getInstance(FlutterPerformanceView.class); @NotNull private final Project myProject; private final Map<FlutterApp, PerfViewAppState> perAppViewState = new HashMap<>(); private Content emptyContent; public FlutterPerformanceView(@NotNull Project project) { myProject = project; } void initToolWindow(ToolWindow window) { if (window.isDisposed()) return; updateForEmptyContent(window); } @Override public void dispose() { Disposer.dispose(this); } @NotNull public Project getProject() { return myProject; } private void updateForEmptyContent(ToolWindow toolWindow) { // There's a possible race here where the tool window gets disposed while we're displaying contents. if (toolWindow.isDisposed()) { return; } toolWindow.setIcon(FlutterIcons.Flutter_13); // Display a 'No running applications' message. final ContentManager contentManager = toolWindow.getContentManager(); final JPanel panel = new JPanel(new BorderLayout()); final JBLabel label = new JBLabel("No running applications", SwingConstants.CENTER); label.setForeground(UIUtil.getLabelDisabledForeground()); panel.add(label, BorderLayout.CENTER); emptyContent = contentManager.getFactory().createContent(panel, null, false); contentManager.addContent(emptyContent); } void debugActive(@NotNull FlutterViewMessages.FlutterDebugEvent event) { final FlutterApp app = event.app; final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); if (!(toolWindowManager instanceof ToolWindowManagerEx)) { return; } final ToolWindow toolWindow = toolWindowManager.getToolWindow(TOOL_WINDOW_ID); if (toolWindow == null) { return; } if (!toolWindow.isAvailable()) { toolWindow.setAvailable(true, null); } addPerformanceViewContent(app, toolWindow); app.getVmService().addVmServiceListener(new VmServiceListenerAdapter() { @Override public void connectionOpened() { onAppChanged(app); } @Override public void connectionClosed() { ApplicationManager.getApplication().invokeLater(() -> { if (toolWindow.isDisposed()) return; final ContentManager contentManager = toolWindow.getContentManager(); onAppChanged(app); final PerfViewAppState state = perAppViewState.remove(app); if (state != null) { if (state.content != null) { contentManager.removeContent(state.content, true); } if (state.disposable != null) { Disposer.dispose(state.disposable); } } if (perAppViewState.isEmpty()) { // No more applications are running. updateForEmptyContent(toolWindow); } }); } }); onAppChanged(app); } private void addPerformanceViewContent(FlutterApp app, ToolWindow toolWindow) { final ContentManager contentManager = toolWindow.getContentManager(); final SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(true); final String tabName; final FlutterDevice device = app.device(); if (device == null) { tabName = app.getProject().getName(); } else { final List<FlutterDevice> existingDevices = new ArrayList<>(); for (FlutterApp otherApp : perAppViewState.keySet()) { existingDevices.add(otherApp.device()); } tabName = device.getUniqueName(existingDevices); } // mainContentPanel contains the toolbar, perfViewsPanel, and the footer final JPanel mainContentPanel = new JPanel(new BorderLayout()); final Content content = contentManager.getFactory().createContent(null, tabName, false); content.setComponent(mainContentPanel); content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); content.setIcon(FlutterIcons.Phone); contentManager.addContent(content); // perfViewsPanel contains the three performance views final JComponent perfViewsPanel = Box.createVerticalBox(); perfViewsPanel.setBorder(JBUI.Borders.empty(0, 3)); mainContentPanel.add(perfViewsPanel, BorderLayout.CENTER); if (emptyContent != null) { contentManager.removeContent(emptyContent, true); emptyContent = null; } toolWindow.setIcon(ExecutionUtil.getLiveIndicator(FlutterIcons.Flutter_13)); final PerfViewAppState state = getOrCreateStateForApp(app); assert (state.content == null); state.content = content; final DefaultActionGroup toolbarGroup = createToolbar(toolWindow, app, this); toolWindowPanel.setToolbar(ActionManager.getInstance().createActionToolbar( "FlutterPerfViewToolbar", toolbarGroup, true).getComponent()); final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar("PerformanceToolbar", toolbarGroup, true); final JComponent toolbarComponent = toolbar.getComponent(); toolbarComponent.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM)); mainContentPanel.add(toolbarComponent, BorderLayout.NORTH); // devtools link and run mode footer final JPanel footer = new JPanel(new BorderLayout()); footer.setBorder(new CompoundBorder( IdeBorderFactory.createBorder(SideBorder.TOP), JBUI.Borders.empty(5, 7))); final JLabel runModeLabel = new JBLabel("Run mode: " + app.getLaunchMode()); if (app.getLaunchMode() == FlutterLaunchMode.DEBUG) { runModeLabel.setIcon(AllIcons.General.BalloonInformation); runModeLabel.setToolTipText("Note: debug mode frame rendering times are not indicative of release mode performance"); } final LinkLabel openDevtools = new LinkLabel("Open DevTools...", null); //noinspection unchecked openDevtools.setListener((linkLabel, data) -> { final DevToolsManager devToolsManager = DevToolsManager.getInstance(app.getProject()); devToolsManager.openToScreen(app, null); }, null); footer.add(runModeLabel, BorderLayout.WEST); footer.add(openDevtools, BorderLayout.EAST); mainContentPanel.add(footer, BorderLayout.SOUTH); final boolean debugConnectionAvailable = app.getLaunchMode().supportsDebugConnection(); final boolean isInProfileMode = app.getMode().isProfiling() || app.getLaunchMode().isProfiling(); // If the inspector is available (non-release mode), then show it. if (debugConnectionAvailable) { state.disposable = Disposer.newDisposable(); // Create the three FPS, memory, and widget recount areas. final PerfFPSPanel fpsPanel = new PerfFPSPanel(app, this); perfViewsPanel.add(fpsPanel); final PerfMemoryPanel memoryPanel = new PerfMemoryPanel(app, this); perfViewsPanel.add(memoryPanel); final PerfWidgetRebuildsPanel widgetRebuildsPanel = new PerfWidgetRebuildsPanel(app, this); perfViewsPanel.add(widgetRebuildsPanel); // If in profile mode, auto-open the performance tool window. if (isInProfileMode) { activateToolWindow(); } } else { // Add a message about the inspector not being available in release mode. final JBLabel label = new JBLabel("Profiling is not available in release mode", SwingConstants.CENTER); label.setForeground(UIUtil.getLabelDisabledForeground()); mainContentPanel.add(label, BorderLayout.CENTER); } } private DefaultActionGroup createToolbar(@NotNull ToolWindow toolWindow, @NotNull FlutterApp app, Disposable parentDisposable) { final DefaultActionGroup toolbarGroup = new DefaultActionGroup(); toolbarGroup.add(registerAction(new PerformanceOverlayAction(app))); toolbarGroup.addSeparator(); toolbarGroup.add(registerAction(new DebugPaintAction(app))); toolbarGroup.add(registerAction(new ShowPaintBaselinesAction(app, true))); toolbarGroup.addSeparator(); toolbarGroup.add(registerAction(new TimeDilationAction(app, true))); return toolbarGroup; } FlutterViewAction registerAction(FlutterViewAction action) { getOrCreateStateForApp(action.app).flutterViewActions.add(action); return action; } public void showForApp(@NotNull FlutterApp app) { final PerfViewAppState appState = perAppViewState.get(app); if (appState != null) { final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(TOOL_WINDOW_ID); toolWindow.getContentManager().setSelectedContent(appState.content); } } public void showForAppRebuildCounts(@NotNull FlutterApp app) { final PerfViewAppState appState = perAppViewState.get(app); if (appState != null) { final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(TOOL_WINDOW_ID); toolWindow.getContentManager().setSelectedContent(appState.content); } } private void onAppChanged(FlutterApp app) { if (myProject.isDisposed()) { return; } final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(TOOL_WINDOW_ID); if (toolWindow == null) { //noinspection UnnecessaryReturnStatement return; } } /** * Activate the tool window. */ private void activateToolWindow() { final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); final ToolWindow toolWindow = toolWindowManager.getToolWindow(TOOL_WINDOW_ID); if (toolWindow.isVisible()) { return; } toolWindow.show(null); } private PerfViewAppState getStateForApp(FlutterApp app) { return perAppViewState.get(app); } private PerfViewAppState getOrCreateStateForApp(FlutterApp app) { return perAppViewState.computeIfAbsent(app, k -> new PerfViewAppState()); } } class PerfViewAppState { ArrayList<FlutterViewAction> flutterViewActions = new ArrayList<>(); @Nullable Disposable disposable; Content content; FlutterViewAction registerAction(FlutterViewAction action) { flutterViewActions.add(action); return action; } }