diff --git a/build.gradle b/build.gradle index be858c4..166cc33 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,6 @@ allprojects { subprojects { apply plugin: 'idea' ext { - productVersion = '22.06.09.0' + productVersion = '22.08.08.0' } } diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java b/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java index 9770739..9fd5865 100644 --- a/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java +++ b/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java @@ -1,7 +1,5 @@ package com.github.grishberg.profiler.chart; -import com.github.grishberg.profiler.core.AnalyzerResult; -import com.github.grishberg.profiler.core.ProfileData; import com.github.grishberg.profiler.analyzer.AnalyzerResultImpl; import com.github.grishberg.profiler.analyzer.ThreadTimeBoundsImpl; import com.github.grishberg.profiler.chart.highlighting.MethodsColorImpl; @@ -14,6 +12,8 @@ import com.github.grishberg.profiler.common.TraceContainer; import com.github.grishberg.profiler.common.settings.SettingsFacade; import com.github.grishberg.profiler.comparator.model.ComparableProfileData; +import com.github.grishberg.profiler.core.AnalyzerResult; +import com.github.grishberg.profiler.core.ProfileData; import com.github.grishberg.profiler.ui.BookMarkInfo; import com.github.grishberg.profiler.ui.SimpleComponentListener; import com.github.grishberg.profiler.ui.TimeFormatter; @@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.lang.model.type.ArrayType; import javax.swing.JPanel; import java.awt.Color; import java.awt.Dimension; @@ -57,7 +56,7 @@ public class CallTracePanel extends JPanel implements ProfileDataDimensionDelega private static final double MINIMUM_WIDTH_IN_PX = 1; private static final AnalyzerResultImpl RESULT_STUB = new AnalyzerResultImpl(Collections.emptyMap(), Collections.emptyMap(), 0, Collections.emptyMap(), Collections.emptyList(), 0, -1); - private final FoundInfoListener foundInfoListener; + private final FoundNavigationListener foundNavigationListener; private boolean init = true; private final Bookmarks bookmarks; @@ -111,7 +110,7 @@ public class CallTracePanel extends JPanel implements ProfileDataDimensionDelega public CallTracePanel(TimeFormatter timeFormatter, MethodsColorImpl methodsColor, - FoundInfoListener foundInfoListener, + FoundNavigationListener foundInfoListener, SettingsFacade settings, AppLogger logger, DependenciesFoundAction dependenciesFoundAction, @@ -123,7 +122,7 @@ public CallTracePanel(TimeFormatter timeFormatter, Palette palette) { this.timeFormatter = timeFormatter; this.methodsColor = methodsColor; - this.foundInfoListener = foundInfoListener; + this.foundNavigationListener = foundInfoListener; this.settings = settings; this.logger = logger; this.stagesFacade = stagesFacade; @@ -839,39 +838,28 @@ private int findElementIndexByXY(double x, double y) { return -1; } - public void findItems(String textToFind, boolean ignoreCase) { - boolean shouldEndsWithText = textToFind.endsWith("()"); - if (shouldEndsWithText) { - textToFind = textToFind.substring(0, textToFind.length() - 2); - } + public void renderFoundItems(Finder.ThreadFindResult threadFindResult) { isSearchingInProgress = true; - String targetString = ignoreCase ? textToFind.toLowerCase() : textToFind; - foundItems.clear(); List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList()); for (int i = 0; i < objectsForThread.size(); i++) { ProfileRectangle element = objectsForThread.get(i); - String lowerCasedName = ignoreCase ? element.profileData.getName().toLowerCase() : element.profileData.getName(); - boolean isEquals = shouldEndsWithText ? lowerCasedName.endsWith(targetString) : lowerCasedName.contains(targetString); - if (isEquals) { + if (threadFindResult.hasMethod(element.profileData)) { foundItems.add(element); element.isFoundElement = true; } else { element.isFoundElement = false; } } - if (foundItems.isEmpty()) { - foundInfoListener.onNotFound(textToFind, ignoreCase); - isSearchingInProgress = false; - repaint(); - return; - } + currentFocusedFoundElement = 0; ProfileRectangle element = foundItems.get(currentFocusedFoundElement); - foundInfoListener.onFound(foundItems.size(), currentFocusedFoundElement, element.profileData); + foundNavigationListener.onSelected(foundItems.size(), currentFocusedFoundElement, element.profileData); zoomAndPanDelegate.fitZoom(element, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED); + + requestFocus(); } private void navigateToElement(Shape element) { @@ -965,26 +953,41 @@ public void focusPrevFoundItem() { if (foundItems.size() > 0) { currentFocusedFoundElement--; if (currentFocusedFoundElement < 0) { - currentFocusedFoundElement = foundItems.size() - 1; + foundNavigationListener.onNavigatedOverFirstItem(); + return; } - ProfileRectangle found = foundItems.get(currentFocusedFoundElement); - zoomAndPanDelegate.fitZoom(found, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED); - foundInfoListener.onFound(foundItems.size(), currentFocusedFoundElement, found.profileData); + + focusFoundItem(currentFocusedFoundElement); } } + public void resetFoundItemToEnd() { + currentFocusedFoundElement = foundItems.size() - 1; + focusFoundItem(currentFocusedFoundElement); + } + + private void focusFoundItem(int currentFocusedFoundElement) { + ProfileRectangle found = foundItems.get(currentFocusedFoundElement); + zoomAndPanDelegate.fitZoom(found, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED); + foundNavigationListener.onSelected(foundItems.size(), currentFocusedFoundElement, found.profileData); + } + public void focusNextFoundItem() { if (foundItems.size() > 0) { currentFocusedFoundElement++; if (currentFocusedFoundElement >= foundItems.size()) { - currentFocusedFoundElement = 0; + foundNavigationListener.onNavigatedOverLastItem(); + return; } - ProfileRectangle found = foundItems.get(currentFocusedFoundElement); - zoomAndPanDelegate.fitZoom(found, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED); - foundInfoListener.onFound(foundItems.size(), currentFocusedFoundElement, found.profileData); + focusFoundItem(currentFocusedFoundElement); } } + public void resetFoundItemToStart() { + currentFocusedFoundElement = 0; + focusFoundItem(currentFocusedFoundElement); + } + public void focusPrevMarker() { BookmarksRectangle currentSelectedBookmark = bookmarks.focusPreviousBookmark(); if (currentSelectedBookmark != null) { diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/Finder.kt b/core/src/main/java/com/github/grishberg/profiler/chart/Finder.kt index 8096512..95b26c1 100644 --- a/core/src/main/java/com/github/grishberg/profiler/chart/Finder.kt +++ b/core/src/main/java/com/github/grishberg/profiler/chart/Finder.kt @@ -1,37 +1,130 @@ package com.github.grishberg.profiler.chart +import com.github.grishberg.profiler.common.CoroutinesDispatchers import com.github.grishberg.profiler.core.AnalyzerResult import com.github.grishberg.profiler.core.ProfileData +import com.github.grishberg.profiler.core.ThreadItem +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class Finder( - private val analyzerResult: AnalyzerResult + private val coroutineScope: CoroutineScope, + private val dispatchers: CoroutinesDispatchers ) { - fun findInThread(textToFind: String, ignoreCase: Boolean, exceptThreadId: Int = -1): FindResult { - for (currentData in analyzerResult.data) { - val threadId = currentData.key + interface FindResultListener { + fun onFindDone(findResult: FindResult) + } + + var listener: FindResultListener? = null + + private var currentSelectedThreadIndex: Int = -1 + + private var currentFindResult: FindResult = FindResult(emptyList()) + + fun getSearchResultThreadsCount(): Int { + return currentFindResult.threadResults.size + } + + fun getCurrentThreadResult(): ThreadFindResult = currentFindResult.threadResults[currentSelectedThreadIndex] + + fun getNextThread(): ThreadItem { + val index = getNextThreadIndex() + return currentFindResult.threadResults[index].threadItem + } + + fun getPreviousThread(): ThreadItem { + val index = getPreviousThreadIndex() + return currentFindResult.threadResults[index].threadItem + } + + fun switchNextThread() { + currentSelectedThreadIndex = getNextThreadIndex() + } + + private fun getNextThreadIndex(): Int { + if (currentSelectedThreadIndex == -1) { + return -1 + } + return if (currentSelectedThreadIndex + 1 >= currentFindResult.threadResults.size) { + 0 + } else { + currentSelectedThreadIndex + 1 + } + } + + fun switchPreviousThread() { + currentSelectedThreadIndex = getPreviousThreadIndex() + } + + private fun getPreviousThreadIndex(): Int { + if (currentSelectedThreadIndex == -1) { + return -1 + } + + return if (currentSelectedThreadIndex - 1 < 0) { + currentFindResult.threadResults.size - 1 + } else { + currentSelectedThreadIndex - 1 + } + } + + fun findMethods( + analyzerResult: AnalyzerResult, + textToFind: String, + ignoreCase: Boolean, + ) { + currentSelectedThreadIndex = -1 + coroutineScope.launch { + val data = withContext(dispatchers.worker) { + findInThread(analyzerResult, textToFind, ignoreCase) + } + withContext(dispatchers.ui) { + currentFindResult = data + if (data.threadResults.isNotEmpty()) { + currentSelectedThreadIndex = 0 + } + listener?.onFindDone(data) + } + } + } + + private fun findInThread( + analyzerResult: AnalyzerResult, + textToFind: String, + ignoreCase: Boolean, + exceptThreadId: Int = -1 + ): FindResult { + val result = mutableListOf() + for (threadData in analyzerResult.data) { + val threadId = threadData.key if (exceptThreadId == threadId) { continue } - val profileList = currentData.value - + val profileList = threadData.value val shouldEndsWithText: Boolean = textToFind.endsWith("()") val targetString = prepareTextToFind(shouldEndsWithText, textToFind, ignoreCase) + val foundMethods = mutableSetOf() for (i in profileList.indices) { - val profileData = profileList[i] - val lowerCasedName: String = if (ignoreCase) profileData.name.toLowerCase() else profileData.name + val currentMethod = profileList[i] + val lowerCasedName: String = if (ignoreCase) currentMethod.name.toLowerCase() else currentMethod.name val isEquals: Boolean = if (shouldEndsWithText) lowerCasedName.endsWith(targetString) else lowerCasedName.contains( targetString ) if (isEquals) { - return FindResult(listOf(profileData), threadId) + foundMethods.add(currentMethod) } } + if (foundMethods.isNotEmpty()) { + val threadItem = analyzerResult.threads.find { it.threadId == threadId }!! + result.add(ThreadFindResult(foundMethods, threadId, threadItem)) + } } - return FindResult(emptyList(), -1) + return FindResult(result) } private fun prepareTextToFind( @@ -48,5 +141,25 @@ class Finder( return targetString } - data class FindResult(val foundResult: List, val threadId: Int) + data class FindResult(val threadResults: List) { + fun generateFoundThreadNames(): String { + if (threadResults.isEmpty()) { + return "" + } + val threadNames = threadResults.map { it.threadItem.name } + return threadNames.joinToString("\n") + } + + fun getResultForThread(threadId: Int): ThreadFindResult? { + return threadResults.find { it.threadId == threadId } + } + } + + data class ThreadFindResult( + val foundResult: Set, + val threadId: Int, + val threadItem: ThreadItem + ) { + fun hasMethod(method: ProfileData) = foundResult.contains(method) + } } diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/FoundInfoListener.kt b/core/src/main/java/com/github/grishberg/profiler/chart/FoundInfoListener.kt index 0e70dc1..bdd201b 100644 --- a/core/src/main/java/com/github/grishberg/profiler/chart/FoundInfoListener.kt +++ b/core/src/main/java/com/github/grishberg/profiler/chart/FoundInfoListener.kt @@ -1,5 +1,6 @@ package com.github.grishberg.profiler.chart +@Deprecated("Use FoundNavigationListener") interface FoundInfoListener { fun onFound(count: Int, selectedIndex: Int, selectedElement: T) fun onNotFound(text: String, ignoreCase: Boolean) diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/FoundNavigationListener.kt b/core/src/main/java/com/github/grishberg/profiler/chart/FoundNavigationListener.kt new file mode 100644 index 0000000..e991dec --- /dev/null +++ b/core/src/main/java/com/github/grishberg/profiler/chart/FoundNavigationListener.kt @@ -0,0 +1,7 @@ +package com.github.grishberg.profiler.chart + +interface FoundNavigationListener { + fun onSelected(count: Int, selectedIndex: Int, selectedElement: T) + fun onNavigatedOverLastItem() + fun onNavigatedOverFirstItem() +} diff --git a/core/src/main/java/com/github/grishberg/profiler/ui/Main.java b/core/src/main/java/com/github/grishberg/profiler/ui/Main.java index 1e09fbe..1b59969 100644 --- a/core/src/main/java/com/github/grishberg/profiler/ui/Main.java +++ b/core/src/main/java/com/github/grishberg/profiler/ui/Main.java @@ -1,8 +1,5 @@ package com.github.grishberg.profiler.ui; -import com.github.grishberg.profiler.core.AnalyzerResult; -import com.github.grishberg.profiler.core.ProfileData; -import com.github.grishberg.profiler.core.ThreadItem; import com.github.grishberg.profiler.analyzer.FlatMethodsReportGenerator; import com.github.grishberg.profiler.chart.BookmarkPopupMenu; import com.github.grishberg.profiler.chart.Bookmarks; @@ -10,7 +7,7 @@ import com.github.grishberg.profiler.chart.CallTracePanel; import com.github.grishberg.profiler.chart.CallTracePreviewPanel; import com.github.grishberg.profiler.chart.Finder; -import com.github.grishberg.profiler.chart.FoundInfoListener; +import com.github.grishberg.profiler.chart.FoundNavigationListener; import com.github.grishberg.profiler.chart.MethodsPopupMenu; import com.github.grishberg.profiler.chart.flame.FlameChartController; import com.github.grishberg.profiler.chart.flame.FlameChartDialog; @@ -37,6 +34,9 @@ import com.github.grishberg.profiler.comparator.ComparatorUIListener; import com.github.grishberg.profiler.comparator.OpenTraceToCompareCallback; import com.github.grishberg.profiler.comparator.model.ComparableProfileData; +import com.github.grishberg.profiler.core.AnalyzerResult; +import com.github.grishberg.profiler.core.ProfileData; +import com.github.grishberg.profiler.core.ThreadItem; import com.github.grishberg.profiler.plugins.PluginsFacade; import com.github.grishberg.profiler.ui.dialogs.KeymapDialog; import com.github.grishberg.profiler.ui.dialogs.LoadingDialog; @@ -49,7 +49,6 @@ import com.github.grishberg.profiler.ui.dialogs.info.FocusElementDelegate; import com.github.grishberg.profiler.ui.dialogs.recorder.JavaMethodsRecorderDialogView; import com.github.grishberg.profiler.ui.dialogs.recorder.JavaMethodsRecorderLogicKt; -import com.github.grishberg.profiler.ui.dialogs.recorder.RecordedResult; import com.github.grishberg.profiler.ui.theme.ThemeController; import com.github.grishberg.tracerecorder.SystraceRecordResult; import org.jetbrains.annotations.NotNull; @@ -91,7 +90,7 @@ import java.io.File; public class Main implements ZoomAndPanDelegate.MouseEventsListener, - FoundInfoListener, ActionListener, ShowDialogDelegate, CallTracePanel.OnRightClickListener, UpdatesChecker.UpdatesFoundAction { + FoundNavigationListener, ActionListener, ShowDialogDelegate, CallTracePanel.OnRightClickListener, UpdatesChecker.UpdatesFoundAction { @Nullable private File currentOpenedFile; @@ -146,6 +145,7 @@ public enum StartMode { private final MethodsColorImpl methodsColor; private JToolBar toolBar = new JToolBar(); private boolean allowModalDialogs; + private final Finder methodsFinder; @Nullable private TraceContainer resultContainer; @@ -288,6 +288,9 @@ public void focusProfileElement(@NotNull ProfileData selectedElement) { previewImageRepository = new PreviewImageRepository(imageFactory, settings, log, coroutineScope, coroutinesDispatchers); + methodsFinder = new Finder(coroutineScope, coroutinesDispatchers); + methodsFinder.setListener(new MethodsFinderListener()); + chart = new CallTracePanel( timeFormatter, methodsColor, @@ -872,49 +875,113 @@ public void onControlShiftMouseClicked(Point point, double x, double y) { // do nothing } + private void findMethods() { + final String textToFind = findClassText.getText(); + if (textToFind == null || textToFind.length() == 0) { + return; + } + + methodsFinder.findMethods(resultContainer.getResult(), textToFind, !caseInsensitiveToggle.isSelected()); + } + + private class MethodsFinderListener implements Finder.FindResultListener { + @Override + public void onFindDone(@NotNull Finder.FindResult findResult) { + if (findResult.getThreadResults().isEmpty()) { + JOptionPane.showMessageDialog(chart, "Not found"); + return; + } + + ThreadItem currentThread = switchThreadsButton.getCurrentThread(); + if (resultContainer == null || currentThread == null) { + return; + } + + Finder.ThreadFindResult threadFindResult = findResult.getResultForThread(currentThread.getThreadId()); + if (threadFindResult == null) { + onResultsFoundInOtherThreads(findResult); + return; + } + + onResultsFoundInCurrentAndOtherThreads(threadFindResult, findResult); + } + } + + private void onResultsFoundInOtherThreads(Finder.FindResult findResult) { + boolean shouldSwitchThread = JOptionPane.showConfirmDialog(frame, "Found results in another threads: \n" + + findResult.generateFoundThreadNames() + + "\n\nShould switch to first one?", + "Found in another thread", JOptionPane.YES_NO_OPTION) == 0; + + if (shouldSwitchThread) { //The ISSUE is here + Finder.ThreadFindResult firstThreadResult = findResult.getThreadResults().get(0); + switchThread(firstThreadResult.getThreadItem()); + chart.renderFoundItems(firstThreadResult); + } + } + + private void onResultsFoundInCurrentAndOtherThreads(Finder.ThreadFindResult threadFindResult, Finder.FindResult findResult) { + JOptionPane.showMessageDialog(frame, "Found results in multiple threads: \n" + + findResult.generateFoundThreadNames() + + "\"\n"); + + chart.renderFoundItems(threadFindResult); + } + @Override - public void onFound(int count, int selectedIndex, ProfileData profileData) { + public void onSelected(int count, int selectedIndex, ProfileData selectedElement) { foundInfo.setText(String.format("found %d, current %d", count, selectedIndex)); - showMethodInfoInTopPanel(profileData); + showMethodInfoInTopPanel(selectedElement); } @Override - public void onNotFound(String text, boolean ignoreCase) { - foundInfo.setText("not found"); - - ThreadItem thread = switchThreadsButton.getCurrentThread(); - if (resultContainer == null || thread == null) { - return; - } - Finder finder = new Finder(resultContainer.getResult()); - Finder.FindResult result = finder.findInThread(text, ignoreCase, thread.getThreadId()); - if (result.getFoundResult().isEmpty()) { - JOptionPane.showMessageDialog(chart, "Not found"); + public void onNavigatedOverLastItem() { + if (methodsFinder.getSearchResultThreadsCount() == 1) { + chart.resetFoundItemToStart(); return; } - int threadIndex = 0; - for (ThreadItem item : resultContainer.getResult().getThreads()) { - if (item.getThreadId() == result.getThreadId()) { - break; - } - threadIndex++; + ThreadItem nextThread = methodsFinder.getNextThread(); + + //if has any result in previous threads - ask and switch + boolean shouldSwitchThread = JOptionPane.showConfirmDialog(frame, "Switch to results in thread: \"" + + nextThread.getName() + + "\"", + "Switch to another thread", JOptionPane.YES_NO_OPTION) == 0; + + if (shouldSwitchThread) { + methodsFinder.switchNextThread(); + switchThread(nextThread); + chart.renderFoundItems(methodsFinder.getCurrentThreadResult()); + } else { + chart.resetFoundItemToStart(); } - ThreadItem foundThreadItem = resultContainer.getResult().getThreads().get(threadIndex); - if (foundThreadItem == null) { - JOptionPane.showMessageDialog(chart, "Not found"); + + chart.requestFocus(); + } + + @Override + public void onNavigatedOverFirstItem() { + if (methodsFinder.getSearchResultThreadsCount() == 1) { + chart.resetFoundItemToEnd(); return; } - boolean shouldSwitchThread = JOptionPane.showConfirmDialog(frame, "Found results in another thread: \"" + foundThreadItem.getName() + - "\"\nShould switch to this thread?", - "Found in another thread", JOptionPane.YES_NO_OPTION) == 0; + ThreadItem previousThread = methodsFinder.getPreviousThread(); - if (shouldSwitchThread) { //The ISSUE is here - switchThread(foundThreadItem); - chart.requestFocus(); - chart.findItems(text, ignoreCase); + //if has any result in previous threads - ask and switch + boolean shouldSwitchThread = JOptionPane.showConfirmDialog(frame, "Switch to results in thread: \"" + + previousThread.getName() + + "\"", + "Switch to another thread", JOptionPane.YES_NO_OPTION) == 0; + + if (shouldSwitchThread) { + methodsFinder.switchPreviousThread(); + switchThread(previousThread); + chart.renderFoundItems(methodsFinder.getCurrentThreadResult()); } + chart.resetFoundItemToEnd(); + chart.requestFocus(); } public void findAllChildren() { @@ -1150,24 +1217,13 @@ public TraceContainer getResultContainer() { return resultContainer; } - private void findInMethod() { - String textToFind = findClassText.getText(); - if (textToFind != null && textToFind.length() > 0) { - chart.findItems(textToFind, !caseInsensitiveToggle.isSelected()); - chart.requestFocus(); - return; - } - } - private class FindInMethodsAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - findInMethod(); + findMethods(); } } - - private class ParseWorker extends SwingWorker { private final File traceFile; private SystraceRecordResult systraceRecords; @@ -1284,11 +1340,12 @@ public WorkerResult(TraceContainer traceContainer) { private class CaseChangeFlagListener implements ChangeListener { private boolean oldSelectedState = false; + @Override public void stateChanged(ChangeEvent e) { - if (oldSelectedState != caseInsensitiveToggle.isSelected()){ + if (oldSelectedState != caseInsensitiveToggle.isSelected()) { oldSelectedState = caseInsensitiveToggle.isSelected(); - findInMethod(); + findMethods(); } } } diff --git a/plugin/build.gradle b/plugin/build.gradle index d2daae0..8b4c0f9 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -34,7 +34,7 @@ if (!hasProperty('studioCompilePath')) { intellij { updateSinceUntilBuild false plugins ['android'] - version "212-EAP-SNAPSHOT" + version "213-EAP-SNAPSHOT" } runIde { @@ -70,8 +70,7 @@ task(verifySetup) { patchPluginXml { sinceBuild("211") changeNotes """ - Fixed swithing threads and other dialogs result.
- Fixed waiting for application 120 sec when acitivy is not entered.
+ Notify found results in other threads.
""" }