diff --git a/flutter-idea/src/io/flutter/FlutterInitializer.java b/flutter-idea/src/io/flutter/FlutterInitializer.java index 5599238343..3a5c9cdea3 100644 --- a/flutter-idea/src/io/flutter/FlutterInitializer.java +++ b/flutter-idea/src/io/flutter/FlutterInitializer.java @@ -8,7 +8,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.intellij.ProjectTopics; -import com.intellij.ide.browsers.BrowserLauncher; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.ide.ui.UISettingsListener; import com.intellij.notification.*; @@ -39,8 +38,6 @@ import io.flutter.editor.FlutterSaveActionsManager; import io.flutter.logging.FlutterConsoleLogManager; import io.flutter.module.FlutterModuleBuilder; -import io.flutter.perf.FlutterWidgetPerfManager; -import io.flutter.performance.FlutterPerformanceViewFactory; import io.flutter.preview.PreviewViewFactory; import io.flutter.pub.PubRoot; import io.flutter.pub.PubRoots; @@ -165,9 +162,6 @@ public void moduleAdded(@NotNull Project project, @NotNull Module module) { // Start watching for survey triggers. FlutterSurveyNotifications.init(project); - // Start the widget perf manager. - FlutterWidgetPerfManager.init(project); - // Watch save actions for reload on save. FlutterReloadManager.init(project); @@ -326,7 +320,6 @@ public void actionPerformed(@NotNull AnActionEvent event) { private void initializeToolWindows(@NotNull Project project) { // Start watching for Flutter debug active events. FlutterViewFactory.init(project); - FlutterPerformanceViewFactory.init(project); PreviewViewFactory.init(project); RemainingDevToolsViewFactory.init(project); DevToolsExtensionsViewFactory.init(project); diff --git a/flutter-idea/src/io/flutter/inspector/WidgetPerfTipsPanel.java b/flutter-idea/src/io/flutter/inspector/WidgetPerfTipsPanel.java deleted file mode 100644 index c2178e59ca..0000000000 --- a/flutter-idea/src/io/flutter/inspector/WidgetPerfTipsPanel.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2018 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.inspector; - -import com.google.common.collect.ArrayListMultimap; -import com.intellij.ide.browsers.BrowserLauncher; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.components.labels.LinkLabel; -import com.intellij.ui.components.labels.LinkListener; -import com.intellij.ui.components.panels.VerticalLayout; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.JBUI; -import io.flutter.perf.*; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.AsyncUtils; -import org.jetbrains.annotations.NotNull; - -import javax.swing.Timer; -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.util.*; - -/** - * Panel displaying performance tips for the currently visible files. - */ -public class WidgetPerfTipsPanel extends JPanel { - static final int PERF_TIP_COMPUTE_DELAY = 1000; - - private final FlutterWidgetPerfManager perfManager; - - private long lastUpdateTime; - private final JPanel perfTips; - - /** - * Currently active file editor if it is a TextEditor. - */ - private TextEditor currentEditor; - private ArrayList currentTextEditors; - - final LinkListener linkListener; - final private List currentTips = new ArrayList<>(); - - public WidgetPerfTipsPanel(Disposable parentDisposable, @NotNull FlutterApp app) { - setLayout(new VerticalLayout(5)); - - add(new JSeparator()); - - perfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - perfTips = new JPanel(); - perfTips.setLayout(new VerticalLayout(0)); - - linkListener = (source, tip) -> handleTipSelection(tip); - final Project project = app.getProject(); - final MessageBusConnection bus = project.getMessageBus().connect(project); - final FileEditorManagerListener listener = new FileEditorManagerListener() { - @Override - public void selectionChanged(@NotNull FileEditorManagerEvent event) { - selectedEditorChanged(); - } - }; - selectedEditorChanged(); - bus.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener); - - // Computing performance tips is somewhat expensive so we don't want to - // compute them too frequently. Performance tips are only computed when - // new performance stats are available but performance stats are updated - // at 60fps so to be conservative we delay computing perf tips. - final Timer perfTipComputeDelayTimer = new Timer(PERF_TIP_COMPUTE_DELAY, this::onComputePerfTips); - perfTipComputeDelayTimer.start(); - Disposer.register(parentDisposable, perfTipComputeDelayTimer::stop); - } - - private static void handleTipSelection(@NotNull PerfTip tip) { - BrowserLauncher.getInstance().browse(tip.getUrl(), null); - } - - private void onComputePerfTips(ActionEvent event) { - final FlutterWidgetPerf stats = perfManager.getCurrentStats(); - if (stats != null) { - final long latestPerfUpdate = stats.getLastLocalPerfEventTime(); - // Only do work if new performance stats have been recorded. - if (latestPerfUpdate != lastUpdateTime) { - lastUpdateTime = latestPerfUpdate; - updateTip(); - } - } - } - - public void clearTips() { - currentTips.clear(); - - remove(perfTips); - - setVisible(hasPerfTips()); - } - - private void selectedEditorChanged() { - lastUpdateTime = -1; - updateTip(); - } - - private void updateTip() { - if (perfManager.getCurrentStats() == null) { - return; - } - final Set selectedEditors = new HashSet<>(perfManager.getSelectedEditors()); - if (selectedEditors.isEmpty()) { - clearTips(); - return; - } - - final WidgetPerfLinter linter = perfManager.getCurrentStats().getPerfLinter(); - AsyncUtils.whenCompleteUiThread(linter.getTipsFor(selectedEditors), (tips, throwable) -> { - if (tips == null || tips.isEmpty() || throwable != null) { - clearTips(); - return; - } - - final Map forPath = new HashMap<>(); - for (TextEditor editor : selectedEditors) { - final VirtualFile file = editor.getFile(); - if (file != null) { - forPath.put(InspectorService.toSourceLocationUri(file.getPath()), editor); - } - } - final ArrayListMultimap newTipsForFile = ArrayListMultimap.create(); - for (PerfTip tip : tips) { - for (Location location : tip.getLocations()) { - if (forPath.containsKey(location.path)) { - newTipsForFile.put(forPath.get(location.path), tip); - } - } - } - - tipsPerFile = newTipsForFile; - if (!PerfTipRule.equivalentPerfTips(currentTips, tips)) { - showPerfTips(tips); - } - }); - } - - ArrayListMultimap tipsPerFile; - - private void showPerfTips(@NotNull ArrayList tips) { - perfTips.removeAll(); - - currentTips.clear(); - currentTips.addAll(tips); - - for (PerfTip tip : tips) { - final LinkLabel label = new LinkLabel<>( - "" + tip.getMessage() + "", - tip.getRule().getIcon(), - linkListener, - tip - ); - label.setPaintUnderline(false); - label.setBorder(JBUI.Borders.empty(0, 5, 5, 5)); - perfTips.add(label); - } - - add(perfTips); - - setVisible(hasPerfTips()); - } - - private boolean hasPerfTips() { - return !currentTips.isEmpty(); - } -} diff --git a/flutter-idea/src/io/flutter/perf/DocumentFileLocationMapper.java b/flutter-idea/src/io/flutter/perf/DocumentFileLocationMapper.java deleted file mode 100644 index f37205bf45..0000000000 --- a/flutter-idea/src/io/flutter/perf/DocumentFileLocationMapper.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.xdebugger.XDebuggerUtil; -import com.intellij.xdebugger.XSourcePosition; -import com.jetbrains.lang.dart.psi.DartId; -import com.jetbrains.lang.dart.psi.DartReferenceExpression; -import io.flutter.inspector.InspectorService; -import org.jetbrains.annotations.Nullable; - -public class DocumentFileLocationMapper implements FileLocationMapper { - @Nullable private final Document document; - - private final PsiFile psiFile; - private final VirtualFile virtualFile; - private final XDebuggerUtil debuggerUtil; - - public DocumentFileLocationMapper(String path, Project project) { - this(lookupDocument(path, project), project); - } - - @Nullable - public static Document lookupDocument(String path, Project project) { - final String fileName = InspectorService.fromSourceLocationUri(path, project); - - final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName); - if (virtualFile != null && virtualFile.exists() && - !(virtualFile instanceof LightVirtualFile) && !(virtualFile instanceof HttpVirtualFile)) { - return FileDocumentManager.getInstance().getDocument(virtualFile); - } - - return null; - } - - DocumentFileLocationMapper(@Nullable Document document, Project project) { - this.document = document; - - if (document != null) { - psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - virtualFile = psiFile != null ? psiFile.getVirtualFile() : null; - debuggerUtil = XDebuggerUtil.getInstance(); - } - else { - psiFile = null; - virtualFile = null; - debuggerUtil = null; - } - } - - @Nullable - @Override - public TextRange getIdentifierRange(int line, int column) { - if (psiFile == null) { - return null; - } - - // Convert to zero based line and column indices. - line = line - 1; - column = column - 1; - - if (document == null || line >= document.getLineCount() || document.isLineModified(line)) { - return null; - } - - final XSourcePosition pos = debuggerUtil.createPosition(virtualFile, line, column); - if (pos == null) { - return null; - } - final int offset = pos.getOffset(); - PsiElement element = psiFile.getOriginalFile().findElementAt(offset); - if (element == null) { - return null; - } - - // Handle named constructors gracefully. For example, for the constructor - // Image.asset(...) we want to return "Image.asset" instead of "asset". - if (element.getParent() instanceof DartId) { - element = element.getParent(); - } - while (element.getParent() instanceof DartReferenceExpression) { - element = element.getParent(); - } - return element.getTextRange(); - } - - @Nullable - @Override - public String getText(@Nullable TextRange textRange) { - if (document == null || textRange == null) { - return null; - } - return document.getText(textRange); - } - - @Override - public String getPath() { - return psiFile == null ? null : psiFile.getVirtualFile().getPath(); - } -} diff --git a/flutter-idea/src/io/flutter/perf/EditorPerfDecorations.java b/flutter-idea/src/io/flutter/perf/EditorPerfDecorations.java deleted file mode 100644 index 826178d557..0000000000 --- a/flutter-idea/src/io/flutter/perf/EditorPerfDecorations.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.event.EditorMouseEvent; -import com.intellij.openapi.editor.event.EditorMouseEventArea; -import com.intellij.openapi.editor.event.EditorMouseListener; -import com.intellij.openapi.editor.ex.MarkupModelEx; -import com.intellij.openapi.editor.ex.RangeHighlighterEx; -import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ex.ToolWindowManagerEx; -import com.intellij.ui.ColorUtil; -import com.intellij.ui.JBColor; -import com.intellij.xdebugger.XSourcePosition; -import io.flutter.performance.FlutterPerformanceView; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.AsyncUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * This class is a view model managing display of performance statistics for - * a specific TextEditor using RangeHighlighters to show the performance - * statistics as animated icons in the gutter of the text editor and by - * highlighting the ranges of text corresponding to the performance statistcs. - */ -class EditorPerfDecorations implements EditorMouseListener, EditorPerfModel { - private static final int HIGHLIGHTER_LAYER = HighlighterLayer.SELECTION - 1; - - /** - * Experimental option to animate highlighted widget names. - *

- * Disabled by default as animating contents of the TextEditor results in - * higher than desired memory usage. - */ - public static boolean ANIMATE_WIDGET_NAME_HIGLIGHTS = false; - - @NotNull - private final TextEditor textEditor; - @NotNull - private final FlutterApp app; - @NotNull - private FilePerfInfo stats; - - private boolean hasDecorations = false; - private boolean hoveredOverLineMarkerArea = false; - - private final Map perfMarkers = new HashMap<>(); - private boolean alwaysShowLineMarkersOverride = false; - - EditorPerfDecorations(@NotNull TextEditor textEditor, @NotNull FlutterApp app) { - this.textEditor = textEditor; - this.app = app; - stats = new FilePerfInfo(); - textEditor.getEditor().addEditorMouseListener(this); - } - - @Override - public boolean getAlwaysShowLineMarkers() { - return hoveredOverLineMarkerArea || alwaysShowLineMarkersOverride; - } - - @Override - public void setAlwaysShowLineMarkersOverride(boolean show) { - final boolean lastValue = getAlwaysShowLineMarkers(); - alwaysShowLineMarkersOverride = show; - if (lastValue != getAlwaysShowLineMarkers()) { - updateIconUIAnimations(); - } - } - - @NotNull - @Override - public FilePerfInfo getStats() { - return stats; - } - - @NotNull - @Override - public TextEditor getTextEditor() { - return textEditor; - } - - @NotNull - @Override - public FlutterApp getApp() { - return app; - } - - void setHasDecorations(boolean value) { - if (value != hasDecorations) { - hasDecorations = value; - } - } - - @Override - public void setPerfInfo(@NotNull FilePerfInfo stats) { - this.stats = stats; - final Editor editor = textEditor.getEditor(); - final MarkupModel markupModel = editor.getMarkupModel(); - - // Remove markers that aren't in the new perf report. - final List rangesToRemove = new ArrayList<>(); - for (TextRange range : perfMarkers.keySet()) { - if (!stats.hasLocation(range)) { - rangesToRemove.add(range); - } - } - for (TextRange range : rangesToRemove) { - removeMarker(range); - } - - for (TextRange range : stats.getLocations()) { - final PerfGutterIconRenderer existing = perfMarkers.get(range); - if (existing == null) { - addRangeHighlighter(range, markupModel); - } - else { - existing.updateUI(true); - } - } - setHasDecorations(true); - } - - private void removeMarker(TextRange range) { - final PerfGutterIconRenderer marker = perfMarkers.remove(range); - if (marker != null) { - final Editor editor = textEditor.getEditor(); - final MarkupModel markupModel = editor.getMarkupModel(); - markupModel.removeHighlighter(marker.getHighlighter()); - } - } - - @Override - public void markAppIdle() { - stats.markAppIdle(); - updateIconUIAnimations(); - } - - private void addRangeHighlighter(TextRange textRange, MarkupModel markupModel) { - if(textRange == null || markupModel == null) { - return; - } - try { - // Catch possible (and rare) instances of addRangeHighlighter throwing an IllegalArgumentException, see - // https://github.com/flutter/flutter-intellij/issues/7328 - final RangeHighlighter rangeHighlighter = markupModel.addRangeHighlighter( - textRange.getStartOffset(), textRange.getEndOffset(), HIGHLIGHTER_LAYER, new TextAttributes(), HighlighterTargetArea.EXACT_RANGE); - - final PerfGutterIconRenderer renderer = new PerfGutterIconRenderer( - textRange, - this, - rangeHighlighter - ); - rangeHighlighter.setGutterIconRenderer(renderer); - assert !perfMarkers.containsKey(textRange); - perfMarkers.put(textRange, renderer); - } catch (IllegalArgumentException e) { - // do nothing - } - } - - @Override - public boolean isAnimationActive() { - return getStats().getTotalValue(PerfMetric.peakRecent) > 0; - } - - @Override - public void onFrame() { - if (app.isReloading() || !hasDecorations || !isAnimationActive()) { - return; - } - updateIconUIAnimations(); - } - - private void updateIconUIAnimations() { - if (!textEditor.getComponent().isVisible()) { - return; - } - for (PerfGutterIconRenderer marker : perfMarkers.values()) { - marker.updateUI(true); - } - } - - private void removeHighlightersFromEditor() { - final List highlighters = new ArrayList<>(); - final MarkupModel markupModel = textEditor.getEditor().getMarkupModel(); - - for (PerfGutterIconRenderer marker : perfMarkers.values()) { - markupModel.removeHighlighter(marker.getHighlighter()); - } - perfMarkers.clear(); - setHasDecorations(false); - } - - public void flushDecorations() { - if (hasDecorations && textEditor.isValid()) { - setHasDecorations(false); - ApplicationManager.getApplication().invokeLater(this::removeHighlightersFromEditor); - } - } - - @Override - public void dispose() { - textEditor.getEditor().removeEditorMouseListener(this); - flushDecorations(); - } - - @Override - public void mousePressed(@NotNull EditorMouseEvent e) { - } - - @Override - public void mouseClicked(@NotNull EditorMouseEvent e) { - } - - @Override - public void mouseReleased(@NotNull EditorMouseEvent e) { - } - - @Override - public void mouseEntered(EditorMouseEvent e) { - final EditorMouseEventArea area = e.getArea(); - if (!hoveredOverLineMarkerArea && - area == EditorMouseEventArea.LINE_MARKERS_AREA || - area == EditorMouseEventArea.FOLDING_OUTLINE_AREA || - area == EditorMouseEventArea.LINE_NUMBERS_AREA) { - // Hover is over the gutter area. - setHoverState(true); - } - } - - @Override - public void mouseExited(EditorMouseEvent e) { - final EditorMouseEventArea area = e.getArea(); - setHoverState(false); - // TODO(jacobr): hovers over a tooltip triggered by a gutter icon should - // be considered a hover of the gutter but this logic does not handle that - // case correctly. - } - - private void setHoverState(boolean value) { - if (value != hoveredOverLineMarkerArea) { - hoveredOverLineMarkerArea = value; - updateIconUIAnimations(); - } - } - - @Override - public void clear() { - stats.clear(); - removeHighlightersFromEditor(); - } -} - -/** - * This class renders the animated gutter icons used to visualize how much - * widget repaint or rebuild work is happening. - *

- * This is a somewhat strange GutterIconRender in that we use it to orchestrate - * animating the color of the associated RangeHighlighter and changing the icon - * of the GutterIconRenderer when performance changes without requiring the - * GutterIconRenderer to be discarded. markupModel.fireAttributesChanged is - * used to notify the MarkupModel when state has changed and a rerender is - * required. - */ -class PerfGutterIconRenderer extends GutterIconRenderer { - - // Speed of the animation in radians per second. - private static final double ANIMATION_SPEED = 4.0; - - private final RangeHighlighter highlighter; - private final TextRange range; - private final EditorPerfModel perfModelForFile; - - // Tracked so we know when to notify that our icon has changed. - private Icon lastIcon; - - PerfGutterIconRenderer(TextRange range, - EditorPerfModel perfModelForFile, - RangeHighlighter highlighter) { - this.highlighter = highlighter; - this.range = range; - this.perfModelForFile = perfModelForFile; - final TextAttributes textAttributes = highlighter.getTextAttributes(null); - assert textAttributes != null; - textAttributes.setEffectType(EffectType.LINE_UNDERSCORE); - - updateUI(false); - } - - public boolean isNavigateAction() { - return isActive(); - } - - private FlutterApp getApp() { - return perfModelForFile.getApp(); - } - - private int getCurrentValue() { - return perfModelForFile.getStats().getCurrentValue(range); - } - - private int getDisplayValue() { - final int value = getCurrentValue(); - if (value == 0 && perfModelForFile.getAlwaysShowLineMarkers()) { - // This is the case where the value was previously non-zero but the app - // is idle so the value was reset. For all ui rendering logic we treat - // the value as 1 so that consistent coloring is used throughout the - // ui. Alternately we could use the original non-zero value but that - // could be more confusing to users. - return 1; - } - return value; - } - - private boolean isActive() { - return getDisplayValue() > 0; - } - - RangeHighlighter getHighlighter() { - return highlighter; - } - - /** - * Returns the action executed when the icon is left-clicked. - * - * @return the action instance, or null if no action is required. - */ - @Nullable - public AnAction getClickAction() { - return new AnAction() { - @Override - public void actionPerformed(@NotNull AnActionEvent event) { - if (isActive()) { - - final ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(getApp().getProject()); - final ToolWindow flutterPerfToolWindow = toolWindowManager.getToolWindow(FlutterPerformanceView.TOOL_WINDOW_ID); - if (flutterPerfToolWindow == null) { - return; - } - - if (flutterPerfToolWindow.isVisible()) { - showPerfViewMessage(); - return; - } - flutterPerfToolWindow.show(() -> showPerfViewMessage()); - } - } - }; - } - - private void showPerfViewMessage() { - final FlutterPerformanceView flutterPerfView = getApp().getProject().getService(FlutterPerformanceView.class); - flutterPerfView.showForAppRebuildCounts(getApp()); - final String message = "" + - getTooltipHtmlFragment() + - ""; - final Iterable current = perfModelForFile.getStats().getRangeStats(range); - if (current.iterator().hasNext()) { - final SummaryStats first = current.iterator().next(); - final XSourcePosition position = first.getLocation().getXSourcePosition(); - if (position != null) { - AsyncUtils.invokeLater(() -> { - position.createNavigatable(getApp().getProject()).navigate(true); - HintManager.getInstance().showInformationHint(perfModelForFile.getTextEditor().getEditor(), message); - }); - } - } - } - - @NotNull - @Override - public Alignment getAlignment() { - return Alignment.LEFT; - } - - @NotNull - @Override - public Icon getIcon() { - lastIcon = getIconInternal(); - return lastIcon; - } - - public Icon getIconInternal() { - return Icons.getIconForCount(getCurrentValue(), perfModelForFile.getAlwaysShowLineMarkers()); - } - - Color getErrorStripeMarkColor() { - // TODO(jacobr): tween from green or blue to red depending on the count. - final int count = getDisplayValue(); - if (count == 0) { - return null; - } - if (count >= Icons.HIGH_LOAD_THRESHOLD) { - return JBColor.YELLOW; - } - return JBColor.GRAY; - } - - public void updateUI(boolean repaint) { - final int count = getDisplayValue(); - final TextAttributes textAttributes = highlighter.getTextAttributes(null); - assert textAttributes != null; - boolean changed = false; - if (count > 0) { - Color targetColor = getErrorStripeMarkColor(); - if (EditorPerfDecorations.ANIMATE_WIDGET_NAME_HIGLIGHTS) { - final double animateTime = (double)(System.currentTimeMillis()) * 0.001; - // TODO(jacobr): consider tracking a start time for the individual - // animation instead of having all animations running in sync. - // 1.0 - Math.cos is used so that balance is 0.0 at the start of the animation - // and the value will vary from 0 to 1.0 - final double balance = (1.0 - Math.cos(animateTime * ANIMATION_SPEED)) * 0.5; - targetColor = ColorUtil.mix(JBColor.WHITE, targetColor, balance); - } - if (!targetColor.equals(textAttributes.getEffectColor())) { - textAttributes.setEffectColor(targetColor); - changed = true; - } - } - else { - textAttributes.setEffectColor(null); - } - final Color errorStripeColor = getErrorStripeMarkColor(); - highlighter.setErrorStripeMarkColor(errorStripeColor); - if (repaint && lastIcon != getIconInternal()) { - changed = true; - } - if (changed && repaint) { - final MarkupModel markupModel = perfModelForFile.getTextEditor().getEditor().getMarkupModel(); - ((MarkupModelEx)markupModel).fireAttributesChanged((RangeHighlighterEx)highlighter, true, false); - } - } - - @SuppressWarnings("StringConcatenationInsideStringBufferAppend") - String getTooltipHtmlFragment() { - final StringBuilder sb = new StringBuilder(); - boolean first = true; - for (SummaryStats stats : perfModelForFile.getStats().getRangeStats(range)) { - final String style = first ? "" : "margin-top: 8px"; - first = false; - sb.append("

"); - if (stats.getKind() == PerfReportKind.rebuild) { - sb.append("Rebuild"); - } - else if (stats.getKind() == PerfReportKind.repaint) { - sb.append("Repaint"); - } - sb.append(" counts for: " + stats.getDescription()); - sb.append("

"); - sb.append("

"); - sb.append("For last frame: " + stats.getValue(PerfMetric.lastFrame) + "
"); - sb.append("In past second: " + stats.getValue(PerfMetric.pastSecond) + "
"); - sb.append("Since entering the current screen: " + stats.getValue(PerfMetric.totalSinceEnteringCurrentScreen) + "
"); - sb.append("Since last hot reload/restart: " + stats.getValue(PerfMetric.total)); - sb.append("

"); - } - if (sb.isEmpty()) { - sb.append("

No widget rebuilds detected for line.

"); - } - return sb.toString(); - } - - @Override - public String getTooltipText() { - return "" + getTooltipHtmlFragment() + ""; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof PerfGutterIconRenderer other)) { - return false; - } - return other.getCurrentValue() == getCurrentValue(); - } - - @Override - public int hashCode() { - return getCurrentValue(); - } -} diff --git a/flutter-idea/src/io/flutter/perf/EditorPerfModel.java b/flutter-idea/src/io/flutter/perf/EditorPerfModel.java deleted file mode 100644 index 69567d29c3..0000000000 --- a/flutter-idea/src/io/flutter/perf/EditorPerfModel.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.fileEditor.TextEditor; -import io.flutter.run.daemon.FlutterApp; -import org.jetbrains.annotations.NotNull; - -/** - * View model for displaying perf stats for a TextEditor. - *

- * This model tracks the state defining how perf stats should be displayed in - * the text editor along with the actual perf stats accessible via the - * getStats method. - */ -public interface EditorPerfModel extends PerfModel, Disposable { - @NotNull - FilePerfInfo getStats(); - - @NotNull - TextEditor getTextEditor(); - - FlutterApp getApp(); - - boolean getAlwaysShowLineMarkers(); - - void setAlwaysShowLineMarkersOverride(boolean show); - - void setPerfInfo(@NotNull FilePerfInfo stats); -} diff --git a/flutter-idea/src/io/flutter/perf/FileLocationMapper.java b/flutter-idea/src/io/flutter/perf/FileLocationMapper.java deleted file mode 100644 index 8a18825ba6..0000000000 --- a/flutter-idea/src/io/flutter/perf/FileLocationMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.openapi.util.TextRange; -import org.jetbrains.annotations.Nullable; - -/** - * Mapper from a line column offset to a range of text associated with the identifier at an - * offset. - *

- * For example, if there is a constructor call at the specified line and offset then the - * name of the constructor called should be returned. - */ -public interface FileLocationMapper { - @Nullable - TextRange getIdentifierRange(int line, int column); - - @Nullable - String getText(@Nullable TextRange textRange); - - String getPath(); -} diff --git a/flutter-idea/src/io/flutter/perf/FileLocationMapperFactory.java b/flutter-idea/src/io/flutter/perf/FileLocationMapperFactory.java deleted file mode 100644 index 7b79f862e9..0000000000 --- a/flutter-idea/src/io/flutter/perf/FileLocationMapperFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2018 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.perf; - -public interface FileLocationMapperFactory { - FileLocationMapper create(String path); -} diff --git a/flutter-idea/src/io/flutter/perf/FilePerfInfo.java b/flutter-idea/src/io/flutter/perf/FilePerfInfo.java deleted file mode 100644 index 0e0dd1297b..0000000000 --- a/flutter-idea/src/io/flutter/perf/FilePerfInfo.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.Multimap; -import com.intellij.openapi.util.TextRange; - -import java.util.Arrays; -import java.util.Collection; - -/** - * Performance stats for a single source file. - *

- * Typically the TextRange objects correspond to widget constructor locations. - * A single constructor location may have multiple SummaryStats objects one for - * each kind of stats (Widget repaints, rebuilds, etc). - */ -class FilePerfInfo { - private final Multimap stats = LinkedListMultimap.create(); - long maxTimestamp = -1; - private final int[] totalForMetric = new int[PerfMetric.values().length]; - - public void clear() { - stats.clear(); - maxTimestamp = -1; - Arrays.fill(totalForMetric, 0); - } - - public Iterable getLocations() { - return stats.keySet(); - } - - public Iterable getStats() { - return stats.values(); - } - - public boolean hasLocation(TextRange range) { - return stats.containsKey(range); - } - - public int getTotalValue(PerfMetric metric) { - return totalForMetric[metric.ordinal()]; - } - - public int getValue(TextRange range, PerfMetric metric) { - final Collection entries = stats.get(range); - if (entries == null) { - return 0; - } - int count = 0; - for (SummaryStats entry : entries) { - count += entry.getValue(metric); - } - return count; - } - - Iterable getRangeStats(TextRange range) { - return stats.get(range); - } - - public long getMaxTimestamp() { - return maxTimestamp; - } - - public void add(TextRange range, SummaryStats entry) { - stats.put(range, entry); - for (PerfMetric metric : PerfMetric.values()) { - totalForMetric[metric.ordinal()] += entry.getValue(metric); - } - } - - public void markAppIdle() { - for (PerfMetric metric : PerfMetric.values()) { - if (metric.timeIntervalMetric) { - totalForMetric[metric.ordinal()] = 0; - } - } - - for (SummaryStats stats : stats.values()) { - stats.markAppIdle(); - } - } - - public int getCurrentValue(TextRange range) { - return getValue(range, PerfMetric.peakRecent); - } -} diff --git a/flutter-idea/src/io/flutter/perf/FilePerfModelFactory.java b/flutter-idea/src/io/flutter/perf/FilePerfModelFactory.java deleted file mode 100644 index ee36a34a4d..0000000000 --- a/flutter-idea/src/io/flutter/perf/FilePerfModelFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.openapi.fileEditor.TextEditor; - -public interface FilePerfModelFactory { - EditorPerfModel create(TextEditor textEditor); -} diff --git a/flutter-idea/src/io/flutter/perf/FlutterWidgetPerf.java b/flutter-idea/src/io/flutter/perf/FlutterWidgetPerf.java deleted file mode 100644 index f2fd5b55b6..0000000000 --- a/flutter-idea/src/io/flutter/perf/FlutterWidgetPerf.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.SetMultimap; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.intellij.concurrency.JobScheduler; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ui.EdtInvocationManager; -import io.flutter.utils.AsyncUtils; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import org.jetbrains.annotations.NotNull; - -import javax.swing.Timer; -import java.awt.event.ActionEvent; -import java.util.*; -import java.util.concurrent.TimeUnit; - -/** - * This class provides the glue code between code fetching performance - * statistics json from a running flutter application and the ui rendering the - * performance statistics directly within the text editors. - *

- * This class is written to be amenable to unittesting unlike - * FlutterWidgetPerfManager so try to put all complex logic in this class - * so that issues can be caught by unittests. - *

- * See EditorPerfDecorations which performs all of the concrete ui rendering - * and VmServiceWidgetPerfProvider which performs fetching of json from a - * production application. - */ -public class FlutterWidgetPerf implements Disposable, WidgetPerfListener { - - private static final Logger LOG = Logger.getInstance(FlutterWidgetPerf.class); - - public static final long IDLE_DELAY_MILISECONDS = 400; - - static class StatsForReportKind { - final Int2ObjectMap data = new Int2ObjectOpenHashMap<>(); - private int lastStartTime = -1; - private int lastNonEmptyReportTime = -1; - } - - // Retry requests if we do not receive a response within this interval. - private static final long REQUEST_TIMEOUT_INTERVAL = 2000; - - // Intentionally use a low FPS as the animations in EditorPerfDecorations - // are quite CPU intensive due to animating content in TextEditor windows. - private static final int UI_FPS = 8; - private boolean isDirty = true; - private boolean requestInProgress = false; - private long lastRequestTime; - - private final Set perfListeners = new HashSet<>(); - - /** - * Note: any access of editorDecorations contents must happen on the UI thread. - */ - private final Map editorDecorations = new HashMap<>(); - private final Int2ObjectMap knownLocationIds = new Int2ObjectOpenHashMap<>(); - private final SetMultimap locationsPerFile = HashMultimap.create(); - private final Map stats = new HashMap<>(); - - final Set currentEditors = new HashSet<>(); - private boolean profilingEnabled; - final Timer uiAnimationTimer; - @NotNull private final WidgetPerfProvider perfProvider; - private boolean isDisposed = false; - private final FilePerfModelFactory perfModelFactory; - private final FileLocationMapperFactory fileLocationMapperFactory; - private volatile long lastLocalPerfEventTime; - private final WidgetPerfLinter perfLinter; - - FlutterWidgetPerf(boolean profilingEnabled, - @NotNull WidgetPerfProvider perfProvider, - FilePerfModelFactory perfModelFactory, - FileLocationMapperFactory fileLocationMapperFactory) { - this.profilingEnabled = profilingEnabled; - this.perfProvider = perfProvider; - this.perfModelFactory = perfModelFactory; - this.fileLocationMapperFactory = fileLocationMapperFactory; - this.perfLinter = new WidgetPerfLinter(this, perfProvider); - - perfProvider.setTarget(this); - uiAnimationTimer = new Timer(1000 / UI_FPS, event -> { - AsyncUtils.invokeLater(() -> onFrame(event)); - }); - } - - // The logic for when requests are in progress is fragile. This helper - // method exists to we have a single place to instrument to track when - // request status changes to help debug issues,. - private void setRequestInProgress(boolean value) { - requestInProgress = value; - } - - private void onFrame(ActionEvent event) { - for (EditorPerfModel decorations : editorDecorations.values()) { - decorations.onFrame(); - } - - for (PerfModel model : perfListeners) { - model.onFrame(); - } - } - - private boolean isConnected() { - return perfProvider.isConnected(); - } - - public long getLastLocalPerfEventTime() { - return lastLocalPerfEventTime; - } - - /** - * Schedule a repaint of the widget perf information. - *

- * When.now schedules a repaint immediately. - *

- * When.soon will schedule a repaint shortly; that can get delayed by another request, with a maximum delay. - */ - @Override - public void requestRepaint(When when) { - if (!profilingEnabled) { - isDirty = false; - return; - } - isDirty = true; - - if (!isConnected() || (this.currentEditors.isEmpty() && this.perfListeners.isEmpty())) { - return; - } - - final long currentTime = System.currentTimeMillis(); - if (requestInProgress && (currentTime - lastRequestTime) < REQUEST_TIMEOUT_INTERVAL) { - return; - } - setRequestInProgress(true); - lastRequestTime = currentTime; - - final TextEditor[] editors = this.currentEditors.toArray(new TextEditor[0]); - AsyncUtils.invokeLater(() -> performRequest(editors)); - } - - @Override - public void onWidgetPerfEvent(PerfReportKind kind, JsonObject json) { - // Read access to the Document objects on background thread is needed so - // a ReadAction is required. Document objects are used to determine the - // widget names at specific locations in documents. - final Runnable action = () -> { - synchronized (this) { - final long startTimeMicros = json.get("startTime").getAsLong(); - final int startTimeMilis = (int)(startTimeMicros / 1000); - lastLocalPerfEventTime = System.currentTimeMillis(); - final StatsForReportKind statsForReportKind = getStatsForKind(kind); - if (statsForReportKind.lastStartTime > startTimeMilis) { - // We went backwards in time. There must have been a hot restart so - // clear all old stats. - statsForReportKind.data.forEach((key,entry) -> { - if(entry != null) { - entry.clear(); - } - }); - } - statsForReportKind.lastStartTime = startTimeMilis; - - // Prefer the new 'locations' format if it exists; else read from 'newLocations'. - if (json.has("locations")) { - final JsonObject fileLocationsMap = json.getAsJsonObject("locations"); - - for (Map.Entry entry : fileLocationsMap.entrySet()) { - final String path = entry.getKey(); - final FileLocationMapper locationMapper = fileLocationMapperFactory.create(path); - - final JsonObject locations = entry.getValue().getAsJsonObject(); - - final JsonArray ids = locations.getAsJsonArray("ids"); - final JsonArray lines = locations.getAsJsonArray("lines"); - final JsonArray columns = locations.getAsJsonArray("columns"); - final JsonArray names = locations.getAsJsonArray("names"); - - for (int i = 0; i < ids.size(); i++) { - final int id = ids.get(i).getAsInt(); - final int line = lines.get(i).getAsInt(); - final int column = columns.get(i).getAsInt(); - - final TextRange textRange = locationMapper.getIdentifierRange(line, column); - - final Location location = new Location( - locationMapper.getPath(), - line, - column, - id, - textRange, - names.get(i).getAsString() - ); - - final Location existingLocation = knownLocationIds.get(id); - if (existingLocation == null) { - addNewLocation(id, location); - } - else { - if (!location.equals(existingLocation)) { - // Cleanup all references to the old location as it is stale. - // This occurs if there is a hot restart or reload that we weren't aware of. - locationsPerFile.remove(existingLocation.path, existingLocation); - for (StatsForReportKind statsForKind : stats.values()) { - statsForKind.data.remove(id); - } - addNewLocation(id, location); - } - } - } - } - } - else if (json.has("newLocations")) { - final JsonObject newLocations = json.getAsJsonObject("newLocations"); - for (Map.Entry entry : newLocations.entrySet()) { - final String path = entry.getKey(); - final FileLocationMapper locationMapper = fileLocationMapperFactory.create(path); - final JsonArray entries = entry.getValue().getAsJsonArray(); - assert (entries.size() % 3 == 0); - for (int i = 0; i < entries.size(); i += 3) { - final int id = entries.get(i).getAsInt(); - final int line = entries.get(i + 1).getAsInt(); - final int column = entries.get(i + 2).getAsInt(); - final TextRange textRange = locationMapper.getIdentifierRange(line, column); - String name = locationMapper.getText(textRange); - if (name == null) { - name = ""; - } - final Location location = new Location(locationMapper.getPath(), line, column, id, textRange, name); - - final Location existingLocation = knownLocationIds.get(id); - if (existingLocation == null) { - addNewLocation(id, location); - } - else { - if (!location.equals(existingLocation)) { - // Cleanup all references to the old location as it is stale. - // This occurs if there is a hot restart or reload that we weren't aware of. - locationsPerFile.remove(existingLocation.path, existingLocation); - for (StatsForReportKind statsForKind : stats.values()) { - statsForKind.data.remove(id); - } - addNewLocation(id, location); - } - } - } - } - } - - final StatsForReportKind statsForKind = getStatsForKind(kind); - final PerfSourceReport report = new PerfSourceReport(json.getAsJsonArray("events"), kind, startTimeMicros); - if (!report.getEntries().isEmpty()) { - statsForReportKind.lastNonEmptyReportTime = startTimeMilis; - } - for (PerfSourceReport.Entry entry : report.getEntries()) { - final int locationId = entry.locationId; - SlidingWindowStats statsForLocation = statsForKind.data.get(locationId); - if (statsForLocation == null) { - statsForLocation = new SlidingWindowStats(); - statsForKind.data.put(locationId, statsForLocation); - } - statsForLocation.add(entry.total, startTimeMilis); - } - } - }; - - final Application application = ApplicationManager.getApplication(); - if (application != null) { - application.runReadAction(action); - } - else { - // Unittest case. - action.run(); - } - } - - @Override - public void onNavigation() { - synchronized (this) { - for (StatsForReportKind statsForKind : stats.values()) { - statsForKind.data.forEach((key, entry) -> { - if (entry != null) { - entry.onNavigation(); - } - }); - } - } - } - - @Override - public void addPerfListener(PerfModel listener) { - perfListeners.add(listener); - } - - @Override - public void removePerfListener(PerfModel listener) { - perfListeners.remove(listener); - } - - private StatsForReportKind getStatsForKind(PerfReportKind kind) { - StatsForReportKind report = stats.get(kind); - if (report == null) { - report = new StatsForReportKind(); - stats.put(kind, report); - } - return report; - } - - private void addNewLocation(int id, Location location) { - knownLocationIds.put(id, location); - locationsPerFile.put(location.path, location); - } - - void setProfilingEnabled(boolean enabled) { - profilingEnabled = enabled; - } - - private void performRequest(TextEditor[] fileEditors) { - assert EdtInvocationManager.getInstance().isEventDispatchThread(); - - if (!profilingEnabled) { - setRequestInProgress(false); - return; - } - - final Multimap editorForPath = LinkedListMultimap.create(); - final List uris = new ArrayList<>(); - for (TextEditor editor : fileEditors) { - final VirtualFile file = editor.getFile(); - if (file == null) { - continue; - } - final String uri = file.getPath(); - editorForPath.put(uri, editor); - uris.add(uri); - } - if (uris.isEmpty() && perfListeners.isEmpty()) { - setRequestInProgress(false); - return; - } - - isDirty = false; - - showReports(editorForPath); - } - - private void showReports(Multimap editorForPath) { - // True if any of the EditorPerfDecorations want to animate. - boolean animate = false; - - synchronized (this) { - for (String path : editorForPath.keySet()) { - for (TextEditor fileEditor : editorForPath.get(path)) { - if (!fileEditor.isValid()) return; - final EditorPerfModel editorDecoration = editorDecorations.get(fileEditor); - if (editorDecoration != null) { - if (!perfProvider.shouldDisplayPerfStats(fileEditor)) { - editorDecoration.clear(); - continue; - } - final FilePerfInfo fileStats = buildSummaryStats(fileEditor); - editorDecoration.setPerfInfo(fileStats); - if (editorDecoration.isAnimationActive()) { - animate = true; - } - } - } - } - } - - if (!animate) { - for (PerfModel listener : perfListeners) { - if (listener.isAnimationActive()) { - animate = true; - break; - } - } - } - - if (animate != uiAnimationTimer.isRunning()) { - if (animate) { - uiAnimationTimer.start(); - } - else { - uiAnimationTimer.stop(); - } - } - performRequestFinish(); - } - - private FilePerfInfo buildSummaryStats(TextEditor fileEditor) { - final FilePerfInfo fileStats = new FilePerfInfo(); - if (fileEditor.getFile() == null) { - return fileStats; - } - - final String path = fileEditor.getFile().getPath(); - for (PerfReportKind kind : PerfReportKind.values()) { - final StatsForReportKind forKind = stats.get(kind); - if (forKind == null) { - continue; - } - final Int2ObjectMap data = forKind.data; - for (Location location : locationsPerFile.get(path)) { - final SlidingWindowStats entry = data.get(location.id); - if (entry == null) { - continue; - } - final TextRange range = location.textRange; - if (range == null) { - continue; - } - fileStats.add( - range, - new SummaryStats( - kind, - new SlidingWindowStatsSummary(entry, forKind.lastStartTime, location), - location.name - ) - ); - } - } - return fileStats; - } - - private void performRequestFinish() { - setRequestInProgress(false); - JobScheduler.getScheduler().schedule(this::maybeNotifyIdle, IDLE_DELAY_MILISECONDS, TimeUnit.MILLISECONDS); - if (isDirty) { - requestRepaint(When.soon); - } - } - - private void maybeNotifyIdle() { - if (isDisposed) { - return; - } - if (System.currentTimeMillis() >= lastRequestTime + IDLE_DELAY_MILISECONDS) { - AsyncUtils.invokeLater(() -> { - for (EditorPerfModel decoration : editorDecorations.values()) { - decoration.markAppIdle(); - } - for (PerfModel listener : perfListeners) { - listener.markAppIdle(); - } - uiAnimationTimer.stop(); - }); - } - } - - public void showFor(Set editors) { - AsyncUtils.invokeAndWait(() -> { - currentEditors.clear(); - currentEditors.addAll(editors); - - // Harvest old editors. - harvestInvalidEditors(editors); - - for (TextEditor fileEditor : currentEditors) { - // Create a new EditorPerfModel if necessary. - if (!editorDecorations.containsKey(fileEditor)) { - editorDecorations.put(fileEditor, perfModelFactory.create(fileEditor)); - } - } - requestRepaint(When.now); - }); - } - - private void harvestInvalidEditors(Set newEditors) { - final Iterator editors = editorDecorations.keySet().iterator(); - - while (editors.hasNext()) { - final TextEditor editor = editors.next(); - if (!editor.isValid() || (newEditors != null && !newEditors.contains(editor))) { - final EditorPerfModel editorPerfDecorations = editorDecorations.get(editor); - editors.remove(); - Disposer.dispose(editorPerfDecorations); - } - } - } - - public void setAlwaysShowLineMarkersOverride(boolean show) { - for (EditorPerfModel model : editorDecorations.values()) { - model.setAlwaysShowLineMarkersOverride(show); - } - } - - @Override - public void dispose() { - if (isDisposed) { - return; - } - - this.isDisposed = true; - - if (uiAnimationTimer.isRunning()) { - uiAnimationTimer.stop(); - } - // TODO(jacobr): WidgetPerfProvider implements Disposer but its dispose method - // needs to be called manually rather than using the Disposer API - // because it is not registered for disposal using - // Disposer.register - perfProvider.dispose(); - AsyncUtils.invokeLater(() -> { - clearModels(); - - for (EditorPerfModel decorations : editorDecorations.values()) { - // TODO(jacobr): EditorPerfModel implements Disposer but its dispose method - // needs to be called manually rather than using the Disposer API - // because it is not registered for disposal using - // Disposer.register - decorations.dispose(); - } - editorDecorations.clear(); - perfListeners.clear(); - }); - } - - /** - * Note: this must be called on the UI thread. - */ - @VisibleForTesting() - public void clearModels() { - for (EditorPerfModel decorations : editorDecorations.values()) { - decorations.clear(); - } - for (PerfModel listener : perfListeners) { - listener.clear(); - } - } - - protected void clear() { - AsyncUtils.invokeLater(this::clearModels); - } - - protected void onRestart() { - AsyncUtils.invokeLater(() -> { - // The app has restarted. Location ids may not be valid. - knownLocationIds.clear(); - stats.clear(); - clearModels(); - }); - } - - public WidgetPerfLinter getPerfLinter() { - return perfLinter; - } - - public ArrayList buildAllSummaryStats(Set textEditors) { - final ArrayList stats = new ArrayList<>(); - synchronized (this) { - for (TextEditor textEditor : textEditors) { - stats.add(buildSummaryStats(textEditor)); - } - } - return stats; - } - - public ArrayList getStatsForMetric(ArrayList metrics, PerfReportKind kind) { - final ArrayList entries = new ArrayList<>(); - synchronized (this) { - final StatsForReportKind forKind = stats.get(kind); - if (forKind != null) { - final int time = forKind.lastNonEmptyReportTime; - forKind.data.forEach((locationId, stats) -> { - for (PerfMetric metric : metrics) { - if (stats.getValue(metric, time) > 0) { - final Location location = knownLocationIds.get(locationId); - // TODO(jacobr): consider changing this check for - // location != null to an assert once the edge case leading to - // occassional null locations has been fixed. I expect the edge - // case occurs because we are sometimes including a few stats - // from before a hot restart due to an incorrect ordering for - // when the events occur. In any case, the extra != null check - // is harmless and ensures the UI display is robust at the cost - // of perhaps ommiting a little likely stale data. - // See https://github.com/flutter/flutter-intellij/issues/2892 - if (location != null) { - entries.add(new SlidingWindowStatsSummary( - stats, - time, - location - )); - } - } - } - }); - } - } - return entries; - } -} diff --git a/flutter-idea/src/io/flutter/perf/FlutterWidgetPerfManager.java b/flutter-idea/src/io/flutter/perf/FlutterWidgetPerfManager.java deleted file mode 100644 index 78c052d950..0000000000 --- a/flutter-idea/src/io/flutter/perf/FlutterWidgetPerfManager.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.fileEditor.*; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.messages.MessageBusConnection; -import io.flutter.FlutterUtils; -import io.flutter.run.FlutterAppManager; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.StreamSubscription; -import io.flutter.view.FlutterViewMessages; -import io.flutter.vmService.ServiceExtensions; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * A singleton for the current Project. This class watches for changes to the - * current Flutter app, and orchestrates displaying rebuild counts and other - * widget performance stats for widgets created in the active source files. - * Performance stats are displayed directly in the TextEditor windows so that - * users can see them as they look at the source code. - *

- * Rebuild counts provide an easy way to understand the coarse grained - * performance of an application and avoid common pitfalls. - *

- * FlutterWidgetPerfManager tracks which source files are visible and - * passes that information to FlutterWidgetPerf which performs the work to - * actually fetch performance information and display them. - */ -public class FlutterWidgetPerfManager implements Disposable, FlutterApp.FlutterAppListener { - - // Whether each of the performance metrics tracked should be tracked by - // default when starting a new application. - public static boolean trackRebuildWidgetsDefault = false; - public static boolean trackRepaintWidgetsDefault = false; - - public FlutterWidgetPerf getCurrentStats() { - return currentStats; - } - - private FlutterWidgetPerf currentStats; - private FlutterApp app; - private final Project project; - private boolean trackRebuildWidgets = trackRebuildWidgetsDefault; - private boolean trackRepaintWidgets = trackRepaintWidgetsDefault; - private boolean debugIsActive; - - private final Set listeners = new HashSet<>(); - - /** - * File editors visible to the user that might contain widgets. - */ - private Set lastSelectedEditors = new HashSet<>(); - - private final List> streamSubscriptions = new ArrayList<>(); - - - @NotNull - public Set getSelectedEditors() { - return lastSelectedEditors; - } - - private FlutterWidgetPerfManager(@NotNull Project project) { - this.project = project; - - Disposer.register(project, this); - - FlutterAppManager.getInstance(project).getActiveAppAsStream().listen( - this::updateCurrentAppChanged, true); - - project.getMessageBus().connect().subscribe( - FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (event) -> debugActive(project, event) - ); - - final MessageBusConnection connection = project.getMessageBus().connect(project); - - updateSelectedEditors(); - connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { - public void selectionChanged(@NotNull FileEditorManagerEvent event) { - if (updateSelectedEditors()) { - notifyPerf(); - } - } - }); - } - - /** - * @return whether the set of selected editors actually changed. - */ - private boolean updateSelectedEditors() { - final FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); - if(fileEditorManager == null) { - return false; - } - final FileEditor[] editors = fileEditorManager.getSelectedEditors(); - final Set newEditors = new HashSet<>(); - for (FileEditor editor : editors) { - if (editor instanceof TextEditor) { - final VirtualFile file = editor.getFile(); - if (FlutterUtils.couldContainWidgets(project, file)) { - newEditors.add((TextEditor)editor); - } - } - } - if (newEditors.equals(lastSelectedEditors)) { - return false; - } - lastSelectedEditors = newEditors; - return true; - } - - /** - * Initialize the rebuild count manager for the given project. - */ - public static void init(@NotNull Project project) { - // Call getInstance() will init FlutterWidgetPerfManager for the given project. - getInstance(project); - } - - @Nullable - public static FlutterWidgetPerfManager getInstance(@NotNull Project project) { - return project.getService(FlutterWidgetPerfManager.class); - } - - public boolean isTrackRebuildWidgets() { - return trackRebuildWidgets; - } - - public void setTrackRebuildWidgets(boolean value) { - if (value == trackRebuildWidgets) { - return; - } - trackRebuildWidgets = value; - onProfilingFlagsChanged(); - if (debugIsActive && app != null && app.isSessionActive()) { - updateTrackWidgetRebuilds(); - } - } - - public boolean isTrackRepaintWidgets() { - return trackRepaintWidgets; - } - - public void setTrackRepaintWidgets(boolean value) { - if (value == trackRepaintWidgets) { - return; - } - trackRepaintWidgets = value; - onProfilingFlagsChanged(); - if (debugIsActive && app != null && app.isSessionActive()) { - updateTrackWidgetRepaints(); - } - } - - private void onProfilingFlagsChanged() { - if (currentStats != null) { - currentStats.setProfilingEnabled(isProfilingEnabled()); - } - } - - private boolean isProfilingEnabled() { - return trackRebuildWidgets || trackRepaintWidgets; - } - - private void debugActive(Project project, FlutterViewMessages.FlutterDebugEvent event) { - debugIsActive = true; - - if (app == null) { - return; - } - - app.addStateListener(this); - syncBooleanServiceExtension(ServiceExtensions.trackRebuildWidgets.getExtension(), () -> trackRebuildWidgets); - syncBooleanServiceExtension(ServiceExtensions.trackRepaintWidgets.getExtension(), () -> trackRepaintWidgets); - - currentStats = new FlutterWidgetPerf( - isProfilingEnabled(), - new VmServiceWidgetPerfProvider(app), - (TextEditor textEditor) -> new EditorPerfDecorations(textEditor, app), - path -> new DocumentFileLocationMapper(path, app.getProject()) - ); - - for (PerfModel listener : listeners) { - currentStats.addPerfListener(listener); - } - } - - public void stateChanged(FlutterApp.State newState) { - switch (newState) { - case RELOADING: - if (currentStats != null) currentStats.clear(); - break; - case RESTARTING: - if (currentStats != null) currentStats.onRestart(); - break; - case STARTED: - notifyPerf(); - break; - } - } - - public void notifyAppRestarted() { - currentStats.clear(); - } - - public void notifyAppReloaded() { - currentStats.clear(); - } - - private void updateTrackWidgetRebuilds() { - app.maybeCallBooleanExtension(ServiceExtensions.trackRebuildWidgets.getExtension(), trackRebuildWidgets) - .whenCompleteAsync((v, e) -> notifyPerf()); - } - - private void updateTrackWidgetRepaints() { - app.maybeCallBooleanExtension(ServiceExtensions.trackRepaintWidgets.getExtension(), trackRepaintWidgets) - .whenCompleteAsync((v, e) -> notifyPerf()); - } - - private void syncBooleanServiceExtension(String serviceExtension, Computable valueProvider) { - final StreamSubscription subscription = app.hasServiceExtension(serviceExtension, (supported) -> { - if (supported) { - app.callBooleanExtension(serviceExtension, valueProvider.compute()); - } - }); - if (subscription != null) { - streamSubscriptions.add(subscription); - } - } - - private void updateCurrentAppChanged(@Nullable FlutterApp app) { - // TODO(jacobr): we currently only support showing stats for the last app - // that was run. After the initial CL lands we should fix this to track - // multiple running apps if needed. The most important use case is if the - // user has one profile app and one debug app running at the same time. - // We should track stats for all running apps and display the aggregated - // stats. A well behaved flutter app should not be painting frames very - // frequently when a user is not interacting with it so showing aggregated - // stats for all apps should match user expectations without forcing users - // to manage which app they have selected. - if (app == this.app) { - return; - } - debugIsActive = false; - - if (this.app != null) { - this.app.removeStateListener(this); - } - - this.app = app; - - for (StreamSubscription subscription : streamSubscriptions) { - subscription.dispose(); - } - streamSubscriptions.clear(); - - if (currentStats != null) { - currentStats.dispose(); - currentStats = null; - } - } - - private void notifyPerf() { - if (!trackRepaintWidgets && !trackRebuildWidgets && currentStats != null) { - // TODO(jacobr): consider just marking as idle. - currentStats.clear(); - } - - if (currentStats == null) { - return; - } - - if (lastSelectedEditors.isEmpty()) { - currentStats.showFor(lastSelectedEditors); - return; - } - final Set editors = new HashSet<>(); - for (TextEditor editor : lastSelectedEditors) { - final VirtualFile file = editor.getFile(); - if (file != null && - !app.isReloading() || !app.isLatestVersionRunning(file)) { - // We skip querying files that have been modified locally as we - // cannot safely display the profile information so there is no - // point in tracking it. - editors.add(editor); - } - } - currentStats.showFor(editors); - } - - @Override - public void dispose() { - if (currentStats != null) { - currentStats.dispose(); - currentStats = null; - listeners.clear(); - } - } - - public void addPerfListener(PerfModel listener) { - listeners.add(listener); - if (currentStats != null) { - currentStats.addPerfListener(listener); - } - } - - public void removePerfListener(PerfModel listener) { - listeners.remove(listener); - if (currentStats != null) { - currentStats.removePerfListener(listener); - } - } -} diff --git a/flutter-idea/src/io/flutter/perf/Icons.java b/flutter-idea/src/io/flutter/perf/Icons.java deleted file mode 100644 index 955fdf3ede..0000000000 --- a/flutter-idea/src/io/flutter/perf/Icons.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import icons.FlutterIcons; -import io.flutter.utils.AnimatedIcon; - -import javax.swing.*; -import java.awt.*; - -public class Icons { - static final AnimatedIcon RED_PROGRESS = new RedProgress(); - static final AnimatedIcon YELLOW_PROGRESS = new YellowProgress(); - static final AnimatedIcon NORMAL_PROGRESS = new AnimatedIcon.Grey(); - private static final Icon EMPTY_ICON = new EmptyIcon(FlutterIcons.CustomInfo); - // Threshold for statistics to use red icons. - static final int HIGH_LOAD_THRESHOLD = 2; - - public static Icon getIconForCount(int count, boolean showInactive) { - if (count == 0) { - return showInactive ? FlutterIcons.CustomInfo : EMPTY_ICON; - } - if (count >= HIGH_LOAD_THRESHOLD) { - return YELLOW_PROGRESS; - } - return NORMAL_PROGRESS; - } - - private static class EmptyIcon implements Icon { - final Icon iconForSize; - - EmptyIcon(Icon iconForSize) { - this.iconForSize = iconForSize; - } - - @Override - public void paintIcon(Component c, Graphics g, int x, int y) { - } - - @Override - public int getIconWidth() { - return iconForSize.getIconWidth(); - } - - @Override - public int getIconHeight() { - return iconForSize.getIconHeight(); - } - } - - // Spinning red progress icon - // - // TODO(jacobr): it would be nice to tint the icons programatically so that - // we could have a wider range of icon colors representing various repaint - // rates. - static final class RedProgress extends AnimatedIcon { - public RedProgress() { - super(150, - FlutterIcons.State.RedProgr_1, - FlutterIcons.State.RedProgr_2, - FlutterIcons.State.RedProgr_3, - FlutterIcons.State.RedProgr_4, - FlutterIcons.State.RedProgr_5, - FlutterIcons.State.RedProgr_6, - FlutterIcons.State.RedProgr_7, - FlutterIcons.State.RedProgr_8); - } - } - - static final class YellowProgress extends AnimatedIcon { - public YellowProgress() { - super(150, - FlutterIcons.State.YellowProgr_1, - FlutterIcons.State.YellowProgr_2, - FlutterIcons.State.YellowProgr_3, - FlutterIcons.State.YellowProgr_4, - FlutterIcons.State.YellowProgr_5, - FlutterIcons.State.YellowProgr_6, - FlutterIcons.State.YellowProgr_7, - FlutterIcons.State.YellowProgr_8); - } - } -} diff --git a/flutter-idea/src/io/flutter/perf/Location.java b/flutter-idea/src/io/flutter/perf/Location.java deleted file mode 100644 index beb7dea7cc..0000000000 --- a/flutter-idea/src/io/flutter/perf/Location.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2018 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 path. - */ -package io.flutter.perf; - -import com.google.common.base.Objects; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.xdebugger.XSourcePosition; -import com.intellij.xdebugger.impl.XSourcePositionImpl; - -/** - * Source location within a file with an id that is unique with the current - * running Flutter application. - */ -public class Location { - - public Location(String path, int line, int column, int id, TextRange textRange, String name) { - this.path = path; - this.line = line; - this.column = column; - this.id = id; - this.textRange = textRange; - this.name = name; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Location other)) return false; - return Objects.equal(line, other.line) - && Objects.equal(column, other.column) - && Objects.equal(path, other.path) - && Objects.equal(id, other.id); - } - - @Override - public int hashCode() { - return Objects.hashCode(line, column, path, id); - } - - public final int line; - public final int column; - public final int id; - public final String path; - - public XSourcePosition getXSourcePosition() { - final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path); - - if (file == null) { - return null; - } - return XSourcePositionImpl.create(file, line - 1, column - 1); - } - - /** - * Range in the file corresponding to the identify name at this location. - */ - public final TextRange textRange; - - /** - * Text of the identifier at this location. - *

- * Typically this is the name of a widget class. - */ - public final String name; -} diff --git a/flutter-idea/src/io/flutter/perf/PerfMetric.java b/flutter-idea/src/io/flutter/perf/PerfMetric.java deleted file mode 100644 index 6985f6db92..0000000000 --- a/flutter-idea/src/io/flutter/perf/PerfMetric.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018 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.perf; - -/** - * Performance metrics. - *

- * Additional performance metrics can be defined without requiring changes to - * package:flutter as computation of metrics is performed in Java using - * the SlidingWindowStats class. - */ -public enum PerfMetric { - lastFrame("Last Frame", true), - peakRecent("Peak Recent", true), - pastSecond("Past Second", true), - totalSinceEnteringCurrentScreen("Current Screen", false), - total("Total", false); - - public final String name; - public final boolean timeIntervalMetric; - - PerfMetric(String name, boolean timeIntervalMetric) { - this.name = name; - this.timeIntervalMetric = timeIntervalMetric; - } - - @Override - public String toString() { - return name; - } -} diff --git a/flutter-idea/src/io/flutter/perf/PerfModel.java b/flutter-idea/src/io/flutter/perf/PerfModel.java deleted file mode 100644 index 6bf4ec35dd..0000000000 --- a/flutter-idea/src/io/flutter/perf/PerfModel.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2018 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.perf; - -/** - * Base class for all view models displaying performance stats - */ -public interface PerfModel { - void markAppIdle(); - - void clear(); - - void onFrame(); - - boolean isAnimationActive(); -} diff --git a/flutter-idea/src/io/flutter/perf/PerfReportKind.java b/flutter-idea/src/io/flutter/perf/PerfReportKind.java deleted file mode 100644 index 136349f9ea..0000000000 --- a/flutter-idea/src/io/flutter/perf/PerfReportKind.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2018 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.perf; - -/** - * Kinds of performance reports supported by package:flutter. - */ -public enum PerfReportKind { - repaint("repaint"), - rebuild("rebuild"); - - public final String name; - - PerfReportKind(String name) { - this.name = name; - } -} diff --git a/flutter-idea/src/io/flutter/perf/PerfSourceReport.java b/flutter-idea/src/io/flutter/perf/PerfSourceReport.java deleted file mode 100644 index c52b694ace..0000000000 --- a/flutter-idea/src/io/flutter/perf/PerfSourceReport.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.gson.JsonArray; - -import java.util.ArrayList; -import java.util.List; - -public class PerfSourceReport { - private final PerfReportKind kind; - - private static final int ENTRY_LENGTH = 2; - - private final List entries; - private final long startTime; - - public PerfSourceReport(JsonArray json, PerfReportKind kind, long startTime) { - this.kind = kind; - this.startTime = startTime; - assert (json.size() % ENTRY_LENGTH == 0); - entries = new ArrayList<>(json.size() / ENTRY_LENGTH); - for (int i = 0; i < json.size(); i += ENTRY_LENGTH) { - entries.add(new Entry( - json.get(i).getAsInt(), - json.get(i + 1).getAsInt() - )); - } - } - - PerfReportKind getKind() { - return kind; - } - - List getEntries() { - return entries; - } - - static class Entry { - public final int locationId; - public final int total; - - Entry(int locationId, int total) { - this.locationId = locationId; - this.total = total; - } - } -} diff --git a/flutter-idea/src/io/flutter/perf/PerfTip.java b/flutter-idea/src/io/flutter/perf/PerfTip.java deleted file mode 100644 index e6c0ce0558..0000000000 --- a/flutter-idea/src/io/flutter/perf/PerfTip.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import java.util.List; - -/** - * A performance tip describing a suggestion to optimize a Flutter application. - */ -public class PerfTip { - final PerfTipRule rule; - final List locations; - double confidence; - - PerfTip(PerfTipRule rule, List locations, double confidence) { - this.rule = rule; - this.locations = locations; - this.confidence = confidence; - } - - public PerfTipRule getRule() { - return rule; - } - - public String getMessage() { - return rule.getMessage(); - } - - /** - * Locations within the application that called the tip to trigger. - */ - public List getLocations() { - return locations; - } - - /** - * Confidence between zero and 1 that the rule should be applied. - */ - public double getConfidence() { - return confidence; - } - - public String getUrl() { - return rule.getUrl(); - } -} diff --git a/flutter-idea/src/io/flutter/perf/PerfTipRule.java b/flutter-idea/src/io/flutter/perf/PerfTipRule.java deleted file mode 100644 index 6a8465654d..0000000000 --- a/flutter-idea/src/io/flutter/perf/PerfTipRule.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.common.base.Objects; -import io.flutter.inspector.DiagnosticsNode; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Rule describing when to generate a performance tip. - *

- * In the future it would make sense to read the rule definitions in from a - * JSON file instead of hard coding the rules in this file. The set of rules - * defined in this file is hardly exaustive and the thresholds for when the - * rules activate could easily be made looser. - */ -public class PerfTipRule { - final PerfReportKind kind; - final int priority; - final String hackFileName; - final String message; - /** - * A unique identifier used for analytics. - */ - final String id; - final String url; - WidgetPattern pattern; - final int minProblemLocationsInSubtree; - final int minSinceNavigate; - final int minPerSecond; - final Icon icon; - - PerfTipRule( - PerfReportKind kind, - int priority, - String hackFileName, - String message, - String id, - String url, - WidgetPattern pattern, - int minProblemLocationsInSubtree, - int minSinceNavigate, - int minPerSecond, - Icon icon - ) { - this.kind = kind; - this.priority = priority; - this.hackFileName = hackFileName; - this.message = message; - this.id = id; - this.url = url; - this.pattern = pattern; - this.minProblemLocationsInSubtree = minProblemLocationsInSubtree; - this.minSinceNavigate = minSinceNavigate; - this.minPerSecond = minPerSecond; - this.icon = icon; - } - - static public WidgetPattern matchParent(String name) { - return new WidgetPattern(name, null); - } - - static public WidgetPattern matchWidget(String name) { - return new WidgetPattern(null, name); - } - - public String getId() { - return id; - } - - public static boolean equalTipRule(PerfTip a, PerfTip b) { - if (a == null || b == null) { - return a == b; - } - return Objects.equal(a.getRule(), b.getRule()); - } - - public static boolean equivalentPerfTips(List a, List b) { - if (a == null || b == null) { - return a == b; - } - if (a.size() != b.size()) { - return false; - } - for (int i = 0; i < a.size(); ++i) { - if (!equalTipRule(a.get(i), b.get(i))) { - return false; - } - } - return true; - } - - public String getMessage() { - return message; - } - - public String getUrl() { - return url; - } - - public Icon getIcon() { - return icon; - } - - String getHtmlFragmentDescription() { - return "

" + message + "

"; - } - - boolean maybeMatches(SummaryStats summary) { - if (!matchesFrequency(summary)) { - return false; - } - return pattern.widget == null || pattern.widget.equals(summary.getDescription()); - } - - boolean matchesFrequency(SummaryStats summary) { - return (minSinceNavigate > 0 && summary.getValue(PerfMetric.totalSinceEnteringCurrentScreen) >= minSinceNavigate) || - (minPerSecond > 0 && summary.getValue(PerfMetric.pastSecond) >= minPerSecond); - } - - - boolean matches(SummaryStats summary, Collection candidates, Map statsInFile) { - if (!maybeMatches(summary)) { - return false; - } - if (pattern.parentWidget != null) { - final boolean patternIsStateful = Objects.equal(pattern.parentWidget, "StatefulWidget"); - for (DiagnosticsNode candidate : candidates) { - if (ancestorMatches(statsInFile, patternIsStateful, candidate, candidate.getParent())) { - return true; - } - } - return false; - } - if (pattern.widget != null) { - for (DiagnosticsNode candidate : candidates) { - if (pattern.widget.equals(candidate.getWidgetRuntimeType())) { - return true; - } - } - } - return false; - } - - private boolean ancestorMatches(Map statsInFile, - boolean patternIsStateful, - DiagnosticsNode candidate, - DiagnosticsNode parent) { - if (parent == null) { - return false; - } - if ((parent.isStateful() && patternIsStateful) || (pattern.parentWidget.equals(parent.getWidgetRuntimeType()))) { - return minProblemLocationsInSubtree <= 1 || minProblemLocationsInSubtree <= countSubtreeMatches(candidate, statsInFile); - } - parent = parent.getParent(); - if (parent != null && Objects.equal(parent.getCreationLocation().getFile(), candidate.getCreationLocation().getFile())) { - // Keep walking up the tree until we hit a different file. - // TODO(jacobr): this is a bit of an ugly heuristic. Think of a cleaner - // way of expressing this concept. In reality we could probably force the - // ancestor to be in the same build method. - return ancestorMatches(statsInFile, patternIsStateful, candidate, parent); - } - return false; - } - - // TODO(jacobr): this method might be slow in degenerate cases if an extreme - // number of locations in a source file match a rule. We could memoize match - // counts to avoid a possible O(n^2) algorithm worst case. - private int countSubtreeMatches(DiagnosticsNode candidate, Map statsInFile) { - final int id = candidate.getLocationId(); - int matches = 0; - if (id >= 0) { - final SummaryStats stats = statsInFile.get(id); - if (stats != null && maybeMatches(stats)) { - matches += 1; - } - } - final ArrayList children = candidate.getChildren().getNow(null); - if (children != null) { - for (DiagnosticsNode child : children) { - matches += countSubtreeMatches(child, statsInFile); - } - } - return matches; - } - - /** - * Pattern describing expectations for the names of a widget and the name of - * its parent in the widget tree. - */ - static public class WidgetPattern { - final String parentWidget; - final String widget; - - WidgetPattern(String parentWidget, String widget) { - this.parentWidget = parentWidget; - this.widget = widget; - } - } -} diff --git a/flutter-idea/src/io/flutter/perf/SlidingWindowStats.java b/flutter-idea/src/io/flutter/perf/SlidingWindowStats.java deleted file mode 100644 index a807351165..0000000000 --- a/flutter-idea/src/io/flutter/perf/SlidingWindowStats.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2018 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.perf; - -/** - * Class for accumulating sliding window performance stats optimized for fast - * performance and stable memory usage. - */ -class SlidingWindowStats { - // This lets as track a bit over 3 seconds at 60fps. - // TODO(jacobr): consider a longer sliding window length - // if we care about replaying historic stats for a longer - // period of time. - static final int _windowLength = 200 * 2; - /// Array of timestamp followed by count. - final int[] _window; - int _next = 0; - int _start = 0; - - int _total = 0; - int _totalSinceNavigation = 0; - - SlidingWindowStats() { - _window = new int[_windowLength]; - } - - int getTotal() { - return _total; - } - - int getTotalSinceNavigation() { - return _totalSinceNavigation; - } - - void clear() { - _next = 0; - _start = 0; - _total = 0; - _totalSinceNavigation = 0; - } - - void onNavigation() { - _totalSinceNavigation = 0; - } - - int getTotalWithinWindow(int windowStart) { - if (_next == _start) { - return 0; - } - final int end = _start >= 0 ? _start : _next; - int i = _next; - int count = 0; - while (true) { - i -= 2; - if (i < 0) { - i += _windowLength; - } - - if (_window[i] < windowStart) { - break; - } - count += _window[i + 1]; - if (i == end) { - break; - } - } - return count; - } - - void add(int count, int timeStamp) { - _total += count; - _totalSinceNavigation += count; - if (_start != _next) { - int last = _next - 2; - if (last < 0) { - last += _windowLength; - } - final int lastTimeStamp = _window[last]; - if (lastTimeStamp == timeStamp) { - _window[last + 1] += count; - return; - } - // The sliding window assumes timestamps must be given in increasing - // order. - assert (lastTimeStamp < timeStamp); - } - _window[_next] = timeStamp; - _window[_next + 1] = count; - _next += 2; - if (_next == _windowLength) { - _next = 0; - } - if (_start == _next) { - // The entire sliding window is now full so no need - // to track an explicit start. - _start = -1; - } - } - - public int getPeakWithinWindow(int windowStart) { - if (_next == _start) { - return 0; - } - final int end = _start >= 0 ? _start : _next; - int i = _next; - int peakValue = 0; - while (true) { - i -= 2; - if (i < 0) { - i += _windowLength; - } - - if (_window[i] < windowStart) { - break; - } - peakValue = Math.max(peakValue, _window[i + 1]); - if (i == end) { - break; - } - } - return peakValue; - } - - public int getValue(PerfMetric metric, int currentTime) { - return switch (metric) { - case total -> getTotal(); - case pastSecond -> getTotalWithinWindow(currentTime - 999); - case lastFrame -> getPeakWithinWindow(currentTime); - case peakRecent -> getPeakWithinWindow(currentTime - 499); - case totalSinceEnteringCurrentScreen -> getTotalSinceNavigation(); - default -> 0; - }; - } -} diff --git a/flutter-idea/src/io/flutter/perf/SlidingWindowStatsSummary.java b/flutter-idea/src/io/flutter/perf/SlidingWindowStatsSummary.java deleted file mode 100644 index ded8121f6a..0000000000 --- a/flutter-idea/src/io/flutter/perf/SlidingWindowStatsSummary.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import org.jetbrains.annotations.NotNull; - -/** - * Snapshot of a SlidingWindowStats object for a specific time. - */ -public class SlidingWindowStatsSummary { - private final int[] cachedStats; - private final @NotNull Location location; - - public SlidingWindowStatsSummary(@NotNull SlidingWindowStats stats, int currentTime, @NotNull Location location) { - cachedStats = new int[PerfMetric.values().length]; - for (PerfMetric metric : PerfMetric.values()) { - cachedStats[metric.ordinal()] = stats.getValue(metric, currentTime); - } - this.location = location; - } - - public @NotNull - Location getLocation() { - return location; - } - - public int getValue(PerfMetric metric) { - return cachedStats[metric.ordinal()]; - } -} diff --git a/flutter-idea/src/io/flutter/perf/SummaryStats.java b/flutter-idea/src/io/flutter/perf/SummaryStats.java deleted file mode 100644 index b90c952ba5..0000000000 --- a/flutter-idea/src/io/flutter/perf/SummaryStats.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2018 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.perf; - -/** - * Statistics summarizing how frequently an event has occurred overall and in - * the past second. - */ -public class SummaryStats { - private final PerfReportKind kind; - private final SlidingWindowStatsSummary entry; - private final String description; - boolean active = true; - - SummaryStats(PerfReportKind kind, SlidingWindowStatsSummary entry, String description) { - this.kind = kind; - this.entry = entry; - this.description = description; - } - - public PerfReportKind getKind() { - return kind; - } - - public int getValue(PerfMetric metric) { - if (metric.timeIntervalMetric && !active) { - return 0; - } - return entry.getValue(metric); - } - - public void markAppIdle() { - active = false; - } - - public String getDescription() { - return description; - } - - public Location getLocation() { - return entry.getLocation(); - } -} diff --git a/flutter-idea/src/io/flutter/perf/VmServiceWidgetPerfProvider.java b/flutter-idea/src/io/flutter/perf/VmServiceWidgetPerfProvider.java deleted file mode 100644 index f7673edb9e..0000000000 --- a/flutter-idea/src/io/flutter/perf/VmServiceWidgetPerfProvider.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.gson.JsonObject; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.text.StringUtil; -import io.flutter.inspector.DiagnosticsNode; -import io.flutter.inspector.InspectorService; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.StreamSubscription; -import io.flutter.utils.VmServiceListenerAdapter; -import io.flutter.vmService.VMServiceManager; -import org.dartlang.vm.service.VmService; -import org.dartlang.vm.service.VmServiceListener; -import org.dartlang.vm.service.element.Event; -import org.dartlang.vm.service.element.IsolateRef; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.CompletableFuture; - -public class VmServiceWidgetPerfProvider implements WidgetPerfProvider { - @NotNull final FlutterApp.FlutterAppListener appListener; - @NotNull final FlutterApp app; - private VmServiceListener vmServiceListener; - private WidgetPerfListener target; - private boolean started; - private boolean isStarted; - private boolean isDisposed = false; - private boolean connected; - private StreamSubscription isolateRefStreamSubscription; - private CompletableFuture inspectorService; - - VmServiceWidgetPerfProvider(@NotNull FlutterApp app) { - this.app = app; - // start listening for frames, reload and restart events - appListener = new FlutterApp.FlutterAppListener() { - @Override - public void stateChanged(FlutterApp.State newState) { - if (!started && app.isStarted()) { - started = true; - requestRepaint(When.now); - } - } - - @Override - public void notifyAppReloaded() { - requestRepaint(When.now); - } - - @Override - public void notifyAppRestarted() { - requestRepaint(When.now); - } - - @Override - public void notifyFrameRendered() { - requestRepaint(When.soon); - } - - public void notifyVmServiceAvailable(VmService vmService) { - setupConnection(vmService); - } - }; - - app.addStateListener(appListener); - - if (app.getVmService() != null) { - setupConnection(app.getVmService()); - } - started = app.isStarted(); - } - - public boolean isStarted() { - return started; - } - - public void setTarget(WidgetPerfListener widgetPerfListener) { - this.target = widgetPerfListener; - } - - private void requestRepaint(When now) { - if (target != null) { - target.requestRepaint(now); - } - } - - private void onWidgetPerfEvent(PerfReportKind kind, JsonObject json) { - if (target != null) { - target.onWidgetPerfEvent(kind, json); - } - } - - private void onNavigation() { - if (target != null) { - target.onNavigation(); - } - } - - @Override - public void dispose() { - app.removeStateListener(appListener); - - if (isolateRefStreamSubscription != null) { - isolateRefStreamSubscription.dispose(); - } - isDisposed = true; - connected = false; - - if (vmServiceListener != null && app.getVmService() != null) { - app.getVmService().removeVmServiceListener(vmServiceListener); - } - } - - private void setupConnection(@NotNull VmService vmService) { - if (isDisposed || connected) { - return; - } - - final VMServiceManager vmServiceManager = app.getVMServiceManager(); - assert vmServiceManager != null; - - connected = true; - - isolateRefStreamSubscription = vmServiceManager.getCurrentFlutterIsolate( - (isolateRef) -> requestRepaint(When.soon), false); - - vmService.addVmServiceListener(new VmServiceListenerAdapter() { - @Override - public void received(String streamId, Event event) { - onVmServiceReceived(streamId, event); - } - - @Override - public void connectionClosed() { - } - }); - - inspectorService = InspectorService.create(app, app.getFlutterDebugProcess(), app.getVmService()); - inspectorService.whenCompleteAsync((service, throwable) -> Disposer.register(this, service)); - - requestRepaint(When.soon); - } - - private IsolateRef getCurrentIsolateRef() { - assert app.getVMServiceManager() != null; - return app.getVMServiceManager().getCurrentFlutterIsolateRaw(); - } - - public boolean isConnected() { - return connected; - } - - @Override - public boolean shouldDisplayPerfStats(FileEditor editor) { - return !app.isReloading() && app.isLatestVersionRunning(editor.getFile()) && !editor.isModified(); - } - - @Override - public CompletableFuture getWidgetTree() { - return inspectorService - .thenComposeAsync((inspectorService) -> inspectorService.createObjectGroup("widget_perf").getSummaryTreeWithoutIds()); - } - - private void onVmServiceReceived(String streamId, Event event) { - // TODO(jacobr): centrailize checks for Flutter.Frame - // They are now in VMServiceManager, InspectorService, and here. - if (StringUtil.equals(streamId, VmService.EXTENSION_STREAM_ID)) { - final String kind = event.getExtensionKind(); - if (kind == null) { - return; - } - switch (kind) { - case "Flutter.Frame": - final JsonObject extensionData = event.getExtensionData().getJson(); - requestRepaint(When.soon); - break; - case "Flutter.RebuiltWidgets": - onWidgetPerfEvent(PerfReportKind.rebuild, event.getExtensionData().getJson()); - break; - case "Flutter.RepaintedWidgets": - onWidgetPerfEvent(PerfReportKind.repaint, event.getExtensionData().getJson()); - break; - case "Flutter.Navigation": - onNavigation(); - break; - } - } - } -} diff --git a/flutter-idea/src/io/flutter/perf/When.java b/flutter-idea/src/io/flutter/perf/When.java deleted file mode 100644 index 2f5d2ee7ec..0000000000 --- a/flutter-idea/src/io/flutter/perf/When.java +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2018 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.perf; - -public enum When { - now, - soon -} diff --git a/flutter-idea/src/io/flutter/perf/WidgetPerfLinter.java b/flutter-idea/src/io/flutter/perf/WidgetPerfLinter.java deleted file mode 100644 index 9b5d6ee92c..0000000000 --- a/flutter-idea/src/io/flutter/perf/WidgetPerfLinter.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.Multimap; -import com.intellij.icons.AllIcons; -import com.intellij.openapi.fileEditor.TextEditor; -import io.flutter.FlutterBundle; -import io.flutter.inspector.DiagnosticsNode; - -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import static io.flutter.perf.PerfTipRule.matchParent; - -/** - * Linter that determines what performance tips to show. - *

- * Performance tips are generally derived from a FlutterWidgetPerf object to - * provide rebuild counts for widgets in the app and the Widget tree expressed - * as a tree of DiagnositcsNode to give information about the types of - * ancestors of widgets in the tree. - */ -public class WidgetPerfLinter { - private static List tips; - final FlutterWidgetPerf widgetPerf; - private final WidgetPerfProvider perfProvider; - private ArrayList lastTips; - private Set lastCandidateLocations; - private Multimap nodesForLocation; - - WidgetPerfLinter(FlutterWidgetPerf widgetPerf, WidgetPerfProvider perfProvider) { - this.widgetPerf = widgetPerf; - this.perfProvider = perfProvider; - } - - static List getAllTips() { - if (tips != null) { - return tips; - } - tips = new ArrayList<>(); - - tips.add(new PerfTipRule( - PerfReportKind.rebuild, - 3, - "perf_diagnosis_demo/lib/clock_demo.dart", - "Performance considerations of StatefulWidget", - "statefulWidget", - FlutterBundle.message("flutter.perf.linter.statefulWidget.url"), - matchParent("StatefulWidget"), - 4, // Only relevant if the build method is somewhat large. - 50, - 20, - AllIcons.Actions.IntentionBulb - )); - tips.add(new PerfTipRule( - PerfReportKind.rebuild, - 1, - "perf_diagnosis_demo/lib/list_demo.dart", - "Using ListView to load items efficiently", - "listViewLoad", - FlutterBundle.message("flutter.perf.linter.listViewLoad.url"), - matchParent("ListView"), - 1, - 40, - -1, - AllIcons.Actions.IntentionBulb - )); - - tips.add(new PerfTipRule( - PerfReportKind.rebuild, - 1, - "perf_diagnosis_demo/lib/spinning_box_demo.dart", - "Performance optimizations when using AnimatedBuilder", - "animatedBuilder", - FlutterBundle.message("flutter.perf.linter.animatedBuilder.url"), - matchParent("AnimatedBuilder"), - 1, - 50, - 20, - AllIcons.Actions.IntentionBulb - )); - - tips.add(new PerfTipRule( - PerfReportKind.rebuild, - 2, - "perf_diagnosis_demo/lib/scorecard_demo.dart", - "Performance considerations of Opacity animations", - "opacityAnimations", - FlutterBundle.message("flutter.perf.linter.opacityAnimations.url"), - matchParent("Opacity"), - 1, - 20, - 8, - AllIcons.Actions.IntentionBulb - )); - - return tips; - } - - public CompletableFuture> getTipsFor(Set textEditors) { - final ArrayList candidateRules = new ArrayList<>(); - final Set candidateLocations = new HashSet<>(); - final ArrayList allFileStats = widgetPerf.buildAllSummaryStats(textEditors); - for (PerfTipRule rule : getAllTips()) { - for (FilePerfInfo fileStats : allFileStats) { - for (SummaryStats stats : fileStats.getStats()) { - if (rule.maybeMatches(stats)) { - candidateRules.add(rule); - candidateLocations.add(stats.getLocation()); - break; - } - } - } - } - - if (candidateRules.isEmpty()) { - return CompletableFuture.completedFuture(new ArrayList<>()); - } - - if (candidateLocations.equals(lastCandidateLocations)) { - // No need to load the widget tree again if the list of locations matching rules has not changed. - return CompletableFuture.completedFuture(computeMatches(candidateRules, allFileStats)); - } - - lastCandidateLocations = candidateLocations; - return perfProvider.getWidgetTree().thenApplyAsync((treeRoot) -> { - if (treeRoot != null) { - nodesForLocation = LinkedListMultimap.create(); - addNodesToMap(treeRoot); - return computeMatches(candidateRules, allFileStats); - } - else { - return new ArrayList<>(); - } - }); - } - - private ArrayList computeMatches(ArrayList candidateRules, ArrayList allFileStats) { - final ArrayList matches = new ArrayList<>(); - final Map> maps = new HashMap<>(); - for (FilePerfInfo fileStats : allFileStats) { - for (SummaryStats stats : fileStats.getStats()) { - Map map = maps.get(stats.getKind()); - //noinspection Java8MapApi - if (map == null) { - map = new HashMap<>(); - maps.put(stats.getKind(), map); - } - map.put(stats.getLocation().id, stats); - } - } - - for (PerfTipRule rule : candidateRules) { - final Map map = maps.get(rule.kind); - if (map != null) { - final ArrayList matchingLocations = new ArrayList<>(); - for (FilePerfInfo fileStats : allFileStats) { - for (SummaryStats stats : fileStats.getStats()) { - if (nodesForLocation == null) { - // TODO(jacobr): warn that we need a new widget tree. - continue; - } - assert (stats.getLocation() != null); - final Collection nodes = nodesForLocation.get(stats.getLocation().id); - if (nodes == null || nodes.isEmpty()) { - // This indicates a mismatch between the current inspector tree and the stats we are using. - continue; - } - if (rule.matches(stats, nodes, map)) { - matchingLocations.add(stats.getLocation()); - } - } - if (!matchingLocations.isEmpty()) { - matches.add(new PerfTip(rule, matchingLocations, 1.0 / rule.priority)); - } - } - } - } - matches.sort(Comparator.comparingDouble(a -> -a.getConfidence())); - final Set matchingRules = new HashSet<>(); - final ArrayList uniqueMatches = new ArrayList<>(); - for (PerfTip match : matches) { - if (matchingRules.add(match.rule)) { - uniqueMatches.add(match); - } - } - return uniqueMatches; - } - - private void addNodesToMap(DiagnosticsNode node) { - final int id = node.getLocationId(); - if (id >= 0) { - nodesForLocation.put(id, node); - } - final ArrayList children = node.getChildren().getNow(null); - if (children != null) { - for (DiagnosticsNode child : children) { - addNodesToMap(child); - } - } - } -} diff --git a/flutter-idea/src/io/flutter/perf/WidgetPerfListener.java b/flutter-idea/src/io/flutter/perf/WidgetPerfListener.java deleted file mode 100644 index c618b556d4..0000000000 --- a/flutter-idea/src/io/flutter/perf/WidgetPerfListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.gson.JsonObject; - -/** - * Interface defining events relevant to tracking widget performance. - *

- * See FlutterWidgetPerf which is the cannonical implementation. - */ -public interface WidgetPerfListener { - void requestRepaint(When when); - - void onWidgetPerfEvent(PerfReportKind kind, JsonObject json); - - void onNavigation(); - - void addPerfListener(PerfModel listener); - - void removePerfListener(PerfModel listener); -} diff --git a/flutter-idea/src/io/flutter/perf/WidgetPerfProvider.java b/flutter-idea/src/io/flutter/perf/WidgetPerfProvider.java deleted file mode 100644 index 55eed37fa0..0000000000 --- a/flutter-idea/src/io/flutter/perf/WidgetPerfProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.fileEditor.FileEditor; -import io.flutter.inspector.DiagnosticsNode; - -import java.util.concurrent.CompletableFuture; - -/** - * Interface defining what information about widget performance can be fetched - * from the running device. - *

- * See VMServiceWidgetPerfProvider for the non-test implementation of this class. - */ -public interface WidgetPerfProvider extends Disposable { - void setTarget(WidgetPerfListener widgetPerfListener); - - boolean isStarted(); - - boolean isConnected(); - - boolean shouldDisplayPerfStats(FileEditor editor); - - CompletableFuture getWidgetTree(); -} diff --git a/flutter-idea/src/io/flutter/performance/FlutterPerformanceView.java b/flutter-idea/src/io/flutter/performance/FlutterPerformanceView.java deleted file mode 100644 index 52b12464ee..0000000000 --- a/flutter-idea/src/io/flutter/performance/FlutterPerformanceView.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * 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.icons.AllIcons; -import com.intellij.ide.browsers.BrowserLauncher; -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.bazel.WorkspaceCache; -import io.flutter.devtools.DevToolsUrl; -import io.flutter.run.FlutterDevice; -import io.flutter.run.FlutterLaunchMode; -import io.flutter.run.daemon.DevToolsService; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.sdk.FlutterSdk; -import io.flutter.sdk.FlutterSdkVersion; -import io.flutter.utils.AsyncUtils; -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 javax.swing.text.SimpleAttributeSet; -import javax.swing.text.StyleConstants; -import javax.swing.text.StyleContext; -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 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; - } - - // 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 FlutterDevice device = app.device(); - final List existingDevices = new ArrayList<>(); - for (FlutterApp otherApp : perAppViewState.keySet()) { - existingDevices.add(otherApp.device()); - } - final String 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; - } - - 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()); - - FlutterSdk sdk = FlutterSdk.getFlutterSdk(app.getProject()); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - if (sdkVersion != null && sdkVersion.canUseDevToolsMultiEmbed()) { - final JPanel warning = new JPanel(new BorderLayout(50, 50)); - JTextPane text = new JTextPane(); - text.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); - text.setCharacterAttributes(StyleContext.getDefaultStyleContext().addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, new Color(234, 57, 35)), false); - text.setText("The performance panel is being removed soon. Use the Flutter DevTools panel instead: View -> Tool Windows -> Flutter DevTools"); - text.setFont(UIManager.getFont("Label.font").deriveFont(Font.BOLD)); - warning.add(text); - perfViewsPanel.add(warning); - } - - 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); - openDevtools.setListener((linkLabel, data) -> { - AsyncUtils.whenCompleteUiThread(DevToolsService.getInstance(app.getProject()).getDevToolsInstance(), (instance, ex) -> { - if (app.getProject().isDisposed()) { - return; - } - - if (ex != null) { - LOG.error(ex); - return; - } - - FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(app.getProject()); - BrowserLauncher.getInstance().browse( - new DevToolsUrl.Builder() - .setDevToolsHost(instance.host) - .setDevToolsPort(instance.port) - .setVmServiceUri(app.getConnector().getBrowserUrl()) - .setFlutterSdkVersion(flutterSdk == null ? null : flutterSdk.getVersion()) - .setWorkspaceCache(WorkspaceCache.getInstance(app.getProject())) - .build() - .getUrlString(), - 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); - if (toolWindow != null) { - 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); - if (toolWindow != null) { - 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 == null || 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 flutterViewActions = new ArrayList<>(); - @Nullable Disposable disposable; - Content content; - - FlutterViewAction registerAction(FlutterViewAction action) { - flutterViewActions.add(action); - return action; - } -} diff --git a/flutter-idea/src/io/flutter/performance/FlutterPerformanceViewFactory.java b/flutter-idea/src/io/flutter/performance/FlutterPerformanceViewFactory.java deleted file mode 100644 index 73b6c9c910..0000000000 --- a/flutter-idea/src/io/flutter/performance/FlutterPerformanceViewFactory.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.ide.util.PropertiesComponent; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowFactory; -import com.intellij.openapi.wm.ToolWindowManager; -import io.flutter.sdk.FlutterSdk; -import io.flutter.sdk.FlutterSdkVersion; -import io.flutter.utils.ViewListener; -import io.flutter.view.FlutterViewMessages; -import kotlin.coroutines.Continuation; -import org.jetbrains.annotations.NotNull; - -public class FlutterPerformanceViewFactory implements ToolWindowFactory, DumbAware { - private static final String TOOL_WINDOW_VISIBLE_PROPERTY = "flutter.performance.tool.window.visible"; - - public static void init(@NotNull Project project) { - project.getMessageBus().connect().subscribe( - FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)(event) -> initPerfView(project, event) - ); - final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(FlutterPerformanceView.TOOL_WINDOW_ID); - if (window != null) { - window.setAvailable(true); - - if (PropertiesComponent.getInstance(project).getBoolean(TOOL_WINDOW_VISIBLE_PROPERTY, false)) { - window.activate(null, false); - } - } - } - - private static void initPerfView(@NotNull Project project, FlutterViewMessages.FlutterDebugEvent event) { - ApplicationManager.getApplication().invokeLater(() -> { - final FlutterPerformanceView flutterPerfView = project.getService(FlutterPerformanceView.class); - final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(FlutterPerformanceView.TOOL_WINDOW_ID); - if (flutterPerfView != null && window != null) { - window.setAvailable(true); - flutterPerfView.debugActive(event); - } - }); - } - - @Override - public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { - //noinspection CodeBlock2Expr - DumbService.getInstance(project).runWhenSmart(() -> { - (project.getService(FlutterPerformanceView.class)).initToolWindow(toolWindow); - }); - } - - @Override - public boolean shouldBeAvailable(@NotNull Project project) { - return false; - } - - public static class FlutterPerformanceViewListener extends ViewListener { - public FlutterPerformanceViewListener(@NotNull Project project) { - super(project, FlutterPerformanceView.TOOL_WINDOW_ID, TOOL_WINDOW_VISIBLE_PROPERTY); - } - } - - @Override - public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation $completion) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - return sdkVersion == null || !sdkVersion.canUseDevToolsMultiEmbed(); - } -} diff --git a/flutter-idea/src/io/flutter/performance/FrameRenderingDisplay.java b/flutter-idea/src/io/flutter/performance/FrameRenderingDisplay.java deleted file mode 100644 index ec6389324d..0000000000 --- a/flutter-idea/src/io/flutter/performance/FrameRenderingDisplay.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright 2020 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.openapi.Disposable; -import com.intellij.openapi.util.Disposer; -import com.intellij.ui.JBColor; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBPanel; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.UIUtil; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.vmService.DisplayRefreshRateManager; -import io.flutter.vmService.FlutterFramesMonitor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.geom.Path2D; -import java.text.DecimalFormat; -import java.util.List; -import java.util.*; - -// TODO(devoncarew): We have a drawing artifacts issue - tall frame columns can be left behind -// as artifacts when they scroll off the screen. - -public class FrameRenderingDisplay { - static final DecimalFormat df = new DecimalFormat(); - - static { - df.setMaximumFractionDigits(1); - } - - public static JPanel createJPanelView(@NotNull Disposable parentDisposable, @NotNull FlutterApp app) { - final JPanel panel = new JPanel(new StackLayout()); - panel.setDoubleBuffered(true); - - assert app.getVMServiceManager() != null; - final FlutterFramesMonitor flutterFramesMonitor = app.getVMServiceManager().getFlutterFramesMonitor(); - - final FrameRenderingPanel frameRenderingPanel = new FrameRenderingPanel(flutterFramesMonitor, app.getDisplayRefreshRateManager()); - - final JBLabel targetFrameTimeLabel = new JBLabel(); - targetFrameTimeLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); - targetFrameTimeLabel.setForeground(UIUtil.getLabelDisabledForeground()); - targetFrameTimeLabel.setBorder(JBUI.Borders.empty(2)); - targetFrameTimeLabel.setOpaque(false); - - - updateTargetLabelForRefreshRate( - app.getDisplayRefreshRateManager().getCurrentDisplayRefreshRateRaw(), - DisplayRefreshRateManager.defaultRefreshRate, - targetFrameTimeLabel - ); - - // Listen for updates to the display refresh rate. - app.getDisplayRefreshRateManager().getCurrentDisplayRefreshRate((fps) -> { - updateTargetLabelForRefreshRate(fps, DisplayRefreshRateManager.defaultRefreshRate, targetFrameTimeLabel); - }, false); - - //noinspection rawtypes - final JBPanel targetFrameTimePanel = new JBPanel(); - targetFrameTimePanel.setLayout(new BoxLayout(targetFrameTimePanel, BoxLayout.Y_AXIS)); - targetFrameTimePanel.setOpaque(false); - targetFrameTimePanel.add(Box.createVerticalGlue()); - targetFrameTimePanel.add(targetFrameTimeLabel); - targetFrameTimePanel.add(Box.createVerticalGlue()); - - panel.add(frameRenderingPanel); - panel.add(targetFrameTimePanel); - - final FlutterFramesMonitor.Listener listener = event -> { - frameRenderingPanel.update(); - - // Repaint this after each frame so that the label does not get painted over by the frame rendering panel. - SwingUtilities.invokeLater(targetFrameTimeLabel::repaint); - }; - - flutterFramesMonitor.addListener(listener); - Disposer.register(parentDisposable, () -> flutterFramesMonitor.removeListener(listener)); - - return panel; - } - - private static void updateTargetLabelForRefreshRate(@Nullable Double fps, @NotNull Double defaultRefreshRate, JBLabel targetLabel) { - if (fps == null) { - fps = defaultRefreshRate; - } - final double targetFrameTime = Math.floor(1000.0f / fps); - final String targetFrameTimeText = Math.round(targetFrameTime) + "ms"; - targetLabel.setText(targetFrameTimeText); - targetLabel - .setToolTipText("Targeting " + targetFrameTimeText + " per frame will\nresult in " - + Math.round(fps) + " frames per second. "); - SwingUtilities.invokeLater(targetLabel::repaint); - } -} - -class FrameRenderingPanel extends JPanel { - private final FlutterFramesMonitor framesMonitor; - - private final DisplayRefreshRateManager displayRefreshRateManager; - - private final Map frameWidgets = new HashMap<>(); - - private Rectangle lastSavedBounds; - - FrameRenderingPanel(@NotNull FlutterFramesMonitor framesMonitor, @NotNull DisplayRefreshRateManager displayRefreshRateManager) { - this.framesMonitor = framesMonitor; - this.displayRefreshRateManager = displayRefreshRateManager; - - setLayout(null); - final Color color = UIUtil.getLabelDisabledForeground(); - //noinspection UseJBColor - setForeground(new Color(color.getRed(), color.getGreen(), color.getBlue(), 0x7f)); - } - - public void update() { - SwingUtilities.invokeLater(this::updateFromFramesMonitor); - } - - public void doLayout() { - if (lastSavedBounds != null && !lastSavedBounds.equals(getBounds())) { - lastSavedBounds = null; - - SwingUtilities.invokeLater(this::updateFromFramesMonitor); - } - } - - private static final Stroke STROKE = new BasicStroke( - 0.5f, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_MITER, 10.0f, new float[]{2.0f, 2.0f}, 0.0f); - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - final Rectangle bounds = getBounds(); - final int height = bounds.height; - - if (height <= 20) { - return; - } - - final Graphics2D g2 = (Graphics2D)g; - - final float msPerPixel = (2.0f * 1000000.0f / 60.0f) / height; - final float y = displayRefreshRateManager.getTargetMicrosPerFrame() / msPerPixel; - final Stroke oldStroke = g2.getStroke(); - try { - g2.setStroke(STROKE); - final Path2D path = new Path2D.Float(); - // Slight left indent to allow space for [targetFrameTimeLabel]. - path.moveTo(34, height - y); - path.lineTo(bounds.width, height - y); - g2.draw(path); - } - finally { - g2.setStroke(oldStroke); - } - } - - void updateFromFramesMonitor() { - final Set frames = new HashSet<>(frameWidgets.keySet()); - - final Rectangle bounds = getBounds(); - lastSavedBounds = bounds; - - final int height = bounds.height; - final int inc = height <= 20 ? 1 : 2; - - int x = bounds.width; - - final int widgetWidth = Math.min(Math.max(Math.round(height / 8.0f), 2), 5); - - synchronized (framesMonitor) { - for (FlutterFramesMonitor.FlutterFrameEvent frame : framesMonitor.frames) { - if (x + widgetWidth < 0) { - break; - } - - x -= (widgetWidth + inc); - - final float msPerPixel = (2.0f * 1000000.0f / 60.0f) / height; - JComponent widget = frameWidgets.get(frame); - if (widget != null) { - frames.remove(frame); - } - else { - widget = new JLabel(); - widget.setOpaque(true); - widget.setBackground(frame.isSlowFrame() ? JBColor.RED : UIUtil.getLabelForeground()); - widget.setToolTipText(frame.isSlowFrame() - ? "This frame took " + - FrameRenderingDisplay.df.format(frame.elapsedMicros / 1000.0d) + - "ms to render, which\ncan cause frame rate to drop below " + - Math.round(displayRefreshRateManager.getCurrentDisplayRefreshRateRaw()) + " FPS." - : "This frame took " + FrameRenderingDisplay.df.format(frame.elapsedMicros / 1000.0d) + "ms to render."); - frameWidgets.put(frame, widget); - add(widget); - } - - int pixelHeight = Math.round(frame.elapsedMicros / msPerPixel); - if (pixelHeight > height) { - pixelHeight = height; - } - pixelHeight = Math.max(1, pixelHeight); - widget.setPreferredSize(new Dimension(widgetWidth, pixelHeight)); - widget.setBounds(x, height - pixelHeight, widgetWidth, pixelHeight); - - // Add a gap between sets of frames. - if (frame.frameSetStart) { - x -= widgetWidth; - } - } - } - - if (!frames.isEmpty()) { - for (FlutterFramesMonitor.FlutterFrameEvent frame : frames) { - final JComponent widget = frameWidgets.remove(frame); - remove(widget); - } - } - } -} - -class StackLayout implements LayoutManager2 { - public static final String BOTTOM = "bottom"; - public static final String TOP = "top"; - private final List components = new LinkedList<>(); - - public StackLayout() { - } - - public void addLayoutComponent(Component comp, Object constraints) { - synchronized (comp.getTreeLock()) { - if ("bottom".equals(constraints)) { - this.components.add(0, comp); - } - else if ("top".equals(constraints)) { - this.components.add(comp); - } - else { - this.components.add(comp); - } - } - } - - public void addLayoutComponent(String name, Component comp) { - this.addLayoutComponent(comp, "top"); - } - - public void removeLayoutComponent(Component comp) { - synchronized (comp.getTreeLock()) { - this.components.remove(comp); - } - } - - public float getLayoutAlignmentX(Container target) { - return 0.5F; - } - - public float getLayoutAlignmentY(Container target) { - return 0.5F; - } - - public void invalidateLayout(Container target) { - } - - public Dimension preferredLayoutSize(Container parent) { - synchronized (parent.getTreeLock()) { - int width = 0; - int height = 0; - - Dimension size; - for (final Iterator i$ = this.components.iterator(); i$.hasNext(); height = Math.max(size.height, height)) { - final Component comp = i$.next(); - size = comp.getPreferredSize(); - width = Math.max(size.width, width); - } - - final Insets insets = parent.getInsets(); - width += insets.left + insets.right; - height += insets.top + insets.bottom; - return new Dimension(width, height); - } - } - - public Dimension minimumLayoutSize(Container parent) { - synchronized (parent.getTreeLock()) { - int width = 0; - int height = 0; - - Dimension size; - for (final Iterator i$ = this.components.iterator(); i$.hasNext(); height = Math.max(size.height, height)) { - final Component comp = i$.next(); - size = comp.getMinimumSize(); - width = Math.max(size.width, width); - } - - final Insets insets = parent.getInsets(); - width += insets.left + insets.right; - height += insets.top + insets.bottom; - return new Dimension(width, height); - } - } - - public Dimension maximumLayoutSize(Container target) { - return new Dimension(2147483647, 2147483647); - } - - public void layoutContainer(Container parent) { - synchronized (parent.getTreeLock()) { - final Insets insets = parent.getInsets(); - - final int width = parent.getWidth() - insets.left - insets.right; - final int height = parent.getHeight() - insets.top - insets.bottom; - final Rectangle bounds = new Rectangle(insets.left, insets.top, width, height); - final int componentsCount = this.components.size(); - - for (int i = 0; i < componentsCount; ++i) { - final Component comp = this.components.get(i); - comp.setBounds(bounds); - parent.setComponentZOrder(comp, componentsCount - i - 1); - } - } - } -} diff --git a/flutter-idea/src/io/flutter/performance/PerfFPSPanel.java b/flutter-idea/src/io/flutter/performance/PerfFPSPanel.java deleted file mode 100644 index 97a2f5e086..0000000000 --- a/flutter-idea/src/io/flutter/performance/PerfFPSPanel.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.openapi.Disposable; -import com.intellij.openapi.util.Disposer; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBPanel; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.UIUtil; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.vmService.FlutterFramesMonitor; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.text.DecimalFormat; -import java.text.NumberFormat; - -public class PerfFPSPanel extends JBPanel { - private static final NumberFormat fpsFormat = new DecimalFormat(); - - private static final String PERFORMANCE_TAB_LABEL = "Frame rendering times"; - - static { - fpsFormat.setMinimumFractionDigits(1); - fpsFormat.setMaximumFractionDigits(1); - } - - private final Disposable parentDisposable; - private final @NotNull FlutterApp app; - - PerfFPSPanel(@NotNull FlutterApp app, @NotNull Disposable parentDisposable) { - this.app = app; - this.parentDisposable = parentDisposable; - - buildUI(); - } - - private void buildUI() { - setLayout(new BorderLayout(0, 3)); - setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), PERFORMANCE_TAB_LABEL)); - setMinimumSize(new Dimension(0, PerfMemoryPanel.HEIGHT)); - setPreferredSize(new Dimension(Short.MAX_VALUE, PerfMemoryPanel.HEIGHT)); - - // FPS - assert app.getVMServiceManager() != null; - final FlutterFramesMonitor flutterFramesMonitor = app.getVMServiceManager().getFlutterFramesMonitor(); - final JBLabel fpsLabel = new JBLabel(" ", SwingConstants.CENTER); - fpsLabel.setForeground(UIUtil.getLabelDisabledForeground()); - final FlutterFramesMonitor.Listener listener = event -> { - fpsLabel.setText(fpsFormat.format(flutterFramesMonitor.getFPS()) + " frames per second"); - SwingUtilities.invokeLater(fpsLabel::repaint); - }; - flutterFramesMonitor.addListener(listener); - Disposer.register(parentDisposable, () -> flutterFramesMonitor.removeListener(listener)); - fpsLabel.setBorder(JBUI.Borders.empty(0, 5)); - - // Frame Rendering - final JPanel frameRenderingPanel = new JPanel(new BorderLayout()); - final JPanel frameRenderingDisplay = FrameRenderingDisplay.createJPanelView(parentDisposable, app); - frameRenderingPanel.add(fpsLabel, BorderLayout.NORTH); - frameRenderingPanel.add(frameRenderingDisplay, BorderLayout.CENTER); - - add(frameRenderingPanel, BorderLayout.CENTER); - } -} diff --git a/flutter-idea/src/io/flutter/performance/PerfMemoryPanel.java b/flutter-idea/src/io/flutter/performance/PerfMemoryPanel.java deleted file mode 100644 index 59e914544e..0000000000 --- a/flutter-idea/src/io/flutter/performance/PerfMemoryPanel.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Disposer; -import com.intellij.ui.components.JBPanel; -import io.flutter.inspector.HeapDisplay; -import io.flutter.run.daemon.FlutterApp; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; - -public class PerfMemoryPanel extends JBPanel { - private static final Logger LOG = Logger.getInstance(PerfMemoryPanel.class); - - private static final String MEMORY_TAB_LABEL = "Memory usage"; - - static final int HEIGHT = 140; - - PerfMemoryPanel(@NotNull FlutterApp app, @NotNull Disposable parentDisposable) { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), MEMORY_TAB_LABEL)); - setMinimumSize(new Dimension(0, PerfMemoryPanel.HEIGHT)); - setPreferredSize(new Dimension(Short.MAX_VALUE, PerfMemoryPanel.HEIGHT)); - - final JPanel heapDisplay = HeapDisplay.createJPanelView(parentDisposable, app); - add(heapDisplay, BorderLayout.CENTER); - - if (app.getVMServiceManager() != null) { - app.getVMServiceManager().getHeapMonitor().addPollingClient(); - } - - Disposer.register(parentDisposable, () -> { - if (app.getVMServiceManager() != null) { - app.getVMServiceManager().getHeapMonitor().removePollingClient(); - } - }); - } -} diff --git a/flutter-idea/src/io/flutter/performance/PerfWidgetRebuildsPanel.java b/flutter-idea/src/io/flutter/performance/PerfWidgetRebuildsPanel.java deleted file mode 100644 index bb6ba87bf4..0000000000 --- a/flutter-idea/src/io/flutter/performance/PerfWidgetRebuildsPanel.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBPanel; -import com.intellij.ui.components.panels.VerticalLayout; -import com.intellij.util.ui.JBUI; -import io.flutter.perf.FlutterWidgetPerfManager; -import io.flutter.perf.PerfMetric; -import io.flutter.perf.PerfReportKind; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.vmService.ServiceExtensions; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; - -public class PerfWidgetRebuildsPanel extends JBPanel { - private static final Logger LOG = Logger.getInstance(PerfWidgetRebuildsPanel.class); - - private static final String REBUILD_STATS_TAB_LABEL = "Widget rebuild stats"; - - /** - * Tracking widget repaints may confuse users so we disable it by default currently. - */ - private static final boolean ENABLE_TRACK_REPAINTS = false; - - private @NotNull final FlutterApp app; - - private final WidgetPerfSummary perfSummary; - - private final JCheckBox trackRebuildsCheckbox; - private JCheckBox trackRepaintsCheckbox; - - private final JPanel perfSummaryContainer; - private final JPanel perfSummaryPlaceholder; - - private JComponent currentSummaryView; - - PerfWidgetRebuildsPanel(@NotNull FlutterApp app, @NotNull Disposable parentDisposable) { - this.app = app; - - setLayout(new BorderLayout()); - setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), REBUILD_STATS_TAB_LABEL)); - setPreferredSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); - - final JPanel rebuildStatsPanel = new JPanel(new BorderLayout(0, 5)); - add(rebuildStatsPanel, BorderLayout.CENTER); - - // rebuild stats - perfSummaryContainer = new JPanel(new BorderLayout(0, 5)); - currentSummaryView = null; - perfSummaryPlaceholder = new JPanel(new BorderLayout()); - final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane( - new JBLabel( - "" + - "Widget rebuild information tells you what widgets have been " + - "recently rebuilt for your application's current screen." + - "
" + - ""), - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER - ); - scrollPane.setBorder(JBUI.Borders.empty()); - scrollPane.setViewportBorder(JBUI.Borders.empty()); - perfSummaryPlaceholder.add(scrollPane); - - final JPanel perfViewSettings = new JPanel(new VerticalLayout(5, 4)); - trackRebuildsCheckbox = new JCheckBox("Track widget rebuilds"); - trackRebuildsCheckbox.setHorizontalAlignment(JLabel.RIGHT); - final boolean isInProfileMode = app.getMode().isProfiling() || app.getLaunchMode().isProfiling(); - if (isInProfileMode){ - trackRebuildsCheckbox.setToolTipText( - "

This profiler identifies widgets that are rebuilt when the UI changes.

" + - "
" + - "

To enable 'Track widget rebuilds', start the app in debug mode.

" + - ""); - } - else { - trackRebuildsCheckbox.setToolTipText( - "

This profiler identifies widgets that are rebuilt when the UI changes.

" + - "
" + - "

Look for the indicators on the left margin of the code editor
and a list of the top rebuilt widgets in this window.

" + - ""); - } - perfViewSettings.add(trackRebuildsCheckbox); - if (ENABLE_TRACK_REPAINTS) { - trackRepaintsCheckbox = new JCheckBox("Show widget repaint information"); - trackRepaintsCheckbox.setHorizontalAlignment(JLabel.RIGHT); - perfViewSettings.add(trackRepaintsCheckbox); - } - perfViewSettings.add(new JSeparator()); - perfSummary = new WidgetPerfSummary(parentDisposable, app, PerfMetric.lastFrame, PerfReportKind.rebuild); - perfSummaryContainer.add(perfViewSettings, BorderLayout.NORTH); - - updateShowPerfSummaryView(); - rebuildStatsPanel.add(perfSummaryContainer, BorderLayout.CENTER); - - // perf tips - final JPanel perfTipsPanel = perfSummary.getWidgetPerfTipsPanel(); - rebuildStatsPanel.add(perfTipsPanel, BorderLayout.SOUTH); - perfTipsPanel.setVisible(false); - - final FlutterWidgetPerfManager widgetPerfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - - trackRebuildsCheckbox.setSelected(widgetPerfManager.isTrackRebuildWidgets()); - if (ENABLE_TRACK_REPAINTS) { - trackRepaintsCheckbox.setSelected(widgetPerfManager.isTrackRepaintWidgets()); - } - - app.hasServiceExtension(ServiceExtensions.trackRebuildWidgets.getExtension(), trackRebuildsCheckbox::setEnabled, parentDisposable); - - if (ENABLE_TRACK_REPAINTS) { - app.hasServiceExtension(ServiceExtensions.trackRepaintWidgets.getExtension(), trackRepaintsCheckbox::setEnabled, parentDisposable); - } - - trackRebuildsCheckbox.addChangeListener((l) -> { - if (app.getProject().isDisposed()) return; - - setTrackRebuildWidgets(trackRebuildsCheckbox.isSelected()); - updateShowPerfSummaryView(); - }); - - if (ENABLE_TRACK_REPAINTS) { - trackRepaintsCheckbox.addChangeListener((l) -> { - if (app.getProject().isDisposed()) return; - - setTrackRepaintWidgets(trackRepaintsCheckbox.isSelected()); - updateShowPerfSummaryView(); - }); - } - } - - void updateShowPerfSummaryView() { - final boolean show = getShowPerfTable(); - final boolean firstRender = currentSummaryView == null; - final JComponent summaryView = show ? perfSummary : perfSummaryPlaceholder; - - if (summaryView != currentSummaryView) { - if (currentSummaryView != null) { - perfSummaryContainer.remove(currentSummaryView); - } - currentSummaryView = summaryView; - perfSummaryContainer.add(summaryView, BorderLayout.CENTER); - perfSummaryContainer.revalidate(); - perfSummaryContainer.repaint(); - } - - if (!show) { - perfSummary.clearPerformanceTips(); - } - } - - boolean getShowPerfTable() { - final FlutterWidgetPerfManager widgetPerfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - return widgetPerfManager.isTrackRebuildWidgets() || widgetPerfManager.isTrackRepaintWidgets(); - } - - private void setTrackRebuildWidgets(boolean selected) { - final FlutterWidgetPerfManager widgetPerfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - widgetPerfManager.setTrackRebuildWidgets(selected); - // Update default so next app launched will match the existing setting. - FlutterWidgetPerfManager.trackRebuildWidgetsDefault = selected; - } - - private void setTrackRepaintWidgets(boolean selected) { - final FlutterWidgetPerfManager widgetPerfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - widgetPerfManager.setTrackRepaintWidgets(selected); - // Update default so next app launched will match the existing setting. - FlutterWidgetPerfManager.trackRepaintWidgetsDefault = selected; - } -} diff --git a/flutter-idea/src/io/flutter/performance/WidgetPerfSummary.java b/flutter-idea/src/io/flutter/performance/WidgetPerfSummary.java deleted file mode 100644 index ea70f1c2b0..0000000000 --- a/flutter-idea/src/io/flutter/performance/WidgetPerfSummary.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.openapi.Disposable; -import com.intellij.openapi.util.Disposer; -import com.intellij.ui.ScrollPaneFactory; -import io.flutter.inspector.WidgetPerfTipsPanel; -import io.flutter.perf.FlutterWidgetPerf; -import io.flutter.perf.FlutterWidgetPerfManager; -import io.flutter.perf.PerfMetric; -import io.flutter.perf.PerfReportKind; -import io.flutter.run.daemon.FlutterApp; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; - -class WidgetPerfSummary extends JPanel implements Disposable { - private static final int REFRESH_TABLE_DELAY = 100; - - private final FlutterWidgetPerfManager perfManager; - private final Timer refreshTableTimer; - private final WidgetPerfTable table; - private final PerfReportKind reportKind; - - private final WidgetPerfTipsPanel myWidgetPerfTipsPanel; - - private long lastUpdateTime; - - WidgetPerfSummary(Disposable parentDisposable, FlutterApp app, PerfMetric metric, PerfReportKind reportKind) { - setLayout(new BorderLayout()); - - this.reportKind = reportKind; - - perfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - - refreshTableTimer = new Timer(REFRESH_TABLE_DELAY, this::onUpdateTable); - refreshTableTimer.start(); - - table = new WidgetPerfTable(app, parentDisposable, metric); - - Disposer.register(parentDisposable, this); - - perfManager.addPerfListener(table); - - add(ScrollPaneFactory.createScrollPane(table, true), BorderLayout.CENTER); - - // Perf info and tips - myWidgetPerfTipsPanel = new WidgetPerfTipsPanel(parentDisposable, app); - } - - public void dispose() { - perfManager.removePerfListener(table); - refreshTableTimer.stop(); - } - - public WidgetPerfTipsPanel getWidgetPerfTipsPanel() { - return myWidgetPerfTipsPanel; - } - - private void onUpdateTable(ActionEvent event) { - final FlutterWidgetPerf stats = perfManager.getCurrentStats(); - if (stats != null) { - final long latestPerfUpdate = stats.getLastLocalPerfEventTime(); - // Only do work if new performance stats have been recorded. - if (latestPerfUpdate != lastUpdateTime) { - lastUpdateTime = latestPerfUpdate; - table.showStats(stats.getStatsForMetric(table.getMetrics(), reportKind)); - } - } - } - - public void clearPerformanceTips() { - myWidgetPerfTipsPanel.clearTips(); - } -} diff --git a/flutter-idea/src/io/flutter/performance/WidgetPerfTable.java b/flutter-idea/src/io/flutter/performance/WidgetPerfTable.java deleted file mode 100644 index ff5c1142f3..0000000000 --- a/flutter-idea/src/io/flutter/performance/WidgetPerfTable.java +++ /dev/null @@ -1,597 +0,0 @@ -/* - * 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.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionGroup; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.SimpleColoredRenderer; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns; -import com.intellij.ui.treeStructure.treetable.TreeTable; -import com.intellij.util.PathUtil; -import com.intellij.util.ui.ColumnInfo; -import com.intellij.xdebugger.XSourcePosition; -import io.flutter.inspector.InspectorActions; -import io.flutter.perf.*; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.AsyncUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.table.JTableHeader; -import javax.swing.table.TableCellRenderer; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.HashSet; - -import static io.flutter.perf.Icons.getIconForCount; - -class WidgetPerfTable extends TreeTable implements DataProvider, PerfModel { - @SuppressWarnings("rawtypes") - private final ColumnInfo[] modelColumns; - private final CountColumnInfo countColumnInfo; - private final WidgetNameColumnInfo widgetNameColumnInfo; - private final FlutterApp app; - private final FlutterWidgetPerfManager perfManager; - private final ListTreeTableModelOnColumns model; - private final DefaultMutableTreeNode root; - private final PerfMetric metric; - private final HashSet openPaths = new HashSet<>(); - private final ArrayList metrics; - private ArrayList entries = new ArrayList<>(); - private boolean idle; - private DefaultMutableTreeNode currentSelection; - - WidgetPerfTable(FlutterApp app, Disposable parentDisposable, PerfMetric metric) { - super(new ListTreeTableModelOnColumns( - new DefaultMutableTreeNode(), - new ColumnInfo[]{ - new WidgetNameColumnInfo("Widget"), - new LocationColumnInfo("Location"), - new CountColumnInfo(metric), - new CountColumnInfo(PerfMetric.totalSinceEnteringCurrentScreen) - } - )); - - getTableHeader().setReorderingAllowed(false); - setSurrendersFocusOnKeystroke(false); - this.app = app; - model = getTreeModel(); - modelColumns = model.getColumns(); - widgetNameColumnInfo = (WidgetNameColumnInfo)model.getColumns()[0]; - countColumnInfo = (CountColumnInfo)model.getColumns()[2]; - perfManager = FlutterWidgetPerfManager.getInstance(app.getProject()); - - setRootVisible(false); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - getTree().addTreeSelectionListener(this::selectionListener); - - final MouseListener mouseListener = new MouseAdapter() { - public void mousePressed(MouseEvent e) { - final int rowIndex = getTree().getRowForLocation(e.getX(), e.getY()); - if (rowIndex != -1) { - onClickRow(rowIndex); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - showLineMarkers(true); - } - - @Override - public void mouseExited(MouseEvent e) { - showLineMarkers(false); - } - - private void showLineMarkers(boolean show) { - final FlutterWidgetPerf stats = perfManager.getCurrentStats(); - if (stats != null) { - stats.setAlwaysShowLineMarkersOverride(true); - } - } - }; - addMouseListener(mouseListener); - setStriped(true); - - final JTableHeader tableHeader = getTableHeader(); - tableHeader.setPreferredSize(new Dimension(0, getRowHeight())); - - getColumnModel().getColumn(0).setPreferredWidth(120); - getColumnModel().getColumn(1).setPreferredWidth(200); - getColumnModel().getColumn(2).setPreferredWidth(60); - - this.metric = metric; - this.metrics = new ArrayList<>(); - metrics.add(metric); - metrics.add(PerfMetric.totalSinceEnteringCurrentScreen); - root = new DefaultMutableTreeNode(); - model.setRoot(root); - } - - void sortByMetric(ArrayList entries) { - openPaths.clear(); - for (TextEditor editor : perfManager.getSelectedEditors()) { - final VirtualFile file = editor.getFile(); - if (file != null) { - openPaths.add(file.getPath()); - } - } - - if (entries != null) { - entries.sort((a, b) -> { - final int comparison = Integer.compare(b.getValue(metric), a.getValue(metric)); - if (comparison != 0) { - return comparison; - } - return Boolean.compare(isOpenLocation(b.getLocation()), isOpenLocation(a.getLocation())); - }); - } - } - - public ArrayList getMetrics() { - return metrics; - } - - private boolean isOpenLocation(Location location) { - if (location == null) { - return false; - } - return openPaths.contains(location.path); - } - - public void clear() { - showStats(new ArrayList<>()); - } - - @Override - public boolean isAnimationActive() { - if (idle) { - return false; - } - // If any rows will be animating, the first row will be animating. - return !entries.isEmpty() && entries.get(0).getValue(metric) > 0; - } - - @Override - public void onFrame() { - if (app.isReloading() || entries.isEmpty() || !isAnimationActive()) { - return; - } - updateIconUIAnimations(); - } - - private void updateIconUIAnimations() { - int lastActive = -1; - // TODO(devoncarew): We've seen NPEs from here. - for (int i = 0; i < entries.size() && entries.get(i).getValue(metric) > 0; ++i) { - lastActive = i; - } - if (lastActive >= 0) { - final int[] active = new int[lastActive + 1]; - for (int i = 0; i <= lastActive; i++) { - active[i] = i; - } - model.nodesChanged(root, active); - } - } - - private void onClickRow(int index) { - if (index < 0 || index >= entries.size()) { - return; - } - final SlidingWindowStatsSummary stats = entries.get(index); - // If the selection is changed by this click there is no need to trigger - // the navigation event here as it will be triggered by the selectionListener. - final boolean selectionChanged = currentSelection == null || currentSelection.getUserObject() == stats; - if (!selectionChanged) { - navigateToStatsEntry(stats); - } - } - - private void selectionListener(TreeSelectionEvent event) { - final DefaultMutableTreeNode selection = (DefaultMutableTreeNode)event.getPath().getLastPathComponent(); - if (!event.isAddedPath()) { - // We only care about selection events not deselection events. - return; - } - if (currentSelection == selection) { - // Selection didn't really change. Unfortunately, events about modifying - // nodes result in spurious selection changed events. - return; - } - currentSelection = selection; - - if (selection != null) { - final SlidingWindowStatsSummary stats = (SlidingWindowStatsSummary)selection.getUserObject(); - navigateToStatsEntry(stats); - } - } - - private void navigateToStatsEntry(SlidingWindowStatsSummary stats) { - if (stats == null) { - return; - } - final Location location = stats.getLocation(); - final XSourcePosition position = location.getXSourcePosition(); - if (position != null) { - AsyncUtils.invokeLater(() -> { - position.createNavigatable(app.getProject()).navigate(false); - }); - } - } - - @Override - public TableCellRenderer getCellRenderer(int row, int column) { - //noinspection unchecked - return modelColumns[column].getRenderer(null); - } - - private ActionGroup createTreePopupActions() { - final DefaultActionGroup group = new DefaultActionGroup(); - final ActionManager actionManager = ActionManager.getInstance(); - group.add(actionManager.getAction(InspectorActions.JUMP_TO_SOURCE)); - return group; - } - - ListTreeTableModelOnColumns getTreeModel() { - return (ListTreeTableModelOnColumns)getTableModel(); - } - - public void markAppIdle() { - idle = true; - widgetNameColumnInfo.setIdle(true); - updateIconUIAnimations(); - } - - public void showStats(ArrayList entries) { - if (entries == null) { - entries = new ArrayList<>(); - } - idle = false; - widgetNameColumnInfo.setIdle(false); - final ArrayList oldEntries = this.entries; - sortByMetric(entries); - this.entries = entries; - int selectionIndex = -1; - Location lastSelectedLocation = null; - - if (statsChanged(oldEntries, entries)) { - final int selectedRowIndex = getSelectedRow(); - if (selectedRowIndex != -1) { - final Object selectedRow = getTreeModel().getRowValue(selectedRowIndex); - if (selectedRow instanceof DefaultMutableTreeNode selectedRowNode) { - final SlidingWindowStatsSummary selectedStats = (SlidingWindowStatsSummary)selectedRowNode.getUserObject(); - if (selectedStats != null) { - lastSelectedLocation = selectedStats.getLocation(); - } - } - } - final boolean previouslyEmpty = root.getChildCount() == 0; - int childIndex = 0; - final ArrayList indicesChanged = new ArrayList(); - final ArrayList indicesInserted = new ArrayList(); - for (SlidingWindowStatsSummary entry : entries) { - if (entry.getLocation().equals(lastSelectedLocation)) { - selectionIndex = childIndex; - } - if (childIndex >= root.getChildCount()) { - root.add(new DefaultMutableTreeNode(entry, false)); - indicesInserted.add(childIndex); - } - else { - final DefaultMutableTreeNode existing = (DefaultMutableTreeNode)root.getChildAt(childIndex); - final SlidingWindowStatsSummary existingEntry = (SlidingWindowStatsSummary)existing.getUserObject(); - if (displayChanged(entry, existingEntry)) { - model.nodeChanged(existing); - indicesChanged.add(childIndex); - } - existing.setUserObject(entry); - } - childIndex++; - } - final int endChildIndex = childIndex; - final ArrayList nodesRemoved = new ArrayList(); - final ArrayList indicesRemoved = new ArrayList(); - // Gather nodes to remove. - for (int j = endChildIndex; j < root.getChildCount(); j++) { - nodesRemoved.add(root.getChildAt(j)); - indicesRemoved.add(j); - } - // Actuallly remove nodes. - while (endChildIndex < root.getChildCount()) { - // Removing the last element is slightly more efficient. - final int lastChild = root.getChildCount() - 1; - root.remove(lastChild); - } - - assert(model != null); - if (previouslyEmpty) { - // TODO(jacobr): I'm not clear why this event is needed in this case. - model.nodeStructureChanged(root); - } - else { - // Report events for all the changes made to the table. - if (!indicesChanged.isEmpty()) { - model.nodesChanged(root, indicesChanged.stream().mapToInt(i -> i).toArray());//.intStream().toArray()); - } - if (!indicesInserted.isEmpty()) { - model.nodesWereInserted(root, indicesInserted.stream().mapToInt(i -> i).toArray()); - } - if (!indicesRemoved.isEmpty()) { - model.nodesWereRemoved(root, indicesRemoved.stream().mapToInt(i -> i).toArray(), nodesRemoved.toArray()); - } - } - - if (selectionIndex >= 0) { - getSelectionModel().setSelectionInterval(selectionIndex, selectionIndex); - currentSelection = (DefaultMutableTreeNode)root.getChildAt(selectionIndex); - } - else { - getSelectionModel().clearSelection(); - } - } - } - - private boolean statsChanged(ArrayList previous, ArrayList current) { - if (previous == current) { - return false; - } - if (previous == null || current == null) { - return true; - } - if (previous.size() != current.size()) { - return true; - } - for (int i = 0; i < previous.size(); ++i) { - if (displayChanged(previous.get(i), current.get(i))) { - return true; - } - } - return false; - } - - private boolean displayChanged(SlidingWindowStatsSummary previous, SlidingWindowStatsSummary current) { - // We only care about updating if the value for the current metric being displayed has changed. - if (!previous.getLocation().equals(current.getLocation())) { - return true; - } - - for (PerfMetric metric : getMetrics()) { - if (previous.getValue(metric) != current.getValue(metric)) { - return true; - } - } - - return false; - } - - @Nullable - @Override - public Object getData(@NotNull String dataId) { - return null; - } - - private static class WidgetNameRenderer extends SimpleColoredRenderer implements TableCellRenderer { - - private boolean idle = false; - - @Override - public final Component getTableCellRendererComponent(JTable table, @Nullable Object value, - boolean isSelected, boolean hasFocus, int row, int col) { - final JPanel panel = new JPanel(); - if (value == null) return panel; - panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - - if (value instanceof SlidingWindowStatsSummary stats) { - final SimpleTextAttributes attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES; - - final JBLabel label = new JBLabel(stats.getLocation().name); - if (isSelected) { - label.setForeground(table.getSelectionForeground()); - } - final int count = stats.getValue(PerfMetric.lastFrame); - label.setIcon(getIconForCount(idle ? 0 : count, true)); - panel.add(Box.createHorizontalStrut(16)); - panel.add(label); - } - - clear(); - setPaintFocusBorder(hasFocus && table.getCellSelectionEnabled()); - acquireState(table, isSelected, hasFocus, row, col); - getCellState().updateRenderer(this); - - if (isSelected) { - panel.setBackground(table.getSelectionBackground()); - } - - return panel; - } - - public void setIdle(boolean idle) { - this.idle = idle; - } - } - - private static class WidgetLocationRenderer extends SimpleColoredRenderer implements TableCellRenderer { - @Override - public final Component getTableCellRendererComponent(JTable table, @Nullable Object value, - boolean isSelected, boolean hasFocus, int row, int col) { - final JPanel panel = new JPanel(); - if (value == null) return panel; - panel.setLayout(new BorderLayout()); - - if (value instanceof SlidingWindowStatsSummary stats) { - final SimpleTextAttributes attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES; - final Location location = stats.getLocation(); - final String path = location.path; - final String filename = PathUtil.getFileName(path); - append(filename, attributes); - - final JBLabel label = new JBLabel(filename); - label.setHorizontalAlignment(SwingConstants.RIGHT); - panel.add(Box.createHorizontalGlue()); - panel.add(label, BorderLayout.CENTER); - - final JBLabel lineLabel = new JBLabel(":" + location.line); - panel.add(lineLabel, BorderLayout.EAST); - - if (isSelected) { - label.setForeground(table.getSelectionForeground()); - lineLabel.setForeground(table.getSelectionForeground()); - } - } - - clear(); - setPaintFocusBorder(hasFocus && table.getCellSelectionEnabled()); - acquireState(table, isSelected, hasFocus, row, col); - getCellState().updateRenderer(this); - - if (isSelected) { - panel.setBackground(table.getSelectionBackground()); - } - - return panel; - } - } - - private static class CountRenderer extends SimpleColoredRenderer implements TableCellRenderer { - private final PerfMetric metric; - - CountRenderer(PerfMetric metric) { - this.metric = metric; - } - - @Override - public final Component getTableCellRendererComponent(JTable table, @Nullable Object value, - boolean isSelected, boolean hasFocus, int row, int col) { - final JPanel panel = new JPanel(); - if (value == null) return panel; - panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - - if (value instanceof SlidingWindowStatsSummary stats) { - final SimpleTextAttributes attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES; - final int count = stats.getValue(metric); - - final JBLabel label = new JBLabel(Integer.toString(count)); - panel.add(Box.createHorizontalGlue()); - panel.add(label); - panel.add(Box.createHorizontalStrut(8)); - - if (isSelected) { - label.setForeground(table.getSelectionForeground()); - } - } - - clear(); - setPaintFocusBorder(hasFocus && table.getCellSelectionEnabled()); - acquireState(table, isSelected, hasFocus, row, col); - getCellState().updateRenderer(this); - - if (isSelected) { - panel.setBackground(table.getSelectionBackground()); - } - - return panel; - } - } - - static class WidgetNameColumnInfo extends ColumnInfo { - private final WidgetNameRenderer renderer = new WidgetNameRenderer(); - - public WidgetNameColumnInfo(String name) { - super(name); - } - - @Nullable - @Override - public SlidingWindowStatsSummary valueOf(DefaultMutableTreeNode node) { - return (SlidingWindowStatsSummary)node.getUserObject(); - } - - @Override - public TableCellRenderer getRenderer(DefaultMutableTreeNode item) { - return renderer; - } - - @Override - public String getTooltipText() { - return "The widget that was rebuilt."; - } - - public void setIdle(boolean idle) { - renderer.setIdle(idle); - } - } - - static class LocationColumnInfo extends ColumnInfo { - private final TableCellRenderer renderer = new WidgetLocationRenderer(); - - public LocationColumnInfo(String name) { - super(name); - } - - @Nullable - @Override - public SlidingWindowStatsSummary valueOf(DefaultMutableTreeNode node) { - return (SlidingWindowStatsSummary)node.getUserObject(); - } - - @Override - public TableCellRenderer getRenderer(DefaultMutableTreeNode item) { - return renderer; - } - - @Override - public String getTooltipText() { - return "Click to locate the widget's constructor in the code."; - } - } - - static class CountColumnInfo extends ColumnInfo { - private final CountRenderer defaultRenderer; - private final PerfMetric metric; - - public CountColumnInfo(PerfMetric metric) { - super(metric.name); - this.metric = metric; - defaultRenderer = new CountRenderer(metric); - } - - @Nullable - @Override - public SlidingWindowStatsSummary valueOf(DefaultMutableTreeNode node) { - return (SlidingWindowStatsSummary)node.getUserObject(); - } - - @Override - public TableCellRenderer getRenderer(DefaultMutableTreeNode item) { - return defaultRenderer; - } - - @Override - public String getTooltipText() { - return switch (metric) { - case lastFrame -> "The number of times the widget was rebuilt in the last frame."; - case totalSinceEnteringCurrentScreen -> "The number of times the widget was rebuilt since entering the current screen."; - default -> null; - }; - } - } -} diff --git a/flutter-idea/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java b/flutter-idea/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java deleted file mode 100644 index 66f6a85bd8..0000000000 --- a/flutter-idea/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import com.google.common.collect.Lists; -import com.google.gson.JsonObject; -import com.intellij.codeHighlighting.BackgroundEditorHighlighter; -import com.intellij.mock.MockVirtualFileSystem; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorLocation; -import com.intellij.openapi.fileEditor.FileEditorState; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.Navigatable; -import io.flutter.inspector.DiagnosticsNode; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.JsonUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.junit.Ignore; -import org.junit.Test; - -import javax.swing.*; -import java.beans.PropertyChangeListener; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import static io.flutter.perf.FlutterWidgetPerf.IDLE_DELAY_MILISECONDS; -import static org.junit.Assert.*; - -class MockWidgetPerfProvider implements WidgetPerfProvider { - - WidgetPerfListener widgetPerfListener; - boolean isDisposed = false; - boolean shouldDisplayStats = true; - List> requests = new ArrayList<>(); - List> locationIdRequests = new ArrayList<>(); - - @Override - public void setTarget(WidgetPerfListener widgetPerfListener) { - this.widgetPerfListener = widgetPerfListener; - } - - @Override - public boolean isStarted() { - return true; - } - - @Override - public boolean isConnected() { - return true; - } - - @Override - public boolean shouldDisplayPerfStats(FileEditor editor) { - return shouldDisplayStats; - } - - @Override - public CompletableFuture getWidgetTree() { - return CompletableFuture.completedFuture(null); - } - - @Override - public void dispose() { - isDisposed = true; - } - - public void simulateWidgetPerfEvent(PerfReportKind kind, String json) { - widgetPerfListener.onWidgetPerfEvent(kind, (JsonObject)JsonUtils.parseString(json)); - } - - public void repaint(When when) { - widgetPerfListener.requestRepaint(when); - } -} - -class MockPerfModel implements PerfModel { - volatile int idleCount = 0; - volatile int clearCount = 0; - volatile int frameCount = 0; - - @Override - public void markAppIdle() { - idleCount++; - } - - @Override - public void clear() { - clearCount++; - } - - @Override - public void onFrame() { - frameCount++; - } - - @Override - public boolean isAnimationActive() { - return idleCount == 0; - } -} - -class MockEditorPerfModel extends MockPerfModel implements EditorPerfModel { - - public boolean isHoveredOverLineMarkerAreaOverride = false; - boolean isDisposed; - CompletableFuture statsFuture; - private FlutterApp app; - private FilePerfInfo stats; - private final TextEditor textEditor; - - MockEditorPerfModel(TextEditor textEditor) { - this.textEditor = textEditor; - statsFuture = new CompletableFuture<>(); - } - - @NotNull - @Override - public FilePerfInfo getStats() { - return stats; - } - - CompletableFuture getStatsFuture() { - return statsFuture; - } - - @NotNull - @Override - public TextEditor getTextEditor() { - return textEditor; - } - - @Override - public FlutterApp getApp() { - return app; - } - - @Override - public boolean getAlwaysShowLineMarkers() { - return isHoveredOverLineMarkerAreaOverride; - } - - @Override - public void setAlwaysShowLineMarkersOverride(boolean show) { - } - - @Override - public void markAppIdle() { - super.markAppIdle(); - stats.markAppIdle(); - } - - @Override - public void clear() { - super.clear(); - stats.clear(); - } - - @Override - public void setPerfInfo(@NotNull FilePerfInfo stats) { - this.stats = stats; - if (statsFuture.isDone()) { - statsFuture = new CompletableFuture<>(); - } - statsFuture.complete(stats); - } - - @Override - public boolean isAnimationActive() { - return stats.getTotalValue(PerfMetric.peakRecent) > 0; - } - - @Override - public void dispose() { - isDisposed = true; - } -} - -class FakeFileLocationMapper implements FileLocationMapper { - - private final String path; - - FakeFileLocationMapper(String path) { - this.path = path; - } - - Map mockText = new HashMap<>(); - - @Override - public TextRange getIdentifierRange(int line, int column) { - // Bogus but unique. - final int offset = line * 1000 + column; - // Dummy name so we can tell what the original line and column was in tests - final String text = "Widget:" + line + ":" + column; - final TextRange range = new TextRange(offset, offset + text.length()); - mockText.put(range, text); - return range; - } - - @Override - public String getText(TextRange textRange) { - assert mockText.containsKey(textRange); - return mockText.get(textRange); - } - - @Override - public String getPath() { - return path; - } -} - -class MockTextEditor implements TextEditor { - static MockVirtualFileSystem fileSystem = new MockVirtualFileSystem(); - private final String name; - private final VirtualFile file; - private boolean modified; - - MockTextEditor(String name) { - this.name = name; - file = fileSystem.findFileByPath(name); - } - - @NotNull - @Override - public Editor getEditor() { - throw new Error("not supported"); - } - - @Override - public VirtualFile getFile() { - return file; - } - - @Override - public boolean canNavigateTo(@NotNull Navigatable navigatable) { - return false; - } - - @Override - public void navigateTo(@NotNull Navigatable navigatable) { - - } - - @NotNull - @Override - public JComponent getComponent() { - throw new Error("not supported"); - } - - @Nullable - @Override - public JComponent getPreferredFocusedComponent() { - return null; - } - - @NotNull - @Override - public String getName() { - return name; - } - - @Override - public void setState(@NotNull FileEditorState state) { - - } - - @Override - public boolean isModified() { - return modified; - } - - @Override - public boolean isValid() { - return true; - } - - @Override - public void selectNotify() { - - } - - @Override - public void deselectNotify() { - - } - - @Override - public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { - - } - - @Override - public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { - - } - - @Nullable - @Override - public BackgroundEditorHighlighter getBackgroundHighlighter() { - return null; - } - - @Nullable - @Override - public FileEditorLocation getCurrentLocation() { - return null; - } - - @Override - public void dispose() { - - } - - @Nullable - @Override - public T getUserData(@NotNull Key key) { - return null; - } - - @Override - public void putUserData(@NotNull Key key, @Nullable T value) { - - } -} - -public class FlutterWidgetPerfTest { - - private TextRange getTextRange(String path, int line, int column) { - return new FakeFileLocationMapper(path).getIdentifierRange(line, column); - } - - @Test - @Ignore("https://github.com/flutter/flutter-intellij/issues/4343") - public void testFileStatsCalculation() throws InterruptedException, ExecutionException { - final MockWidgetPerfProvider widgetPerfProvider = new MockWidgetPerfProvider(); - - final Map perfModels = new HashMap<>(); - final FlutterWidgetPerf flutterWidgetPerf = new FlutterWidgetPerf( - true, - widgetPerfProvider, - (TextEditor textEditor) -> { - final MockEditorPerfModel model = new MockEditorPerfModel(textEditor); - perfModels.put(textEditor.getName(), model); - return model; - }, - FakeFileLocationMapper::new - ); - widgetPerfProvider.simulateWidgetPerfEvent( - PerfReportKind.rebuild, - "{\"startTime\":1000,\"events\":[1,1,2,1,3,1,4,1,6,1,10,4,11,4,12,4,13,1,14,1,95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1],\"newLocations\":{\"/sample/project/main.dart\":[1,11,14,2,18,16,3,23,17,4,40,16,6,46,16,10,69,9,11,70,9,12,71,18,13,41,19,14,42,20,95,51,58],\"/sample/project/clock.dart\":[96,33,12,97,52,12,100,53,16,102,54,14,104,55,17,105,34,15,106,35,16]}}" - ); - - // Simulate 60fps for 2 seconds. - for (int frame = 1; frame <= 120; frame++) { - final long startTime = 1000 + frame * 1000 * 1000 / 60; - widgetPerfProvider.simulateWidgetPerfEvent( - PerfReportKind.rebuild, "{\"startTime\":" + - startTime + - ",\"events\":[95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1]}"); - } - final Set textEditors = new HashSet<>(); - final TextEditor clockTextEditor = new MockTextEditor("/sample/project/clock.dart"); - textEditors.add(clockTextEditor); - flutterWidgetPerf.showFor(textEditors); - assertEquals(perfModels.size(), 1); - MockEditorPerfModel clockModel = perfModels.get("/sample/project/clock.dart"); - assertEquals(clockModel.getTextEditor(), clockTextEditor); - - FilePerfInfo stats = clockModel.getStatsFuture().get(); - // Stats for the first response are rebuilds only - assertEquals(1620, stats.getTotalValue(PerfMetric.pastSecond)); - List locations = Lists.newArrayList(stats.getLocations()); - assertEquals(7, locations.size()); - final TextRange textRange = getTextRange("/sample/project/clock.dart", 52, 12); - List rangeStats = Lists.newArrayList(stats.getRangeStats(textRange)); - - assertEquals(1, rangeStats.size()); - - assertEquals(PerfReportKind.rebuild, rangeStats.get(0).getKind()); - assertEquals(360, rangeStats.get(0).getValue(PerfMetric.pastSecond)); - assertEquals(726, rangeStats.get(0).getValue(PerfMetric.total)); - assertEquals("Widget:52:12", rangeStats.get(0).getDescription()); - - rangeStats = Lists.newArrayList(stats.getRangeStats(getTextRange("/sample/project/clock.dart", 34, 15))); - - assertEquals(1, rangeStats.size()); - assertEquals(PerfReportKind.rebuild, rangeStats.get(0).getKind()); - assertEquals(60, rangeStats.get(0).getValue(PerfMetric.pastSecond)); - assertEquals(121, rangeStats.get(0).getValue(PerfMetric.total)); - assertEquals("Widget:34:15", rangeStats.get(0).getDescription()); - - clockModel.markAppIdle(); - assertEquals(0, stats.getTotalValue(PerfMetric.pastSecond)); - rangeStats = Lists.newArrayList(stats.getRangeStats(locations.get(4))); - assertEquals(0, rangeStats.get(0).getValue(PerfMetric.pastSecond)); - // Total is not impacted. - assertEquals(121, rangeStats.get(0).getValue(PerfMetric.total)); - - rangeStats = Lists.newArrayList(stats.getRangeStats(textRange)); - - assertEquals(1, rangeStats.size()); - assertEquals(PerfReportKind.rebuild, rangeStats.get(0).getKind()); - assertEquals(0, rangeStats.get(0).getValue(PerfMetric.pastSecond)); - assertEquals(726, rangeStats.get(0).getValue(PerfMetric.total)); - assertEquals("Widget:52:12", rangeStats.get(0).getDescription()); - - final TextEditor mainTextEditor = new MockTextEditor("/sample/project/main.dart"); - textEditors.add(mainTextEditor); - - flutterWidgetPerf.clearModels(); - // Add events with both rebuilds and repaints. - widgetPerfProvider.simulateWidgetPerfEvent(PerfReportKind.rebuild, - "{\"startTime\":19687239,\"events\":[95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1]}"); - widgetPerfProvider.simulateWidgetPerfEvent(PerfReportKind.repaint, - "{\"startTime\":19687239,\"events\":[95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1]}"); - - flutterWidgetPerf.showFor(textEditors); - - assertEquals(2, perfModels.size()); - clockModel = perfModels.get("/sample/project/clock.dart"); - final MockEditorPerfModel mainModel = perfModels.get("/sample/project/main.dart"); - assert mainModel != null; - - final FilePerfInfo mainStats = mainModel.getStatsFuture().get(); - assert clockModel != null; - stats = clockModel.getStatsFuture().get(); - - // We have new fake data for the files so the count in the past second is - // back up from zero - assertEquals(2, mainStats.getTotalValue(PerfMetric.pastSecond)); - assertEquals(142, mainStats.getTotalValue(PerfMetric.total)); - - assertEquals(3321, stats.getTotalValue(PerfMetric.total)); - - locations = Lists.newArrayList(stats.getLocations()); - assertEquals(7, locations.size()); - rangeStats = Lists.newArrayList(stats.getRangeStats(getTextRange("/sample/project/clock.dart", 52, 12))); - - assertEquals(2, rangeStats.size()); - assertEquals(PerfReportKind.repaint, rangeStats.get(0).getKind()); - assertEquals(6, rangeStats.get(0).getValue(PerfMetric.pastSecond)); - assertEquals(6, rangeStats.get(0).getValue(PerfMetric.total)); - assertEquals("Widget:52:12", rangeStats.get(0).getDescription()); - - assertEquals(PerfReportKind.rebuild, rangeStats.get(1).getKind()); - assertEquals(6, rangeStats.get(1).getValue(PerfMetric.pastSecond)); - assertEquals(732, rangeStats.get(1).getValue(PerfMetric.total)); - assertEquals("Widget:52:12", rangeStats.get(1).getDescription()); - - rangeStats = Lists.newArrayList(stats.getRangeStats(getTextRange("/sample/project/clock.dart", 33, 12))); - - assertEquals(2, rangeStats.size()); - assertEquals(PerfReportKind.repaint, rangeStats.get(0).getKind()); - assertEquals(1, rangeStats.get(0).getValue(PerfMetric.pastSecond)); - assertEquals(1, rangeStats.get(0).getValue(PerfMetric.total)); - assertEquals("Widget:33:12", rangeStats.get(0).getDescription()); - - assertEquals(PerfReportKind.rebuild, rangeStats.get(1).getKind()); - assertEquals(1, rangeStats.get(1).getValue(PerfMetric.pastSecond)); - assertEquals(122, rangeStats.get(1).getValue(PerfMetric.total)); - assertEquals("Widget:33:12", rangeStats.get(1).getDescription()); - - assertFalse(clockModel.isDisposed); - assertFalse(mainModel.isDisposed); - - flutterWidgetPerf.showFor(new HashSet<>()); - - assertTrue(clockModel.isDisposed); - assertTrue(mainModel.isDisposed); - - flutterWidgetPerf.dispose(); - } - - @Test - public void testOverallStatsCalculation_oldFormat() throws InterruptedException { - final MockWidgetPerfProvider widgetPerfProvider = new MockWidgetPerfProvider(); - - final FlutterWidgetPerf flutterWidgetPerf = new FlutterWidgetPerf( - true, - widgetPerfProvider, - textEditor -> null, - FakeFileLocationMapper::new - ); - final MockPerfModel perfModel = new MockPerfModel(); - flutterWidgetPerf.addPerfListener(perfModel); - - widgetPerfProvider.simulateWidgetPerfEvent( - PerfReportKind.rebuild, - "{\"startTime\":1000,\"events\":[1,1,2,1,3,1,4,1,6,1,10,4,11,4,12,4,13,1,14,1,95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1],\"newLocations\":{\"/sample/project/main.dart\":[1,11,14,2,18,16,3,23,17,4,40,16,6,46,16,10,69,9,11,70,9,12,71,18,13,41,19,14,42,20,95,51,58],\"/sample/project/clock.dart\":[96,33,12,97,52,12,100,53,16,102,54,14,104,55,17,105,34,15,106,35,16]}}"); - - // Simulate 60fps for 2 seconds. - for (int frame = 1; frame <= 120; frame++) { - final long startTime = 1000 + frame * 1000 * 1000 / 60; - widgetPerfProvider.simulateWidgetPerfEvent( - PerfReportKind.rebuild, "{\"startTime\":" + - startTime + - ",\"events\":[95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1]}"); - } - final ArrayList lastFrameOnly = new ArrayList<>(); - lastFrameOnly.add(PerfMetric.lastFrame); - final ArrayList metrics = new ArrayList<>(); - metrics.add(PerfMetric.lastFrame); - metrics.add(PerfMetric.totalSinceEnteringCurrentScreen); - - ArrayList stats = flutterWidgetPerf.getStatsForMetric(lastFrameOnly, PerfReportKind.repaint); - // No repaint stats are provided. - assertTrue(stats.isEmpty()); - - stats = flutterWidgetPerf.getStatsForMetric(lastFrameOnly, PerfReportKind.rebuild); - assertEquals(8, stats.size()); - - for (SlidingWindowStatsSummary stat : stats) { - assertTrue(stat.getValue(PerfMetric.lastFrame) > 0); - } - - stats = flutterWidgetPerf.getStatsForMetric(metrics, PerfReportKind.rebuild); - assertEquals(26, stats.size()); - for (SlidingWindowStatsSummary stat : stats) { - assertTrue(stat.getValue(PerfMetric.lastFrame) > 0 || - stat.getValue(PerfMetric.totalSinceEnteringCurrentScreen) > 0); - } - - /// Test that the perfModel gets notified correctly when there are - // events to draw a frame of the ui. - assertEquals(perfModel.frameCount, 0); - SwingUtilities.invokeLater(() -> { - flutterWidgetPerf.requestRepaint(When.now); - }); - - while (perfModel.frameCount == 0) { - try { - //noinspection BusyWait - Thread.sleep(1); - } - catch (InterruptedException e) { - fail(e.toString()); - } - } - assertEquals(1, perfModel.frameCount); - assertEquals(0, perfModel.idleCount); - // Verify that an idle event occurs once we wait the idle time delay. - Thread.sleep(IDLE_DELAY_MILISECONDS); - Thread.yield(); - assertEquals(1, perfModel.idleCount); - - flutterWidgetPerf.removePerfListener(perfModel); - flutterWidgetPerf.dispose(); - } - - @Test - public void testOverallStatsCalculation_newFormat() throws InterruptedException { - final MockWidgetPerfProvider widgetPerfProvider = new MockWidgetPerfProvider(); - - final FlutterWidgetPerf flutterWidgetPerf = new FlutterWidgetPerf( - true, - widgetPerfProvider, - textEditor -> null, - FakeFileLocationMapper::new - ); - final MockPerfModel perfModel = new MockPerfModel(); - flutterWidgetPerf.addPerfListener(perfModel); - - widgetPerfProvider.simulateWidgetPerfEvent( - PerfReportKind.rebuild, - "{\"startTime\":1000,\"events\":[1,1,2,1,3,1,4,1,6,1,10,4,11,4,12,4,13,1,14,1,95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1],\"locations\":{\"/sample/project/main.dart\":{\t\"ids\":[1, 2, 3, 4, 6, 10, 11, 12, 13, 14, 95],\"lines\":[11, 18, 23, 40, 46, 69, 70, 71, 41, 42, 51],\"columns\":[14, 16, 17, 16, 16, 9, 9, 18, 19, 20, 58],\"names\":[\"One\", \"Two\", \"Three\", \"Four\", \"Five\", \"Six\", \"Seven\", \"Eight\", \"Nine\", \"Ten\", \"Eleven\"]},\"/sample/project/clock.dart\":{\"ids\":[96, 97, 100, 102, 104, 105, 106],\"lines\":[33, 52, 53, 54, 55, 34, 35],\"columns\":[12, 12, 16, 14, 17, 15, 16],\"names\":[\"Twelve\", \"Thirteen\", \"Fourteen\", \"Fifteen\", \"Sixteen\", \"Seventeen\", \"Eighteen\"]}}}"); - - // Simulate 60fps for 2 seconds. - for (int frame = 1; frame <= 120; frame++) { - final long startTime = 1000 + frame * 1000 * 1000 / 60; - widgetPerfProvider.simulateWidgetPerfEvent( - PerfReportKind.rebuild, "{\"startTime\":" + - startTime + - ",\"events\":[95,1,96,1,97,6,100,6,102,6,104,6,105,1,106,1]}"); - } - final ArrayList lastFrameOnly = new ArrayList<>(); - lastFrameOnly.add(PerfMetric.lastFrame); - final ArrayList metrics = new ArrayList<>(); - metrics.add(PerfMetric.lastFrame); - metrics.add(PerfMetric.totalSinceEnteringCurrentScreen); - - ArrayList stats = flutterWidgetPerf.getStatsForMetric(lastFrameOnly, PerfReportKind.repaint); - // No repaint stats are provided. - assertTrue(stats.isEmpty()); - - stats = flutterWidgetPerf.getStatsForMetric(lastFrameOnly, PerfReportKind.rebuild); - assertEquals(8, stats.size()); - - for (SlidingWindowStatsSummary stat : stats) { - assertTrue(stat.getValue(PerfMetric.lastFrame) > 0); - } - - stats = flutterWidgetPerf.getStatsForMetric(metrics, PerfReportKind.rebuild); - assertEquals(26, stats.size()); - for (SlidingWindowStatsSummary stat : stats) { - assertTrue(stat.getValue(PerfMetric.lastFrame) > 0 || - stat.getValue(PerfMetric.totalSinceEnteringCurrentScreen) > 0); - } - - /// Test that the perfModel gets notified correctly when there are - // events to draw a frame of the ui. - assertEquals(perfModel.frameCount, 0); - SwingUtilities.invokeLater(() -> { - flutterWidgetPerf.requestRepaint(When.now); - }); - - while (perfModel.frameCount == 0) { - try { - //noinspection BusyWait - Thread.sleep(1); - } - catch (InterruptedException e) { - fail(e.toString()); - } - } - assertEquals(1, perfModel.frameCount); - assertEquals(0, perfModel.idleCount); - // Verify that an idle event occurs once we wait the idle time delay. - Thread.sleep(IDLE_DELAY_MILISECONDS); - assertEquals(1, perfModel.idleCount); - - flutterWidgetPerf.removePerfListener(perfModel); - flutterWidgetPerf.dispose(); - } -} diff --git a/flutter-idea/testSrc/unit/io/flutter/perf/SlidingWindowStatsTest.java b/flutter-idea/testSrc/unit/io/flutter/perf/SlidingWindowStatsTest.java deleted file mode 100644 index 88fb689e6c..0000000000 --- a/flutter-idea/testSrc/unit/io/flutter/perf/SlidingWindowStatsTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2018 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.perf; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class SlidingWindowStatsTest { - @Test - public void simpleSlidingWindowStats() { - final SlidingWindowStats stats = new SlidingWindowStats(); - assertEquals(0, stats.getTotal()); - stats.add(1, 0); - stats.add(1, 1); - assertEquals(2, stats.getTotal()); - stats.add(1, 2); - stats.add(1, 3); - assertEquals(4, stats.getTotal()); - assertEquals(4, stats.getTotalWithinWindow(0)); - assertEquals(3, stats.getTotalWithinWindow(1)); - assertEquals(2, stats.getTotalWithinWindow(2)); - assertEquals(1, stats.getTotalWithinWindow(3)); - } - - @Test - public void totalSinceNavigation() { - final SlidingWindowStats stats = new SlidingWindowStats(); - assertEquals(0, stats.getTotal()); - stats.add(1, 0); - stats.add(1, 1); - assertEquals(2, stats.getTotalSinceNavigation()); - assertEquals(2, stats.getTotal()); - stats.onNavigation(); - assertEquals(0, stats.getTotalSinceNavigation()); - assertEquals(2, stats.getTotal()); - stats.add(1, 2); - assertEquals(1, stats.getTotalSinceNavigation()); - } - - private void add1000Times(SlidingWindowStats stats, int timeStamp) { - for (int i = 0; i < 1000; i++) { - stats.add(1, timeStamp); - } - } - - @Test - public void duplicateSlidingWindowStatTimestamps() { - final SlidingWindowStats stats = new SlidingWindowStats(); - - assertEquals(0, stats.getTotal()); - add1000Times(stats, 0); - add1000Times(stats, 1); - assertEquals(2000, stats.getTotal()); - add1000Times(stats, 2); - add1000Times(stats, 3); - assertEquals(4000, stats.getTotal()); - assertEquals(4000, stats.getTotalWithinWindow(0)); - assertEquals(3000, stats.getTotalWithinWindow(1)); - assertEquals(2000, stats.getTotalWithinWindow(2)); - assertEquals(1000, stats.getTotalWithinWindow(3)); - add1000Times(stats, 4); - add1000Times(stats, 5); - add1000Times(stats, 6); - add1000Times(stats, 7); - add1000Times(stats, 8); - add1000Times(stats, 9); - add1000Times(stats, 10); - assertEquals(11000, stats.getTotal()); - assertEquals(11000, stats.getTotalWithinWindow(0)); - add1000Times(stats, 11); - } - - @Test - public void clearSlidingWindowStats() { - final SlidingWindowStats stats = new SlidingWindowStats(); - stats.add(1, 0); - stats.add(1, 1); - stats.add(1, 2); - stats.add(1, 3); - assertEquals(4, stats.getTotal()); - assertEquals(4, stats.getTotalWithinWindow(0)); - - stats.clear(); - stats.add(1, 4); - stats.add(1, 5); - assertEquals(2, stats.getTotal()); - assertEquals(1, stats.getTotalWithinWindow(5)); - assertEquals(2, stats.getTotalWithinWindow(0)); - stats.clear(); - - // Intentionally shift timestamps backwards as could happen after a hot - // reload. - stats.add(1, 0); - stats.add(1, 1); - stats.add(1, 2); - stats.add(1, 3); - assertEquals(4, stats.getTotal()); - assertEquals(4, stats.getTotalWithinWindow(0)); - stats.add(1, 4); - stats.add(1, 5); - stats.add(1, 6); - stats.add(1, 7); - stats.add(1, 8); - stats.add(1, 9); - stats.add(1, 10); - assertEquals(11, stats.getTotal()); - assertEquals(11, stats.getTotalWithinWindow(0)); - } -} diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 49a9bbebad..402e555b4f 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -308,8 +308,6 @@ - @@ -389,10 +387,6 @@ factoryClass="io.flutter.view.FlutterViewFactory"/> - - - @@ -417,9 +411,6 @@ - - @@ -326,10 +324,6 @@ factoryClass="io.flutter.view.FlutterViewFactory"/> - - - @@ -354,9 +348,6 @@ -