Mercurial > hg > heapstats
view analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/fx/plugin/builtin/snapshot/tabs/HistogramController.java @ 272:dd85c1cbc8c8
Bug 3752: Migrate to OpenJFX 13
Reviewed-by: ykubota
https://github.com/HeapStats/heapstats/pull/144
author | Yasumasa Suenaga <yasuenag@gmail.com> |
---|---|
date | Fri, 27 Sep 2019 14:47:03 +0900 |
parents | |
children |
line wrap: on
line source
/* * Copyright (C) 2015-2019 Nippon Telegraph and Telephone Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package jp.co.ntt.oss.heapstats.fx.plugin.builtin.snapshot.tabs; import java.io.File; import java.net.URL; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.collections.ObservableSet; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.StackedAreaChart; import javafx.scene.chart.XYChart; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.ListView; import javafx.scene.control.SelectionMode; import javafx.scene.control.SelectionModel; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.stage.FileChooser; import javax.xml.bind.JAXB; import jp.co.ntt.oss.heapstats.container.snapshot.DiffData; import jp.co.ntt.oss.heapstats.container.snapshot.ObjectData; import jp.co.ntt.oss.heapstats.container.snapshot.SnapShotHeader; import jp.co.ntt.oss.heapstats.fx.plugin.builtin.snapshot.BindingFilter; import jp.co.ntt.oss.heapstats.fx.plugin.builtin.snapshot.ChartColorManager; import jp.co.ntt.oss.heapstats.task.DiffCalculator; import jp.co.ntt.oss.heapstats.fx.utils.EpochTimeConverter; import jp.co.ntt.oss.heapstats.api.utils.HeapStatsUtils; import jp.co.ntt.oss.heapstats.fx.utils.TaskAdapter; import jp.co.ntt.oss.heapstats.xml.binding.Filter; import jp.co.ntt.oss.heapstats.xml.binding.Filters; /** * FXML Controller class for "Histogram" tab in SnapShot plugin. */ public class HistogramController implements Initializable { @FXML private TableView<BindingFilter> excludeTable; @FXML private TableColumn<BindingFilter, Boolean> hideColumn; @FXML private TableColumn<BindingFilter, String> excludeNameColumn; @FXML private TextField searchText; @FXML private ListView<String> searchList; @FXML private Button selectFilterApplyBtn; @FXML private StackedAreaChart<Number, Long> topNChart; @FXML private AnchorPane topNChartAnchor; @FXML private NumberAxis topNYAxis; @FXML private TableView<DiffData> lastDiffTable; @FXML private TableColumn<DiffData, String> colorColumn; @FXML private TableColumn<DiffData, String> classNameColumn; @FXML private TableColumn<DiffData, String> classLoaderColumn; @FXML private TableColumn<DiffData, Long> instanceColumn; @FXML private TableColumn<DiffData, Long> totalSizeColumn; private ObjectProperty<ObservableList<SnapShotHeader>> currentTarget; private ObjectProperty<ObservableSet<String>> currentClassNameSet; private BooleanProperty instanceGraph; private ObjectProperty<SelectionModel<SnapShotHeader>> snapshotSelectionModel; private boolean searchFilterEnable; private boolean excludeFilterEnable; private ObjectProperty<ObservableMap<LocalDateTime, List<ObjectData>>> topNList; private LongProperty currentObjectTag; private Consumer<XYChart<Number, ? extends Number>> drawRebootSuspectLine; private Consumer<Task<Void>> taskExecutor; private List<String> hideRegexList; private EpochTimeConverter epochTimeConverter; /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { instanceGraph = new SimpleBooleanProperty(); currentTarget = new SimpleObjectProperty<>(FXCollections.emptyObservableList()); currentClassNameSet = new SimpleObjectProperty<>(FXCollections.emptyObservableSet()); snapshotSelectionModel = new SimpleObjectProperty<>(); topNList = new SimpleObjectProperty<>(); currentObjectTag = new SimpleLongProperty(); searchFilterEnable = false; excludeFilterEnable = false; hideColumn.setCellValueFactory(new PropertyValueFactory<>("hide")); hideColumn.setCellFactory(CheckBoxTableCell.forTableColumn(hideColumn)); excludeNameColumn.setCellFactory(p -> new TableCell<BindingFilter, String>(){ @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); BindingFilter filter = (BindingFilter)getTableRow().getItem(); if(!empty && (filter != null)){ styleProperty().bind(Bindings.createStringBinding(() -> filter.appliedProperty().get() ? "-fx-background-color: skyblue;" : "-fx-background-color: white;", filter.appliedProperty())); setText(filter.getName()); } } }); colorColumn.setCellFactory(p -> new TableCell<DiffData, String>() { @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); String style = Optional.ofNullable((DiffData) getTableRow().getItem()) .filter(d -> d.isRanked()) .map(d -> "-fx-background-color: " + ChartColorManager.getNextColor(d.getClassName())) .orElse("-fx-background-color: transparent;"); setStyle(style); } }); classNameColumn.setCellValueFactory(new PropertyValueFactory<>("className")); classLoaderColumn.setCellValueFactory(new PropertyValueFactory<>("classLoaderName")); instanceColumn.setCellValueFactory(new PropertyValueFactory<>("instances")); instanceColumn.setSortType(TableColumn.SortType.DESCENDING); totalSizeColumn.setCellValueFactory(new PropertyValueFactory<>("totalSize")); totalSizeColumn.setSortType(TableColumn.SortType.DESCENDING); searchList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); topNChart.lookup(".chart").setStyle("-fx-background-color: " + HeapStatsUtils.getChartBgColor() + ";"); topNChart.getXAxis().setTickMarkVisible(HeapStatsUtils.getTickMarkerSwitch()); searchFilterEnable = false; excludeFilterEnable = false; selectFilterApplyBtn.disableProperty().bind(searchList.selectionModelProperty().getValue().selectedItemProperty().isNull()); currentObjectTag.bind(Bindings.createLongBinding(() -> Optional.ofNullable(lastDiffTable.getSelectionModel().getSelectedItem()) .map(d -> d.getTag()) .orElse(0xffffffffffffffffl), lastDiffTable.getSelectionModel().selectedItemProperty())); epochTimeConverter = new EpochTimeConverter(); } /** * Build TopN Data for Chart with givien data. * * @param header SnapShot header which you want to build. * @param seriesMap Chart series map which is contains class name as key, * chart series as value. * @param objData ObjectData which is you want to build. */ private void buildTopNChartData(SnapShotHeader header, Map<String, XYChart.Series<Number, Long>> seriesMap, ObjectData objData) { XYChart.Series<Number, Long> series = seriesMap.get(objData.getName()); if (series == null) { series = new XYChart.Series<>(); series.setName(objData.getName()); topNChart.getData().add(series); seriesMap.put(objData.getName(), series); } long time = header.getSnapShotDate().atZone(ZoneId.systemDefault()).toEpochSecond(); long yValue = instanceGraph.get() ? objData.getCount() : objData.getTotalSize() / 1024 / 1024; XYChart.Data<Number, Long> data = new XYChart.Data<>(time, yValue); series.getData().add(data); String unit = instanceGraph.get() ? "instances" : "MB"; String tip = String.format("%s: %s, %d " + unit, series.getName(), epochTimeConverter.toString(time), yValue); Tooltip.install(data.getNode(), new Tooltip(tip)); } private void setTopNChartColor(XYChart.Series<Number, Long> series) { String color = ChartColorManager.getNextColor(series.getName()); series.getNode().lookup(".chart-series-area-line").setStyle(String.format("-fx-stroke: %s;", color)); series.getNode().lookup(".chart-series-area-fill").setStyle(String.format("-fx-fill: %s;", color)); series.getData().stream() .map(d -> d.getNode().lookup(".chart-area-symbol")) .forEach(n -> n.setStyle(String.format("-fx-background-color: %s, white;", color))); } /** * onSucceeded event handler for DiffTask. * * @param diff Target task. * @param seriesMap Chart series map which is contains class name as key, * chart series as value. */ private void onDiffTaskSucceeded(DiffCalculator diff, Map<String, XYChart.Series<Number, Long>> seriesMap) { /* Set chart range */ long startEpoch = currentTarget.get().get(0).getSnapShotDate().atZone(ZoneId.systemDefault()).toEpochSecond(); long endEpoch = currentTarget.get().get(currentTarget.get().size() - 1).getSnapShotDate().atZone(ZoneId.systemDefault()).toEpochSecond(); NumberAxis xAxis = (NumberAxis)topNChart.getXAxis(); xAxis.setTickUnit((endEpoch - startEpoch) / HeapStatsUtils.getXTickUnit()); xAxis.setLowerBound(startEpoch); xAxis.setUpperBound(endEpoch); topNList.set(FXCollections.observableMap(diff.getTopNList())); currentTarget.get().stream() .forEachOrdered(h -> topNList.get().get(h.getSnapShotDate()).stream() .forEachOrdered(o -> buildTopNChartData(h, seriesMap, o))); lastDiffTable.getItems().addAll(diff.getLastDiffList()); lastDiffTable.getSortOrder().add(instanceGraph.get() ? instanceColumn : totalSizeColumn); topNChart.getData().forEach(this::setTopNChartColor); Platform.runLater(() -> drawRebootSuspectLine.accept(topNChart)); long maxVal = topNChart.getData().stream() .flatMap(s -> s.dataProperty().get().stream()) .collect(Collectors.groupingBy(d -> d.getXValue(), Collectors.summingLong(d -> d.getYValue()))) .values() .stream() .mapToLong(Long::longValue) .max() .getAsLong(); topNYAxis.setUpperBound(maxVal * 1.05d); topNYAxis.setTickUnit(maxVal / 20); topNYAxis.setLabel(instanceGraph.get() ? "instances" : "MB"); snapshotSelectionModel.get().selectLast(); if(excludeFilterEnable){ excludeTable.getItems().stream() .forEach(f -> f.appliedProperty().set(f.isHide())); } else{ excludeTable.getItems().stream() .forEach(f -> f.appliedProperty().set(false)); } } private void putIfAbsentToExcludeFilter(Filter filter){ Optional<BindingFilter> exists = excludeTable.getItems().stream() .filter(f -> f.getName().equals(filter.getName())) .findAny(); if(exists.isPresent()){ Alert dialog = new Alert(Alert.AlertType.ERROR, filter.getName() + " already exists!", ButtonType.OK); dialog.showAndWait(); } else{ excludeTable.getItems().add(new BindingFilter(filter)); } } /** * Event handler for adding exclude filter. * * @param event ActionEvent of this event. */ @FXML private void onAddClick(ActionEvent event) { FileChooser dialog = new FileChooser(); ResourceBundle resource = ResourceBundle.getBundle("snapshotResources", new Locale(HeapStatsUtils.getLanguage())); dialog.setTitle(resource.getString("dialog.filterchooser.title")); dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory())); dialog.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Filter file (*.xml)", "*.xml"), new FileChooser.ExtensionFilter("All files", "*.*")); List<File> excludeFilterList = dialog.showOpenMultipleDialog(((Node) event.getSource()).getScene().getWindow()); if (excludeFilterList != null) { excludeFilterList.stream() .map(f -> (Filters) JAXB.unmarshal(f, Filters.class)) .filter(f -> f != null) .flatMap(f -> f.getFilter().stream()) .forEach(this::putIfAbsentToExcludeFilter); } } /** * Drawing and Showing table with beging selected value. * * @return Class name filter. null if any filter is disabled. */ public Predicate<? super ObjectData> getFilter() { HashSet<String> targetSet = new HashSet<>(searchList.getSelectionModel().getSelectedItems()); Predicate<ObjectData> searchFilter = o -> targetSet.contains(o.getName()); Predicate<ObjectData> hideFilter = o -> hideRegexList.stream().noneMatch(s -> o.getName().matches(s)); Predicate<ObjectData> filter; if (searchFilterEnable && excludeFilterEnable) { filter = searchFilter.and(hideFilter); } else if (searchFilterEnable) { filter = searchFilter; } else if (excludeFilterEnable) { filter = hideFilter; } else { filter = null; } return filter; } /** * Event handler of apply button in exclude filter. * * @param event ActionEvent of this event. */ @FXML private void onHiddenFilterApply(ActionEvent event) { excludeFilterEnable = true; hideRegexList = excludeTable.getItems().stream() .filter(f -> f.isHide()) .flatMap(f -> f.getClasses().getName().stream()) .map(s -> ".*" + s + ".*") .collect(Collectors.toList()); Predicate<? super ObjectData> filter = getFilter(); taskExecutor.accept(getDrawTopNDataTask(currentTarget.get(), false, filter)); } /** * Event handler of changing search TextField. * * @param event KeyEvent of this event. */ @FXML private void onSearchTextChanged(KeyEvent event) { Stream<String> searchStrings = currentClassNameSet.get().stream(); if(excludeFilterEnable){ searchStrings = searchStrings.filter(n -> hideRegexList.stream().noneMatch(s -> n.matches(s))); } searchList.getItems().clear(); searchList.getItems().addAll(searchStrings.filter(n -> n.contains(searchText.getText())) .collect(Collectors.toList())); } /** * Selection method for incremental search. * * @param event ActionEvent of this event. */ @FXML private void onSelectFilterApply(ActionEvent event) { searchFilterEnable = true; Predicate<? super ObjectData> filter = getFilter(); taskExecutor.accept(getDrawTopNDataTask(currentTarget.get(), false, filter)); } /** * Event handler of clear button. * * @param event ActionEvent of this event. */ @FXML private void onSelectFilterClear(ActionEvent event) { searchFilterEnable = false; excludeFilterEnable = false; searchText.setText(""); searchList.getItems().clear(); taskExecutor.accept(getDrawTopNDataTask(currentTarget.get(), true, null)); } /** * Get task for drawing Top N data to Chart and Table. * * @param target SnapShot to be drawed. * @param includeOthers *Others* data should be included in this Top N data. * @param predicate Filter function. * @return Task for drawing Top N data. */ public Task<Void> getDrawTopNDataTask(List<SnapShotHeader> target, boolean includeOthers, Predicate<? super ObjectData> predicate) { topNChart.getData().clear(); lastDiffTable.getItems().clear(); Map<String, XYChart.Series<Number, Long>> seriesMap = new HashMap<>(); TaskAdapter<DiffCalculator> diff = new TaskAdapter<>(new DiffCalculator(target, HeapStatsUtils.getRankLevel(), includeOthers, predicate, HeapStatsUtils.getReplaceClassName(), instanceGraph.get())); diff.setOnSucceeded(evt -> onDiffTaskSucceeded(diff.getTask(), seriesMap)); return diff; } /** * Set Consumer for drawing reboot line. * * @param drawRebootSuspectLine Consumer for drawing reboot line. */ public void setDrawRebootSuspectLine(Consumer<XYChart<Number, ? extends Number>> drawRebootSuspectLine) { this.drawRebootSuspectLine = drawRebootSuspectLine; } /** * Set Consumer for executing JavaFX task. This Consumer is used for tasks * which should be shown ProgressIndicator. * * @param taskExecutor Consumer for executing JavaFX task. */ public void setTaskExecutor(Consumer<Task<Void>> taskExecutor) { this.taskExecutor = taskExecutor; } /** * Get property which contains chart category (instance count or object * size). * * @return Property which contains chart category. */ public BooleanProperty instanceGraphProperty() { return instanceGraph; } /** * Get property of list of SnapShotHeader. * * @return Property of list of SnapShotHeader. */ public ObjectProperty<ObservableList<SnapShotHeader>> currentTargetProperty() { return currentTarget; } /** * Get property of class name set. * * @return Property of class name set. */ public ObjectProperty<ObservableSet<String>> currentClassNameSetProperty() { return currentClassNameSet; } /** * Get property for current SnapShot selection. * * @return Property for current SnapShot selection. */ public ObjectProperty<SelectionModel<SnapShotHeader>> snapshotSelectionModelProperty() { return snapshotSelectionModel; } /** * Get TopN chart. * * @return TopN chart. */ public StackedAreaChart<Number, Long> getTopNChart() { return topNChart; } /** * Get property of Map of Top N list. * * @return Property of map of Top N list. */ public ObjectProperty<ObservableMap<LocalDateTime, List<ObjectData>>> topNListProperty() { return topNList; } /** * Get property of current Object tag. * * @return Property of currentObjectTag. */ public LongProperty currentObjectTagProperty() { return currentObjectTag; } /** * Get selected diff data. * * @return Selected diff data. */ public DiffData getSelectedData() { return lastDiffTable.getSelectionModel().getSelectedItem(); } /** * Clear all items in Histogram tab. */ public void clearAllItems(){ excludeTable.getItems().stream() .forEach((f -> f.setHide(false))); excludeFilterEnable = false; searchText.setText(""); searchList.getItems().clear(); searchFilterEnable = false; topNChart.getData().clear(); topNChartAnchor.getChildren().clear(); lastDiffTable.getItems().clear(); } }