From afea8f533c54c2ed75ea092be2342fcf7875b62d Mon Sep 17 00:00:00 2001 From: rx Date: Fri, 30 Dec 2016 15:18:14 +0100 Subject: [PATCH] VERY IMPORTANT PERFORMANCE UPDATE : Decoupled Views from ProfileListener There was a serious performance issue with large logs. The UI would hang or even OOM. One of the underlying causes was that every view (Flat, Tree, FlameGraph) was a Listener, and they all would be updated when a new Profile was selected. In case of the FlameGraph View, the performance hit is extremely large, and the UI would hang. Now, the views have been decoupled. The ProfileContext containing an ObjectProperty is created first, and the different views will bind or unbind their own ObjectProperty to that when they are shown or hidden. As a result, the rendition for each view only takes place at the time they are shown, and even then only if the Profile changes. When they are hidden, they no longer react to new Profiles emitted by the back end until such a time as they are shown again. --- .../controller/FlameViewController.java | 14 ++- .../javafx/controller/FlatViewController.java | 34 +++--- .../javafx/controller/ProfileController.java | 8 -- .../controller/ProfileRootController.java | 72 +++++------ .../controller/ProfileViewController.java | 54 +++++++++ .../javafx/controller/RootController.java | 77 ++++++++---- .../javafx/controller/TreeViewController.java | 114 +++++++++--------- .../ports/javafx/model/ProfileContext.java | 108 +++++++---------- .../model/task/InitializeProfileTask.java | 64 +++++----- .../ports/javafx/util/DialogUtil.java | 15 ++- .../ports/javafx/util/FxUtil.java | 14 ++- 11 files changed, 318 insertions(+), 256 deletions(-) delete mode 100644 src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileController.java create mode 100644 src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileViewController.java diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlameViewController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlameViewController.java index e46c0f770..3048ffc37 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlameViewController.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlameViewController.java @@ -19,13 +19,12 @@ package com.insightfullogic.honest_profiler.ports.javafx.controller; import com.insightfullogic.honest_profiler.core.profiles.FlameGraph; -import com.insightfullogic.honest_profiler.core.profiles.FlameGraphListener; import com.insightfullogic.honest_profiler.ports.javafx.view.FlameGraphCanvas; import javafx.fxml.FXML; import javafx.scene.layout.VBox; -public class FlameViewController extends AbstractController implements FlameGraphListener +public class FlameViewController extends ProfileViewController { @FXML private VBox rootContainer; @@ -33,8 +32,10 @@ public class FlameViewController extends AbstractController implements FlameGrap private FlameGraphCanvas flameView; @FXML - public void initialize() + protected void initialize() { + super.initialize(profileContext -> profileContext.flameGraphProperty()); + flameView = new FlameGraphCanvas(); rootContainer.getChildren().add(flameView); } @@ -63,8 +64,13 @@ public void refreshFlameView() } @Override - public void accept(final FlameGraph flameGraph) + protected void refresh(FlameGraph flameGraph) { + if (flameGraph == null) + { + return; + } + flameView.accept(flameGraph); } } diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlatViewController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlatViewController.java index c75183c8e..47c9e0e53 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlatViewController.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/FlatViewController.java @@ -37,7 +37,6 @@ import com.insightfullogic.honest_profiler.core.profiles.Profile; import com.insightfullogic.honest_profiler.ports.javafx.controller.filter.FilterDialogController; import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext; -import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; import com.insightfullogic.honest_profiler.ports.javafx.model.filter.FilterSpecification; import com.insightfullogic.honest_profiler.ports.javafx.util.DialogUtil; import com.insightfullogic.honest_profiler.ports.javafx.util.report.ReportUtil; @@ -60,7 +59,7 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.MouseEvent; -public class FlatViewController extends AbstractController +public class FlatViewController extends ProfileViewController { @FXML private Button filterButton; @@ -91,13 +90,13 @@ public class FlatViewController extends AbstractController private FilterDialogController filterDialogController; private ObjectProperty filterSpec; - private ProfileContext profileContext; - private ProfileFilter currentFilter; @FXML - private void initialize() + protected void initialize() { + super.initialize(profileContext -> profileContext.profileProperty()); + info(filterButton, "Specify filters restricting the visible entries"); info(compareButton, "Click to select another open profile to compare this profile against"); info(exportButton, "Export the visible entries to a CSV file"); @@ -126,25 +125,16 @@ private void initialize() @Override public void setApplicationContext(ApplicationContext applicationContext) { - super.setApplicationContext(applicationContext); filterDialogController.setApplicationContext(appCtx()); } - public void setProfileContext(ProfileContext profileContext) - { - this.profileContext = profileContext; - profileContext.profileProperty() - .addListener((property, oldValue, newValue) -> refresh(newValue)); - } - // Initialization Helper Methods private void initializeComparison() { compareButton.setGraphic(viewFor(COMPARE_16)); - compareButton - .setTooltip(new Tooltip("Compare this profile with another open profile")); + compareButton.setTooltip(new Tooltip("Compare this profile with another open profile")); compareButton.setOnMousePressed(new EventHandler() { @Override @@ -178,7 +168,7 @@ private void initializeFilter() newValue == null || !newValue.isFiltering() ? viewFor(FUNNEL_16) : viewFor(FUNNEL_ACTIVE_16)); currentFilter = new ProfileFilter(newValue.getFilters()); - refresh(profileContext.getProfile()); + refresh(getTarget()); }); filterButton.setGraphic(viewFor(FUNNEL_16)); @@ -212,8 +202,14 @@ private void configureTimeShareColumn(TableColumn colu // Refresh Methods - private void refresh(Profile profile) + @Override + protected void refresh(Profile profile) { + if (profile == null) + { + return; + } + Profile newProfile = profile.copy(); currentFilter.accept(newProfile); @@ -233,14 +229,14 @@ private void refreshContextMenu(ContextMenu menu) profileNames.forEach(name -> { - if (name.equals(profileContext.getName())) + if (name.equals(prContext().getName())) { return; } MenuItem item = new MenuItem(name); item.setOnAction( - event -> appCtx().createDiffView(profileContext.getName(), name)); + event -> appCtx().createDiffView(prContext().getName(), name)); menu.getItems().add(item); }); } diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileController.java deleted file mode 100644 index f2045d069..000000000 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileController.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.insightfullogic.honest_profiler.ports.javafx.controller; - -import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; - -public interface ProfileController -{ - ProfileContext getProfileContext(); -} diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileRootController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileRootController.java index 99463e15c..f1f35c33d 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileRootController.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileRootController.java @@ -18,18 +18,13 @@ **/ package com.insightfullogic.honest_profiler.ports.javafx.controller; -import static com.insightfullogic.honest_profiler.ports.javafx.ViewType.FLAME; import static com.insightfullogic.honest_profiler.ports.javafx.ViewType.FLAT; import static com.insightfullogic.honest_profiler.ports.javafx.util.ConversionUtil.getStringConverterForType; -import com.insightfullogic.honest_profiler.core.profiles.Profile; -import com.insightfullogic.honest_profiler.core.profiles.ProfileListener; import com.insightfullogic.honest_profiler.ports.javafx.ViewType; import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext; import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; -import com.insightfullogic.honest_profiler.ports.javafx.model.task.InitializeProfileTask; -import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.ChoiceBox; @@ -37,7 +32,6 @@ import javafx.scene.layout.AnchorPane; public class ProfileRootController extends AbstractController - implements ProfileListener, ProfileController { @FXML private ChoiceBox viewChoice; @@ -52,8 +46,6 @@ public class ProfileRootController extends AbstractController @FXML private FlameViewController flameController; - private ProfileContext profileContext; - @FXML public void initialize() { @@ -61,18 +53,6 @@ public void initialize() viewChoice, "Select the View : Flat View lists all methods as a list; Tree View shows the stack trees per thread; Flame View shows the Flame Graph"); info(traceCount, "Shows the number of samples in the profile"); - - profileContext = new ProfileContext(); - profileContext.addListeners(this); - - flatController.setProfileContext(profileContext); - treeController.setProfileContext(profileContext); - - viewChoice.getSelectionModel().selectedItemProperty() - .addListener((property, oldValue, newValue) -> show(newValue)); - viewChoice.setConverter(getStringConverterForType(ViewType.class)); - viewChoice.getItems().addAll(ViewType.values()); - viewChoice.getSelectionModel().select(FLAT); } // Instance Accessors @@ -86,28 +66,22 @@ public void setApplicationContext(ApplicationContext applicationContext) flameController.setApplicationContext(applicationContext); } - @Override - public ProfileContext getProfileContext() - { - return profileContext; - } - - // ProfileListener Implementation - - /** - * Not threadsafe: must be run on JavaFx thread. - */ - @Override - public void accept(Profile profile) + public void setProfileContext(ProfileContext profileContext) { - traceCount.setText(profile.getTraceCount() + " samples"); - } + flatController.setProfileContext(profileContext); + treeController.setProfileContext(profileContext); + flameController.setProfileContext(profileContext); - // Profiling startup + traceCount.setText(profileContext.getProfile().getTraceCount() + " samples"); + profileContext.profileProperty().addListener( + (property, oldValue, newValue) -> traceCount + .setText(newValue == null ? null : newValue.getTraceCount() + " samples")); - public Task getProfileInitializationTask(Object source, boolean live) - { - return new InitializeProfileTask(appCtx(), profileContext, source, live, flameController); + viewChoice.setConverter(getStringConverterForType(ViewType.class)); + viewChoice.getItems().addAll(ViewType.values()); + viewChoice.getSelectionModel().selectedItemProperty() + .addListener((property, oldValue, newValue) -> show(newValue)); + viewChoice.getSelectionModel().select(FLAT); } // View Switch @@ -121,9 +95,25 @@ private void show(ViewType viewType) child.setVisible(viewType.ordinal() == i); } - if (viewType == FLAME) + switch (viewType) { - flameController.refreshFlameView(); + case FLAT: + treeController.deactivate(); + flameController.deactivate(); + flatController.activate(); + break; + case TREE: + flatController.deactivate(); + flameController.deactivate(); + treeController.activate(); + break; + case FLAME: + flameController.activate(); + flameController.refreshFlameView(); + flatController.deactivate(); + treeController.deactivate(); + break; + default: } } } diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileViewController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileViewController.java new file mode 100644 index 000000000..436b5299d --- /dev/null +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/ProfileViewController.java @@ -0,0 +1,54 @@ +package com.insightfullogic.honest_profiler.ports.javafx.controller; + +import java.util.function.Function; + +import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; + +public abstract class ProfileViewController extends AbstractController +{ + + private ProfileContext profileContext; + + private ObjectProperty target; + private Function> targetExtractor; + + protected void initialize(Function> targetExtractor) + { + this.targetExtractor = targetExtractor; + target = new SimpleObjectProperty<>(); + target.addListener((property, oldValue, newValue) -> refresh(newValue)); + } + + protected ProfileContext prContext() + { + return profileContext; + } + + public void setProfileContext(ProfileContext profileContext) + { + this.profileContext = profileContext; + } + + protected T getTarget() + { + return target.get(); + } + + // Activation + + public void activate() + { + target.bind(targetExtractor.apply(profileContext)); + } + + public void deactivate() + { + target.unbind(); + } + + protected abstract void refresh(T target); +} diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/RootController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/RootController.java index 595354b27..b1741d8fc 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/RootController.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/RootController.java @@ -2,6 +2,8 @@ import static com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext.ProfileMode.LOG; import static com.insightfullogic.honest_profiler.ports.javafx.util.DialogUtil.selectLogFile; +import static com.insightfullogic.honest_profiler.ports.javafx.util.DialogUtil.showErrorDialog; +import static com.insightfullogic.honest_profiler.ports.javafx.util.DialogUtil.showExceptionDialog; import static com.insightfullogic.honest_profiler.ports.javafx.util.FxUtil.FXML_FLAT_DIFF_VIEW; import static com.insightfullogic.honest_profiler.ports.javafx.util.FxUtil.FXML_PROFILE_ROOT; import static com.insightfullogic.honest_profiler.ports.javafx.util.FxUtil.addProfileNr; @@ -23,6 +25,8 @@ import com.insightfullogic.honest_profiler.ports.javafx.UserInterfaceConfigurationException; import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext; import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; +import com.insightfullogic.honest_profiler.ports.javafx.model.task.InitializeProfileTask; +import com.insightfullogic.honest_profiler.ports.javafx.util.FxUtil; import com.insightfullogic.honest_profiler.ports.sources.LocalMachineSource; import javafx.concurrent.Task; @@ -32,7 +36,6 @@ import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; -import javafx.scene.control.ProgressIndicator; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.layout.Pane; @@ -69,8 +72,8 @@ public void initialize() menuBar, "The File menu allows you to open existing log files. The Monitor menu allows you to select a running JVM and profile it. JVMs not running the Honest Profiler agent are greyed out."); - openLogItem.setOnAction(event -> doWithFile(file -> generateProfileTab(file, false))); - openLiveItem.setOnAction(event -> doWithFile(file -> generateProfileTab(file, true))); + openLogItem.setOnAction(event -> doWithFile(file -> createNewProfile(file, false))); + openLiveItem.setOnAction(event -> doWithFile(file -> createNewProfile(file, true))); quitItem.setOnAction(event -> exit()); @@ -99,21 +102,21 @@ private void addToMachineMenu(final VirtualMachine vm) String vmName = vm.getDisplayName(); final String vmId = (vmName.contains(" ") ? vmName.substring(0, vmName.indexOf(" ")) : vmName) + " (" + vm.getId() + ")"; - MenuItem machineItem = new MenuItem(vmId); + MenuItem vmItem = new MenuItem(vmId); - machineItem.setId(vm.getId()); - machineItem.setDisable(!vm.isAgentLoaded()); - machineItem.setOnAction(event -> generateProfileTab(vm, true)); + vmItem.setId(vm.getId()); + vmItem.setDisable(!vm.isAgentLoaded()); + vmItem.setOnAction(event -> createNewProfile(vm, true)); if (monitorMenu != null) { - monitorMenu.getItems().add(machineItem); + monitorMenu.getItems().add(vmItem); } } - private void removeFromMachineMenu(final VirtualMachine machine) + private void removeFromMachineMenu(final VirtualMachine vm) { - monitorMenu.getItems().removeIf(node -> machine.getId().equals(node.getId())); + monitorMenu.getItems().removeIf(node -> vm.getId().equals(node.getId())); } public void close() @@ -123,27 +126,62 @@ public void close() // Profile-related Helper Methods - private void generateProfileTab(Object source, boolean live) + private void createNewProfile(Object source, boolean live) { Tab tab = newLoadingTab(); ProfileRootController controller = loadViewIntoTab(FXML_PROFILE_ROOT, tab); tab.getContent().setVisible(false); - ProfileContext profileContext = controller.getProfileContext(); - Task task = controller.getProfileInitializationTask(source, live); + Task task = new InitializeProfileTask(appCtx(), source, live); task.setOnSucceeded(event -> { - initializeProfileTabTitle(tab, profileContext); - tab.getContent().setVisible(true); + try + { + handleNewProfile(tab, controller, task.get()); + } + catch (Throwable t) + { + showExceptionDialog( + "Profile Error", + "Profile not opened", + "An exception occurred trying to open the profile.", + t); + } }); - task.setOnFailed(event -> profileTabs.getTabs().remove(tab)); - task.setOnCancelled(event -> profileTabs.getTabs().remove(tab)); + task.setOnFailed( + event -> + { + profileTabs.getTabs().remove(tab); + showErrorDialog( + "Profile Error", + "Profile not opened", + "The profile could not be opened."); + }); + task.setOnCancelled( + event -> + { + profileTabs.getTabs().remove(tab); + showErrorDialog( + "Profile Error", + "Profile not opened", + "The task for opening the profile was canceled."); + }); appCtx().getExecutorService().submit(task); } + private void handleNewProfile(Tab tab, ProfileRootController controller, + ProfileContext profileContext) + { + controller.setApplicationContext(appCtx()); + controller.setProfileContext(profileContext); + initializeProfileTabTitle(tab, profileContext); + + tab.getContent().setVisible(true); + } + public void generateDiffTab(String baseName, String newName) { Tab tab = newLoadingTab(); @@ -185,7 +223,6 @@ private T loadViewIntoTab(String fxml, Tab tab) FXMLLoader loader = loaderFor(this, fxml); tab.setContent(loader.load()); T controller = loader.getController(); - controller.setApplicationContext(appCtx()); profileTabs.getTabs().add(tab); profileTabs.getSelectionModel().select(tab); return controller; @@ -201,9 +238,7 @@ private T loadViewIntoTab(String fxml, Tab tab) private Tab newLoadingTab() { Tab tab = new Tab("Loading..."); - ProgressIndicator progress = new ProgressIndicator(); - progress.setMaxSize(15, 15); - tab.setGraphic(progress); + tab.setGraphic(FxUtil.getProgressIndicator(15, 15)); return tab; } diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/TreeViewController.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/TreeViewController.java index b7cfbb57f..89739491f 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/TreeViewController.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/controller/TreeViewController.java @@ -29,12 +29,13 @@ import static com.insightfullogic.honest_profiler.ports.javafx.view.Rendering.renderMethod; import static com.insightfullogic.honest_profiler.ports.javafx.view.Rendering.renderPercentage; +import java.util.function.Function; + import com.insightfullogic.honest_profiler.core.filters.ProfileFilter; import com.insightfullogic.honest_profiler.core.profiles.Profile; import com.insightfullogic.honest_profiler.core.profiles.ProfileNode; import com.insightfullogic.honest_profiler.ports.javafx.controller.filter.FilterDialogController; import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext; -import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; import com.insightfullogic.honest_profiler.ports.javafx.model.filter.FilterSpecification; import com.insightfullogic.honest_profiler.ports.javafx.util.DialogUtil; import com.insightfullogic.honest_profiler.ports.javafx.util.TreeUtil; @@ -47,14 +48,16 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Tooltip; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableColumn; +import javafx.scene.control.TreeTableColumn.CellDataFeatures; import javafx.scene.control.TreeTableView; -public class TreeViewController extends AbstractController +public class TreeViewController extends ProfileViewController { @FXML private Button filterButton; @@ -77,7 +80,6 @@ public class TreeViewController extends AbstractController private FilterDialogController filterDialogController; private ObjectProperty filterSpec; - private ProfileContext profileContext; private ProfileFilter currentFilter; private RootNodeAdapter rootNode; @@ -85,6 +87,8 @@ public class TreeViewController extends AbstractController @FXML private void initialize() { + super.initialize(profileContext -> profileContext.profileProperty()); + info(filterButton, "Specify filters restricting the visible entries"); info(expandAllButton, "Expand all trees"); info(collapseAllButton, "Collapse all trees"); @@ -101,7 +105,7 @@ private void initialize() newValue == null || !newValue.isFiltering() ? viewFor(FUNNEL_16) : viewFor(FUNNEL_ACTIVE_16)); currentFilter = new ProfileFilter(newValue.getFilters()); - refresh(profileContext.getProfile()); + refresh(getTarget()); }); rootNode = new RootNodeAdapter(filterSpec); @@ -112,8 +116,8 @@ private void initialize() filterButton.setGraphic(viewFor(FUNNEL_16)); filterButton.setTooltip(new Tooltip("Specify filters")); - filterButton.setOnAction( - event -> filterSpec.set(filterDialogController.showAndWait().get())); + filterButton + .setOnAction(event -> filterSpec.set(filterDialogController.showAndWait().get())); expandAllButton.setGraphic(viewFor(EXPAND_16)); expandAllButton.setTooltip(new Tooltip("Expand all threads")); @@ -123,59 +127,21 @@ private void initialize() collapseAllButton.setGraphic(viewFor(COLLAPSE_16)); collapseAllButton.setTooltip(new Tooltip("Collapse all threads")); collapseAllButton.setOnAction( - event -> treeView.getRoot().getChildren().stream() - .forEach(TreeUtil::collapseFully)); + event -> treeView.getRoot().getChildren().stream().forEach(TreeUtil::collapseFully)); treeView.setRoot(rootNode); - totalColumn.setCellValueFactory( - param -> new ReadOnlyStringWrapper( - param.getValue().getValue() != null - ? renderPercentage(param.getValue().getValue().getTotalTimeShare()) : "")); - selfColumn.setCellValueFactory( - param -> new ReadOnlyStringWrapper( - param.getValue().getValue() != null - ? renderPercentage(param.getValue().getValue().getSelfTimeShare()) : "")); + totalColumn.setCellValueFactory(data -> wrapDouble(data, ProfileNode::getTotalTimeShare)); + selfColumn.setCellValueFactory(data -> wrapDouble(data, ProfileNode::getSelfTimeShare)); methodColumn.setCellFactory(column -> new ProfileNodeTreeTableCell()); - methodColumn.setCellValueFactory(param -> - { - TreeItem treeItem = param.getValue(); - String text = ""; - - if (treeItem instanceof ThreadNodeAdapter) - { - ThreadNodeAdapter adapter = (ThreadNodeAdapter) treeItem; - String name = adapter.getThreadName(); - - StringBuilder builder = new StringBuilder("Thread").append(' '); - if (adapter.getThreadId() < 0) - { - builder.append("Unknown [" + adapter.getThreadId() + "]"); - } - else - { - builder.append(adapter.getThreadId()); - } - - builder.append(' ') - .append((name == null || name.isEmpty()) ? "Unknown" : "[" + name + "]") - .append(" (depth = ").append(adapter.getDepth()).append(", # samples = ") - .append(adapter.getNrOfSamples()).append(")"); - - text = builder.toString(); - } - else if (treeItem instanceof MethodNodeAdapter) - { - text = renderMethod(treeItem.getValue().getFrameInfo()); - } - - return new ReadOnlyStringWrapper(text); - }); + methodColumn.setCellValueFactory(data -> buildProfileNodeCell(data.getValue())); percentColumn.setCellFactory(param -> new TreeViewCell()); } + // Instance Accessors + @Override public void setApplicationContext(ApplicationContext applicationContext) { @@ -183,17 +149,53 @@ public void setApplicationContext(ApplicationContext applicationContext) filterDialogController.setApplicationContext(appCtx()); } - public void setProfileContext(ProfileContext profileContext) + // Helper Methods + + private StringProperty buildProfileNodeCell(TreeItem treeItem) { - this.profileContext = profileContext; - profileContext.profileProperty() - .addListener((property, oldValue, newValue) -> refresh(newValue)); + String text = ""; + + if (treeItem instanceof ThreadNodeAdapter) + { + ThreadNodeAdapter adapter = (ThreadNodeAdapter) treeItem; + String name = adapter.getThreadName(); + + StringBuilder builder = new StringBuilder("Thread").append(' '); + builder.append( + (adapter.getThreadId() < 0) ? "Unknown [" + adapter.getThreadId() + "]" + : adapter.getThreadId()); + + builder.append(' ') + .append((name == null || name.isEmpty()) ? "Unknown" : "[" + name + "]") + .append(" (depth = ").append(adapter.getDepth()).append(", # samples = ") + .append(adapter.getNrOfSamples()).append(")"); + + text = builder.toString(); + } + else if (treeItem instanceof MethodNodeAdapter) + { + text = renderMethod(treeItem.getValue().getFrameInfo()); + } + + return new ReadOnlyStringWrapper(text); } - // Helper Methods + private StringProperty wrapDouble(CellDataFeatures data, + Function accessor) + { + return new ReadOnlyStringWrapper( + data.getValue().getValue() != null + ? renderPercentage(accessor.apply(data.getValue().getValue())) : ""); + } - private void refresh(Profile profile) + @Override + protected void refresh(Profile profile) { + if (profile == null) + { + return; + } + Profile newProfile = profile.copy(); currentFilter.accept(newProfile); rootNode.update(newProfile.getTrees()); diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/ProfileContext.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/ProfileContext.java index 7bc3f3363..4e49f8005 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/ProfileContext.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/ProfileContext.java @@ -1,15 +1,10 @@ package com.insightfullogic.honest_profiler.ports.javafx.model; -import static java.util.Arrays.asList; import static javafx.application.Platform.isFxApplicationThread; import static javafx.application.Platform.runLater; -import static org.slf4j.LoggerFactory.getLogger; - -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; +import com.insightfullogic.honest_profiler.core.profiles.FlameGraph; +import com.insightfullogic.honest_profiler.core.profiles.FlameGraphListener; import com.insightfullogic.honest_profiler.core.profiles.Profile; import com.insightfullogic.honest_profiler.core.profiles.ProfileListener; @@ -18,30 +13,26 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -public class ProfileContext implements ProfileListener +public class ProfileContext { public static enum ProfileMode { - LIVE, - LOG + LIVE, LOG } - private final Logger logger; - private int id; - private SimpleStringProperty name; - private ProfileMode mode; - private SimpleObjectProperty profile; - private List listeners; + private final SimpleStringProperty name; + private final ProfileMode mode; + private final SimpleObjectProperty profile; + private final SimpleObjectProperty flameGraph; - public ProfileContext() + public ProfileContext(String name, ProfileMode mode) { - logger = getLogger(ProfileContext.class); - - name = new SimpleStringProperty(); + this.name = new SimpleStringProperty(name); + this.mode = mode; profile = new SimpleObjectProperty<>(); - listeners = new ArrayList<>(); + flameGraph = new SimpleObjectProperty<>(); } public int getId() @@ -59,21 +50,11 @@ public String getName() return name.get(); } - public void setName(String name) - { - this.name.set(name); - } - public ProfileMode getMode() { return mode; } - public void setMode(ProfileMode mode) - { - this.mode = mode; - } - public Profile getProfile() { return profile.get(); @@ -89,51 +70,46 @@ public ObjectProperty profileProperty() return profile; } - public void setProfile(Profile profile) + public ObjectProperty flameGraphProperty() { - this.profile.set(profile); + return flameGraph; } - public void addListener(ProfileListener listener) + public ProfileListener getProfileListener() { - listeners.add(listener); - } - - public void addListeners(ProfileListener... listeners) - { - this.listeners.addAll(asList(listeners)); - } - - @Override - public void accept(Profile profile) - { - // All UI updates must go through here. - onFxThread(() -> + return new ProfileListener() { - this.profile.set(profile); - - try - { - listeners.forEach(listener -> listener.accept(profile)); - } - catch (Throwable t) + @Override + public void accept(Profile t) { - logger.error(t.getMessage(), t); + if (isFxApplicationThread()) + { + profile.set(t); + } + else + { + runLater(() -> profile.set(t)); + } } - }); + }; } - // Controllers can happily update the UI without worrying about threading - // implications - private void onFxThread(final Runnable block) + public FlameGraphListener getFlameGraphListener() { - if (isFxApplicationThread()) + return new FlameGraphListener() { - block.run(); - } - else - { - runLater(block); - } + @Override + public void accept(FlameGraph t) + { + if (isFxApplicationThread()) + { + flameGraph.set(t); + } + else + { + runLater(() -> flameGraph.set(t)); + } + } + }; } } diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/task/InitializeProfileTask.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/task/InitializeProfileTask.java index 623e4ba05..98994c915 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/task/InitializeProfileTask.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/model/task/InitializeProfileTask.java @@ -13,81 +13,67 @@ import com.insightfullogic.honest_profiler.core.parser.LogEventPublisher; import com.insightfullogic.honest_profiler.core.sources.CantReadFromSourceException; import com.insightfullogic.honest_profiler.core.sources.VirtualMachine; -import com.insightfullogic.honest_profiler.ports.javafx.controller.FlameViewController; import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext; import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; import com.insightfullogic.honest_profiler.ports.sources.FileLogSource; import javafx.concurrent.Task; -public class InitializeProfileTask extends Task +public class InitializeProfileTask extends Task { private final ApplicationContext applicationContext; - private final ProfileContext profileContext; private final Object source; private final boolean live; - private final FlameViewController flameController; - public InitializeProfileTask(ApplicationContext applicationContext, - ProfileContext profileContext, - Object source, - boolean live, - FlameViewController flameController) + public InitializeProfileTask(ApplicationContext applicationContext, Object source, boolean live) { super(); this.applicationContext = applicationContext; - this.profileContext = profileContext; this.source = source; this.live = live; - this.flameController = flameController; } @Override - protected Void call() throws Exception + protected ProfileContext call() throws Exception { + ProfileContext profileContext; + if (source instanceof VirtualMachine) { VirtualMachine vm = (VirtualMachine) source; - String vmName = vm.getDisplayName(); - profileContext.setName( - (vmName.contains(" ") ? vmName.substring(0, vmName.indexOf(" ")) : vmName) - + " (" - + vm.getId() - + ")"); - profileContext.setMode(LIVE); - monitor((VirtualMachine) source); + profileContext = new ProfileContext(convertVmName(vm), LIVE); + monitor(profileContext, (VirtualMachine) source); } else if (live) { File file = (File) source; - profileContext.setName(file.getName()); - profileContext.setMode(LIVE); - monitor(file); + profileContext = new ProfileContext(file.getName(), LIVE); + monitor(profileContext, file); } else { File file = (File) source; - profileContext.setName(file.getName()); - profileContext.setMode(LOG); - openFile((File) source); + profileContext = new ProfileContext(file.getName(), LOG); + openFile(profileContext, (File) source); } + applicationContext.registerProfileContext(profileContext); - return null; + return profileContext; } - private void openFile(File file) + private void openFile(ProfileContext profileContext, File file) { final LogEventListener collector = new LogEventPublisher() - .publishTo(new LogCollector(profileContext, false)) - .publishTo(new FlameGraphCollector(flameController)); + .publishTo(new LogCollector(profileContext.getProfileListener(), false)) + .publishTo(new FlameGraphCollector(profileContext.getFlameGraphListener())); pipe(new FileLogSource(file), collector, false).run(); } - private void monitor(File file) + private void monitor(ProfileContext profileContext, File file) { try { - pipeFile(new FileLogSource(file), profileContext); + pipeFile(new FileLogSource(file), profileContext.getProfileListener()); } catch (CantReadFromSourceException crfse) { @@ -95,15 +81,25 @@ private void monitor(File file) } } - private void monitor(VirtualMachine machine) + private void monitor(ProfileContext profileContext, VirtualMachine machine) { try { - pipeFile(machine.getLogSourceFromVmArgs(), profileContext); + pipeFile(machine.getLogSourceFromVmArgs(), profileContext.getProfileListener()); } catch (CantReadFromSourceException crfse) { throw new RuntimeException(crfse.getMessage(), crfse); } } + + private String convertVmName(VirtualMachine vm) + { + String name = vm.getDisplayName(); + + StringBuilder result = new StringBuilder(); + result.append((name.contains(" ")) ? name.substring(0, name.indexOf(" ")) : name); + result.append(" (").append(vm.getId()).append(")"); + return result.toString(); + } } diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/DialogUtil.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/DialogUtil.java index adc0bf473..e5d8a463d 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/DialogUtil.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/DialogUtil.java @@ -100,7 +100,6 @@ public static void showExportDialog(Window window, String initialFileName, catch (IOException ioe) { showExceptionDialog( - window, "I/O Issue", "File could not be written", "An issue occurred trying to export data to file " @@ -111,8 +110,18 @@ public static void showExportDialog(Window window, String initialFileName, } } - public static void showExceptionDialog(Window window, String title, String headerText, - String contentText, + public static void showErrorDialog(String title, String header, String content) + { + + Alert alert = new Alert(ERROR); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + + alert.showAndWait(); + } + + public static void showExceptionDialog(String title, String headerText, String contentText, Throwable t) { Alert alert = new Alert(ERROR); diff --git a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/FxUtil.java b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/FxUtil.java index d71b1407f..9a6bd7e64 100644 --- a/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/FxUtil.java +++ b/src/main/java/com/insightfullogic/honest_profiler/ports/javafx/util/FxUtil.java @@ -19,6 +19,7 @@ import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; import javafx.scene.Node; +import javafx.scene.control.ProgressIndicator; import javafx.scene.control.TableView; import javafx.scene.image.WritableImage; import javafx.scene.layout.HBox; @@ -37,10 +38,8 @@ public final class FxUtil public static String FXML_FLAT_DIFF_VIEW = FXML_DIR + "FlatDiffView.fxml"; private static Color[] LABEL_PALETTE = new Color[] - { - LIGHTSTEELBLUE, LIGHTGREEN, ORANGE, LIGHTBLUE, BEIGE, GOLD, LIGHTGREY, LIGHTPINK, CYAN, - CHARTREUSE - }; + { LIGHTSTEELBLUE, LIGHTGREEN, ORANGE, LIGHTBLUE, BEIGE, GOLD, LIGHTGREY, LIGHTPINK, CYAN, + CHARTREUSE }; public static FXMLLoader loaderFor(Object originator, String resource) { @@ -117,6 +116,13 @@ public static void refreshTable(TableView table) }); } + public static ProgressIndicator getProgressIndicator(double maxWidth, double maxHeight) + { + ProgressIndicator progress = new ProgressIndicator(); + progress.setMaxSize(maxWidth, maxHeight); + return progress; + } + private FxUtil() { // Private constructor for Utility Class