view analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/fx/plugin/builtin/snapshot/tabs/SummaryController.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.net.URL;
import java.time.ZoneId;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedAreaChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import jp.co.ntt.oss.heapstats.container.snapshot.SnapShotHeader;
import jp.co.ntt.oss.heapstats.container.snapshot.SummaryData;
import jp.co.ntt.oss.heapstats.fx.utils.EpochTimeConverter;
import jp.co.ntt.oss.heapstats.api.utils.HeapStatsUtils;

/**
 * FXML Controller class for "Summary Data" tab in SnapShot plugin.
 */
public class SummaryController implements Initializable {

    @FXML
    private TableView<SummaryData.SummaryDataEntry> summaryTable;

    @FXML
    private TableColumn<SummaryData.SummaryDataEntry, String> keyColumn;

    @FXML
    private TableColumn<SummaryData.SummaryDataEntry, String> valueColumn;

    @FXML
    private StackedAreaChart<Number, Long> heapChart;

    private XYChart.Series<Number, Long> youngUsage;

    private XYChart.Series<Number, Long> oldUsage;

    private XYChart.Series<Number, Long> free;

    @FXML
    private LineChart<Number, Long> instanceChart;

    private XYChart.Series<Number, Long> instances;

    @FXML
    private LineChart<Number, Long> gcTimeChart;

    private XYChart.Series<Number, Long> gcTime;

    @FXML
    private AreaChart<Number, Long> metaspaceChart;

    private XYChart.Series<Number, Long> metaspaceUsage;

    private XYChart.Series<Number, Long> metaspaceCapacity;

    private ObjectProperty<SummaryData> summaryData;

    private ObjectProperty<ObservableList<SnapShotHeader>> currentTarget;

    private ObjectProperty<ObservableSet<String>> currentClassNameSet;

    private EpochTimeConverter epochTimeConverter;

    private Tooltip heapTooltip;

    private GridPane heapTooltipGrid;

    private Label youngLabel;

    private Label oldLabel;

    private Label freeLabel;

    private Tooltip metaspaceTooltip;

    private GridPane metaspaceTooltipGrid;

    private Label metaspaceUsageLabel;

    private Label metaspaceCapacityLabel;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        summaryData = new SimpleObjectProperty<>();
        summaryData.addListener((v, o, n) -> setSummaryTable(n));
        currentTarget = new SimpleObjectProperty<>(FXCollections.emptyObservableList());
        currentClassNameSet = new SimpleObjectProperty<>(FXCollections.emptyObservableSet());

        keyColumn.setCellValueFactory(new PropertyValueFactory<>("category"));
        valueColumn.setCellValueFactory(new PropertyValueFactory<>("value"));

        String bgcolor = "-fx-background-color: " + HeapStatsUtils.getChartBgColor() + ";";
        Stream.of(heapChart, instanceChart, gcTimeChart, metaspaceChart)
              .peek(c -> c.lookup(".chart").setStyle(bgcolor))
              .forEach(c -> c.getXAxis().setTickMarkVisible(HeapStatsUtils.getTickMarkerSwitch()));

        epochTimeConverter = new EpochTimeConverter();
        Stream.of(heapChart, instanceChart, gcTimeChart, metaspaceChart)
              .map(c -> (NumberAxis)c.getXAxis())
              .forEach(a -> a.setTickLabelFormatter(epochTimeConverter));

        initializeChartSeries();
    }

    private void setSummaryTable(SummaryData data) {
        if (data == null) {
            summaryTable.getItems().clear();
        } else {
            ResourceBundle resource = ResourceBundle.getBundle("snapshotResources", new Locale(HeapStatsUtils.getLanguage()));
            String safepointTimeStr = data.hasSafepointTime() ? String.format("%d ms (%.02f %%)", data.getSafepointTime(), data.getSafepointPercentage())
                                                              : "N/A";

            summaryTable.setItems(FXCollections.observableArrayList(new SummaryData.SummaryDataEntry(resource.getString("summary.snapshot.count"), Integer.toString(data.getCount())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.gc.count"), String.format("%d (Full: %d, Young: %d)", data.getFullCount() + data.getYngCount(), data.getFullCount(), data.getYngCount())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.heap.usage"), String.format("%.1f MB", data.getLatestHeapUsage() / 1024.0d / 1024.0d)),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.metaspace.usage"), String.format("%.1f MB", data.getLatestMetaspaceUsage() / 1024.0d / 1024.0d)),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.gc.time"), String.format("%d ms", data.getMaxGCTime())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.gc.totaltime"), String.format("%d ms", data.getTotalGCTime())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.safepoint.time"), safepointTimeStr),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.snapshot.size"), String.format("%.1f KB", data.getMaxSnapshotSize() / 1024.0d)),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.snapshot.entrycount"), Long.toString(data.getMaxEntryCount()))
            ));
        }
    }

    /**
     * Initialize Series in Chart. This method uses to avoid RuntimeException
     * which is related to: RT-37994: [FXML] ProxyBuilder does not support
     * read-only collections https://javafx-jira.kenai.com/browse/RT-37994
     */
    @SuppressWarnings("unchecked")
    private void initializeChartSeries() {
        youngUsage = new XYChart.Series<>();
        youngUsage.setName("Young");
        oldUsage = new XYChart.Series<>();
        oldUsage.setName("Old");
        free = new XYChart.Series<>();
        free.setName("Free");

        String cssName = "/jp/co/ntt/oss/heapstats/fx/plugin/builtin/snapshot/tabs/";
        if (HeapStatsUtils.getHeapOrder()) {
            heapChart.getData().addAll(youngUsage, oldUsage, free);
            cssName += "heapsummary-bottom-young.css";
        } else {
            heapChart.getData().addAll(oldUsage, youngUsage, free);
            cssName += "heapsummary-bottom-old.css";
        }
        heapChart.getStylesheets().add(SummaryController.class.getResource(cssName).toString());

        instances = new XYChart.Series<>();
        instances.setName("Instances");
        instanceChart.getData().add(instances);

        gcTime = new XYChart.Series<>();
        gcTime.setName("GC Time");
        gcTimeChart.getData().add(gcTime);

        metaspaceCapacity = new XYChart.Series<>();
        metaspaceCapacity.setName("Capacity");
        metaspaceUsage = new XYChart.Series<>();
        metaspaceUsage.setName("Usage");
        metaspaceChart.getData().addAll(metaspaceCapacity, metaspaceUsage);

        /* Tooltip setup */
        /* Java heap */
        heapTooltipGrid = new GridPane();
        heapTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
        youngLabel = new Label();
        oldLabel = new Label();
        freeLabel = new Label();
        Rectangle youngRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle oldRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle freeRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);

        Platform.runLater(() -> {
            youngRect.setStyle("-fx-fill: " + ((Path)heapChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
            oldRect.setStyle("-fx-fill: " + ((Path)heapChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
            freeRect.setStyle("-fx-fill: " + ((Path)heapChart.lookup(".series2")).getFill().toString().replace("0x", "#"));
        });

        heapTooltipGrid.add(youngRect, 0, 0);
        heapTooltipGrid.add(new Label("Young"), 1, 0);
        heapTooltipGrid.add(youngLabel, 2, 0);
        heapTooltipGrid.add(oldRect, 0, 1);
        heapTooltipGrid.add(new Label("Old"), 1, 1);
        heapTooltipGrid.add(oldLabel, 2, 1);
        heapTooltipGrid.add(freeRect, 0, 2);
        heapTooltipGrid.add(new Label("Free"), 1, 2);
        heapTooltipGrid.add(freeLabel, 2, 2);
        heapTooltip = new Tooltip();
        heapTooltip.setGraphic(heapTooltipGrid);
        heapTooltip.setContentDisplay(ContentDisplay.BOTTOM);

        /* Metaspace */
        metaspaceTooltipGrid = new GridPane();
        metaspaceTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
        metaspaceUsageLabel = new Label();
        metaspaceCapacityLabel = new Label();
        Rectangle metaUsageRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle metaCapacityRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);

        Platform.runLater(() -> {
            metaUsageRect.setStyle("-fx-fill: " + ((Path)metaspaceChart.lookup(".default-color0.chart-series-area-fill")).getFill().toString().replace("0x", "#"));
            metaCapacityRect.setStyle("-fx-fill: " + ((Path)metaspaceChart.lookup(".default-color1.chart-series-area-fill")).getFill().toString().replace("0x", "#"));
        });

        metaspaceTooltipGrid.add(metaUsageRect, 0, 0);
        metaspaceTooltipGrid.add(new Label("Usage"), 1, 0);
        metaspaceTooltipGrid.add(metaspaceUsageLabel, 2, 0);
        metaspaceTooltipGrid.add(metaCapacityRect, 0, 1);
        metaspaceTooltipGrid.add(new Label("Capacity"), 1, 1);
        metaspaceTooltipGrid.add(metaspaceCapacityLabel, 2, 1);
        metaspaceTooltip = new Tooltip();
        metaspaceTooltip.setGraphic(metaspaceTooltipGrid);
        metaspaceTooltip.setContentDisplay(ContentDisplay.BOTTOM);
    }

    /**
     * JavaFX task class for calculating GC summary.
     */
    private class CalculateGCSummaryTask extends Task<Void> {

        private int processedIndex;

        private final Consumer<XYChart<Number, ? extends Number>> drawRebootSuspectLine;

        /* Java Heap Usage Chart */
        private final ObservableList<XYChart.Data<Number, Long>> youngUsageBuf;
        private final ObservableList<XYChart.Data<Number, Long>> oldUsageBuf;
        private final ObservableList<XYChart.Data<Number, Long>> freeBuf;

        /* Instances */
        private final ObservableList<XYChart.Data<Number, Long>> instanceBuf;

        /* GC time Chart */
        private final ObservableList<XYChart.Data<Number, Long>> gcTimeBuf;

        /* Metaspace Chart */
        private final ObservableList<XYChart.Data<Number, Long>> metaspaceUsageBuf;
        private final ObservableList<XYChart.Data<Number, Long>> metaspaceCapacityBuf;

        /**
         * Constructor of CalculateGCSummaryTask.
         *
         * @param drawRebootSuspectLine Consumer for drawing reboot line. This
         * consumer is called in Platform#runLater() at succeeded().
         */
        public CalculateGCSummaryTask(Consumer<XYChart<Number, ? extends Number>> drawRebootSuspectLine) {
            this.drawRebootSuspectLine = drawRebootSuspectLine;

            youngUsageBuf = FXCollections.observableArrayList();
            oldUsageBuf = FXCollections.observableArrayList();
            freeBuf = FXCollections.observableArrayList();
            instanceBuf = FXCollections.observableArrayList();
            gcTimeBuf = FXCollections.observableArrayList();
            metaspaceUsageBuf = FXCollections.observableArrayList();
            metaspaceCapacityBuf = FXCollections.observableArrayList();
        }

        private void processSnapShotHeader(SnapShotHeader header) {
            long time = header.getSnapShotDate().atZone(ZoneId.systemDefault()).toEpochSecond();

            youngUsageBuf.add(new XYChart.Data<>(time, header.getNewHeap() / 1024 / 1024));
            oldUsageBuf.add(new XYChart.Data<>(time, header.getOldHeap() / 1024 / 1024));
            freeBuf.add(new XYChart.Data<>(time, (header.getTotalCapacity() - header.getNewHeap() - header.getOldHeap()) / 1024 / 1024));

            instanceBuf.add(new XYChart.Data<>(time, header.getNumInstances()));

            gcTimeBuf.add(new XYChart.Data<>(time, header.getGcTime()));

            metaspaceUsageBuf.add(new XYChart.Data<>(time, header.getMetaspaceUsage() / 1024 / 1024));
            metaspaceCapacityBuf.add(new XYChart.Data<>(time, header.getMetaspaceCapacity() / 1024 / 1024));

            currentClassNameSet.get().addAll(header.getSnapShot(HeapStatsUtils.getReplaceClassName())
                    .values()
                    .stream()
                    .map(s -> s.getName())
                    .collect(Collectors.toSet()));

            updateProgress(++processedIndex, currentTarget.get().size());
        }

        @Override
        protected Void call() throws Exception {
            updateMessage("Calcurating GC summary...");
            processedIndex = 0;
            currentTarget.get().stream()
                    .forEachOrdered(d -> processSnapShotHeader(d));
            return null;
        }

        private void setupJavaHeapChartTooltip(int idx){
            XYChart.Data<Number, Long> youngNode = youngUsage.getData().get(idx);
            XYChart.Data<Number, Long> oldNode = oldUsage.getData().get(idx);
            XYChart.Data<Number, Long> freeNode = free.getData().get(idx);
            long youngInMB = youngNode.getYValue();
            long oldInMB = oldNode.getYValue();
            long freeInMB = freeNode.getYValue();

            EventHandler<MouseEvent> handler = e -> {
                heapTooltip.setText(epochTimeConverter.toString(youngNode.getXValue()));
                youngLabel.setText(youngInMB + " MB");
                oldLabel.setText(oldInMB + " MB");
                freeLabel.setText(freeInMB + " MB");
            };

            Tooltip.install(youngNode.getNode(), heapTooltip);
            youngNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(oldNode.getNode(), heapTooltip);
            oldNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(freeNode.getNode(), heapTooltip);
            freeNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
        }

        private void setupMetaspaceChartTooltip(int idx){
            XYChart.Data<Number, Long> usageNode = metaspaceUsage.getData().get(idx);
            XYChart.Data<Number, Long> capacityNode = metaspaceCapacity.getData().get(idx);
            long usage = usageNode.getYValue();
            long capacity = capacityNode.getYValue();

            EventHandler<MouseEvent> handler = e -> {
                metaspaceTooltip.setText(epochTimeConverter.toString(usageNode.getXValue()));
                metaspaceUsageLabel.setText(usage + " MB");
                metaspaceCapacityLabel.setText(capacity + " MB");
            };

            Tooltip.install(usageNode.getNode(), metaspaceTooltip);
            usageNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(capacityNode.getNode(), metaspaceTooltip);
            capacityNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
        }

        @Override
        protected void succeeded() {
            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();
            Stream.of(heapChart, instanceChart, gcTimeChart, metaspaceChart)
                  .peek(c -> ((NumberAxis)c.getXAxis()).setTickUnit((endEpoch - startEpoch) / HeapStatsUtils.getXTickUnit()))
                  .peek(c -> ((NumberAxis)c.getXAxis()).setLowerBound(startEpoch))
                  .peek(c -> ((NumberAxis)c.getXAxis()).setUpperBound(endEpoch))
                  .forEach(c -> Platform.runLater(() -> drawRebootSuspectLine.accept(c)));
            
            /* Replace new chart data */
            youngUsage.setData(youngUsageBuf);
            oldUsage.setData(oldUsageBuf);
            free.setData(freeBuf);

            instances.setData(instanceBuf);

            gcTime.setData(gcTimeBuf);

            metaspaceUsage.setData(metaspaceUsageBuf);
            metaspaceCapacity.setData(metaspaceCapacityBuf);

            /* Set tooltip */
            /* Java Heap & Metaspace */
            IntStream.range(0, currentTarget.get().size())
                     .peek(this::setupJavaHeapChartTooltip)
                     .forEach(this::setupMetaspaceChartTooltip);

            Tooltip tooltip = new Tooltip();

            /* Insatances */
            instances.getData()
                     .stream()
                     .peek(d -> Tooltip.install(d.getNode(), tooltip))
                     .forEach(d -> d.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, e -> tooltip.setText((epochTimeConverter.toString(d.getXValue()) + ": " + d.getYValue()))));

            /* GC time */
            gcTime.getData()
                  .stream()
                  .peek(d -> Tooltip.install(d.getNode(), tooltip))
                  .forEach(d -> d.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, e -> tooltip.setText(String.format("%s: %d ms", epochTimeConverter.toString(d.getXValue()), d.getYValue()))));
        }

    }

    /**
     * Get property of SummaryData.
     *
     * @return Property of SummaryData.
     */
    public ObjectProperty<SummaryData> summaryDataProperty() {
        return summaryData;
    }

    /**
     * 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 new task for calculating GC summary.
     *
     * @param drawRebootSuspectLine Consumer for drawing reboot line.
     * @return Task for calculating GC summary.
     */
    public Task<Void> getCalculateGCSummaryTask(Consumer<XYChart<Number, ? extends Number>> drawRebootSuspectLine) {
        return new CalculateGCSummaryTask(drawRebootSuspectLine);
    }

    /**
     * Get Java heap chart.
     *
     * @return Java heap chart.
     */
    public StackedAreaChart<Number, Long> getHeapChart() {
        return heapChart;
    }

    /**
     * Get instance chart.
     *
     * @return Instance chart.
     */
    public LineChart<Number, Long> getInstanceChart() {
        return instanceChart;
    }

    /**
     * Get GC time chart.
     *
     * @return GC time chart.
     */
    public LineChart<Number, Long> getGcTimeChart() {
        return gcTimeChart;
    }

    /**
     * Get Metaspace chart.
     *
     * @return Metaspace chart.
     */
    public AreaChart<Number, Long> getMetaspaceChart() {
        return metaspaceChart;
    }

    /**
     * Clear all items in SnapShot Summary tab.
     */
    public void clearAllItems(){
        summaryTable.getItems().clear();
        
        Stream.of(heapChart, instanceChart, gcTimeChart, metaspaceChart)
              .peek(c -> c.getData().stream().forEach(s -> s.getData().clear())) // Clear chart series data.
              .flatMap(c -> c.getParent().getChildrenUnmodifiable().stream()) // Get parent node of chart.
              .filter(n -> n instanceof AnchorPane)
              .forEach(p -> ((AnchorPane)p).getChildren().clear()); // Chart reboot suspect lines.
}

}