view analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/fx/plugin/builtin/log/tabs/LogResourcesController.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.log.tabs;

import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.ResourceBundle;
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.concurrent.Task;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
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.layout.StackPane;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import jp.co.ntt.oss.heapstats.container.log.ArchiveData;
import jp.co.ntt.oss.heapstats.container.log.DiffData;
import jp.co.ntt.oss.heapstats.container.log.LogData;
import jp.co.ntt.oss.heapstats.container.log.SummaryData;
import jp.co.ntt.oss.heapstats.fx.utils.EpochTimeConverter;
import jp.co.ntt.oss.heapstats.api.utils.HeapStatsUtils;

/**
 * FXML Controller class for "Resource Data" tab in LogData plugin.
 */
public class LogResourcesController implements Initializable {

    @FXML
    private GridPane chartGrid;

    @FXML
    private StackedAreaChart<Number, Double> javaCPUChart;

    private XYChart.Series<Number, Double> javaUserUsage;

    private XYChart.Series<Number, Double> javaSysUsage;

    @FXML
    private StackedAreaChart<Number, Double> systemCPUChart;

    private XYChart.Series<Number, Double> systemUserUsage;

    private XYChart.Series<Number, Double> systemNiceUsage;

    private XYChart.Series<Number, Double> systemSysUsage;

    private XYChart.Series<Number, Double> systemIdleUsage;

    private XYChart.Series<Number, Double> systemIOWaitUsage;

    private XYChart.Series<Number, Double> systemIRQUsage;

    private XYChart.Series<Number, Double> systemSoftIRQUsage;

    private XYChart.Series<Number, Double> systemStealUsage;

    private XYChart.Series<Number, Double> systemGuestUsage;

    @FXML
    private LineChart<Number, Long> javaMemoryChart;

    private XYChart.Series<Number, Long> javaVSZUsage;

    private XYChart.Series<Number, Long> javaRSSUsage;

    @FXML
    private LineChart<Number, Long> safepointChart;

    private XYChart.Series<Number, Long> safepoints;

    @FXML
    private LineChart<Number, Long> safepointTimeChart;

    private XYChart.Series<Number, Long> safepointTime;

    @FXML
    private LineChart<Number, Long> threadChart;

    private XYChart.Series<Number, Long> threads;

    @FXML
    private LineChart<Number, Long> monitorChart;

    private XYChart.Series<Number, Long> monitors;

    @FXML
    private TableView<SummaryData.SummaryDataEntry> procSummary;

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

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

    private ObjectProperty<ObservableList<ArchiveData>> archiveList;

    private List<LocalDateTime> suspectList;
    
    private ResourceBundle resource;
    
    private EpochTimeConverter epochTimeConverter;

    /* Tooltip for Java CPU chart */
    private Tooltip javaCPUTooltip;

    private GridPane javaCPUTooltipGrid;

    private Label javaUserLabel;

    private Label javaSysLabel;

    /* Tooltip for System CPU chart */
    private Tooltip systemCPUTooltip;

    private GridPane systemCPUTooltipGrid;

    private Label systemUserLabel;

    private Label systemNiceLabel;

    private Label systemSysLabel;

    private Label systemIdleLabel;

    private Label systemIOWaitLabel;

    private Label systemIRQLabel;

    private Label systemSoftIRQLabel;

    private Label systemStealLabel;

    private Label systemGuestLabel;

    /* Tooltip for Java Memory chart */
    private Tooltip javaMemoryTooltip;

    private GridPane javaMemoryTooltipGrid;

    private Label javaMemoryVSZLabel;

    private Label javaMemoryRSSLabel;

    /* Generic Tooltip */
    private Tooltip tooltip;

    private void initializeJavaCPUTooltip(){
        javaUserLabel = new Label();
        javaSysLabel = new Label();

        Rectangle javaUserRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle javaSysRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);

        Platform.runLater(() -> {
            javaUserRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
            javaSysRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
        });

        javaCPUTooltipGrid = new GridPane();
        javaCPUTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
        javaCPUTooltipGrid.add(javaUserRect, 0, 0);
        javaCPUTooltipGrid.add(new Label("user"), 1, 0);
        javaCPUTooltipGrid.add(javaUserLabel, 2, 0);
        javaCPUTooltipGrid.add(javaSysRect, 0, 1);
        javaCPUTooltipGrid.add(new Label("sys"), 1, 1);
        javaCPUTooltipGrid.add(javaSysLabel, 2, 1);

        javaCPUTooltip = new Tooltip();
        javaCPUTooltip.setGraphic(javaCPUTooltipGrid);
        javaCPUTooltip.setContentDisplay(ContentDisplay.BOTTOM);
    }

    private void initializeJavaMemoryTooltip(){
        javaMemoryVSZLabel = new Label();
        javaMemoryRSSLabel = new Label();

        Rectangle javaMemoryVSZRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle javaMemoryRSSRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);

        Platform.runLater(() -> {
            javaMemoryRSSRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
            javaMemoryVSZRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
        });

        javaMemoryTooltipGrid = new GridPane();
        javaMemoryTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
        javaMemoryTooltipGrid.add(javaMemoryVSZRect, 0, 0);
        javaMemoryTooltipGrid.add(new Label("VSZ"), 1, 0);
        javaMemoryTooltipGrid.add(javaMemoryVSZLabel, 2, 0);
        javaMemoryTooltipGrid.add(javaMemoryRSSRect, 0, 1);
        javaMemoryTooltipGrid.add(new Label("RSS"), 1, 1);
        javaMemoryTooltipGrid.add(javaMemoryRSSLabel, 2, 1);

        javaMemoryTooltip = new Tooltip();
        javaMemoryTooltip.setGraphic(javaMemoryTooltipGrid);
        javaMemoryTooltip.setContentDisplay(ContentDisplay.BOTTOM);
    }

    private void initializeSystemCPUTooltip(){
        systemUserLabel = new Label();
        systemNiceLabel = new Label();
        systemSysLabel = new Label();
        systemIdleLabel = new Label();
        systemIOWaitLabel = new Label();
        systemIRQLabel = new Label();
        systemSoftIRQLabel = new Label();
        systemStealLabel = new Label();
        systemGuestLabel = new Label();

        Rectangle systemUserRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemNiceRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemSysRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemIdleRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemIOWaitRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemIRQRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemSoftIRQRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemStealRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
        Rectangle systemGuestRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);

        Platform.runLater(() -> {
            systemUserRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
            systemNiceRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
            systemSysRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series2")).getFill().toString().replace("0x", "#"));
            systemIdleRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series3")).getFill().toString().replace("0x", "#"));
            systemIOWaitRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series4")).getFill().toString().replace("0x", "#"));
            systemIRQRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series5")).getFill().toString().replace("0x", "#"));
            systemSoftIRQRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series6")).getFill().toString().replace("0x", "#"));
            systemStealRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series7")).getFill().toString().replace("0x", "#"));
            systemGuestRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series8")).getFill().toString().replace("0x", "#"));
        });

        systemCPUTooltipGrid = new GridPane();
        systemCPUTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
        systemCPUTooltipGrid.add(systemUserRect, 0, 0);
        systemCPUTooltipGrid.add(new Label("user"), 1, 0);
        systemCPUTooltipGrid.add(systemUserLabel, 2, 0);
        systemCPUTooltipGrid.add(systemNiceRect, 0, 1);
        systemCPUTooltipGrid.add(new Label("nice"), 1, 1);
        systemCPUTooltipGrid.add(systemNiceLabel, 2, 1);
        systemCPUTooltipGrid.add(systemSysRect, 0, 2);
        systemCPUTooltipGrid.add(new Label("sys"), 1, 2);
        systemCPUTooltipGrid.add(systemSysLabel, 2, 2);
        systemCPUTooltipGrid.add(systemIdleRect, 0, 3);
        systemCPUTooltipGrid.add(new Label("idle"), 1, 3);
        systemCPUTooltipGrid.add(systemIdleLabel, 2, 3);
        systemCPUTooltipGrid.add(systemIOWaitRect, 0, 4);
        systemCPUTooltipGrid.add(new Label("iowait"), 1, 4);
        systemCPUTooltipGrid.add(systemIOWaitLabel, 2, 4);
        systemCPUTooltipGrid.add(systemIRQRect, 0, 5);
        systemCPUTooltipGrid.add(new Label("IRQ"), 1, 5);
        systemCPUTooltipGrid.add(systemIRQLabel, 2, 5);
        systemCPUTooltipGrid.add(systemSoftIRQRect, 0, 6);
        systemCPUTooltipGrid.add(new Label("Soft IRQ"), 1, 6);
        systemCPUTooltipGrid.add(systemSoftIRQLabel, 2, 6);
        systemCPUTooltipGrid.add(systemStealRect, 0, 7);
        systemCPUTooltipGrid.add(new Label("steal"), 1, 7);
        systemCPUTooltipGrid.add(systemStealLabel, 2, 7);
        systemCPUTooltipGrid.add(systemGuestRect, 0, 8);
        systemCPUTooltipGrid.add(new Label("guest"), 1, 8);
        systemCPUTooltipGrid.add(systemGuestLabel, 2, 8);

        systemCPUTooltip = new Tooltip();
        systemCPUTooltip.setGraphic(systemCPUTooltipGrid);
        systemCPUTooltip.setContentDisplay(ContentDisplay.BOTTOM);
    }

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        resource = rb;
        
        categoryColumn.setCellValueFactory(new PropertyValueFactory<>("category"));
        valueColumn.setCellValueFactory(new PropertyValueFactory<>("value"));

        String bgcolor = "-fx-background-color: " + HeapStatsUtils.getChartBgColor() + ";";
        Stream.of(javaCPUChart, systemCPUChart, javaMemoryChart, safepointChart, safepointTimeChart, threadChart, monitorChart)
              .peek(c -> c.lookup(".chart").setStyle(bgcolor))
              .forEach(c -> c.getXAxis().setTickMarkVisible(HeapStatsUtils.getTickMarkerSwitch()));

        initializeChartSeries();

        archiveList = new SimpleObjectProperty<>();
        epochTimeConverter = new EpochTimeConverter();

        initializeJavaCPUTooltip();
        initializeSystemCPUTooltip();
        initializeJavaMemoryTooltip();
    }

    /**
     * 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() {
        threads = new XYChart.Series<>();
        threads.setName("Threads");
        threadChart.getData().add(threads);

        javaUserUsage = new XYChart.Series<>();
        javaUserUsage.setName("user");
        javaSysUsage = new XYChart.Series<>();
        javaSysUsage.setName("sys");
        javaCPUChart.getData().addAll(javaUserUsage, javaSysUsage);

        systemUserUsage = new XYChart.Series<>();
        systemUserUsage.setName("user");
        systemNiceUsage = new XYChart.Series<>();
        systemNiceUsage.setName("nice");
        systemSysUsage = new XYChart.Series<>();
        systemSysUsage.setName("sys");
        systemIdleUsage = new XYChart.Series<>();
        systemIdleUsage.setName("idle");
        systemIOWaitUsage = new XYChart.Series<>();
        systemIOWaitUsage.setName("I/O wait");
        systemIRQUsage = new XYChart.Series<>();
        systemIRQUsage.setName("IRQ");
        systemSoftIRQUsage = new XYChart.Series<>();
        systemSoftIRQUsage.setName("soft IRQ");
        systemStealUsage = new XYChart.Series<>();
        systemStealUsage.setName("steal");
        systemGuestUsage = new XYChart.Series<>();
        systemGuestUsage.setName("guest");
        systemCPUChart.getData().addAll(systemUserUsage, systemNiceUsage, systemSysUsage,
                systemIdleUsage, systemIOWaitUsage, systemIRQUsage,
                systemSoftIRQUsage, systemStealUsage, systemGuestUsage);

        javaVSZUsage = new XYChart.Series<>();
        javaVSZUsage.setName("VSZ");
        javaRSSUsage = new XYChart.Series<>();
        javaRSSUsage.setName("RSS");
        javaMemoryChart.getData().addAll(javaVSZUsage, javaRSSUsage);

        safepoints = new XYChart.Series<>();
        safepoints.setName("Safepoints");
        safepointChart.getData().add(safepoints);

        safepointTime = new XYChart.Series<>();
        safepointTime.setName("Safepoint Time");
        safepointTimeChart.getData().add(safepointTime);

        monitors = new XYChart.Series<>();
        monitors.setName("Monitors");
        monitorChart.getData().add(monitors);
    }

    private void drawLineInternal(StackPane target, List<Number> drawList, String style) {
        AnchorPane anchor = null;
        XYChart chart = null;

        for (Node node : ((StackPane) target).getChildren()) {

            if (node instanceof AnchorPane) {
                anchor = (AnchorPane) node;
            } else if (node instanceof XYChart) {
                chart = (XYChart) node;
            }

            if ((anchor != null) && (chart != null)) {
                break;
            }

        }

        if ((anchor == null) || (chart == null)) {
            throw new IllegalStateException(resource.getString("message.drawline"));
        }

        ObservableList<Node> anchorChildren = anchor.getChildren();
        anchorChildren.removeAll(anchorChildren.stream()
                .filter(n -> n instanceof Rectangle)
                .map(n -> (Rectangle) n)
                .filter(r -> r.getStyle().equals(style))
                .collect(Collectors.toList()));

        NumberAxis xAxis = (NumberAxis) chart.getXAxis();
        Axis yAxis = chart.getYAxis();
        Label chartTitle = (Label) chart.getChildrenUnmodifiable().stream()
                .filter(n -> n.getStyleClass().contains("chart-title"))
                .findFirst()
                .get();

        double startX = xAxis.getLayoutX() + 4.0d;
        double yPos = yAxis.getLayoutY() + chartTitle.getLayoutY() + chartTitle.getHeight();
        List<Rectangle> rectList = drawList.stream()
                .map(t -> new Rectangle(xAxis.getDisplayPosition(t) + startX, yPos, 2.0d, yAxis.getHeight()))
                .peek(r -> ((Rectangle) r).setStyle(style))
                .collect(Collectors.toList());
        anchorChildren.addAll(rectList);
    }

    private void drawArchiveLine() {

        if (archiveList.get().isEmpty()) {
            return;
        }

        List<Number> archiveDateList = archiveList.get()
                                                  .stream()
                                                  .map(a -> a.getDate().atZone(ZoneId.systemDefault()).toEpochSecond())
                                                  .collect(Collectors.toList());
        chartGrid.getChildren().stream()
                .filter(n -> n instanceof StackPane)
                .forEach(p -> drawLineInternal((StackPane) p, archiveDateList, "-fx-fill: black;"));
    }

    /**
     * Draw line which represents to suspect to reboot. This method does not
     * clear AnchorPane to draw lines. So this method must be called after
     * drawArchiveLine().
     */
    private void drawRebootSuspectLine() {

        if ((suspectList == null) || suspectList.isEmpty()) {
            return;
        }

        List<Number> suspectRebootDateList = suspectList.stream()
                                                        .map(d -> d.atZone(ZoneId.systemDefault()).toEpochSecond())
                                                        .collect(Collectors.toList());
        chartGrid.getChildren().stream()
                .filter(n -> n instanceof StackPane)
                .forEach(p -> drawLineInternal((StackPane) p, suspectRebootDateList, "-fx-fill: yellow;"));
    }

    /**
     * Draw lines which represents reboot and ZIP archive timing.
     */
    public void drawEventLineToChart() {
        drawArchiveLine();
        drawRebootSuspectLine();
    }

    /**
     * Task class for drawing log chart data.
     */
    private class DrawLogChartTask extends Task<Void> {

        /* Java CPU */
        private final ObservableList<XYChart.Data<Number, Double>> javaUserUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> javaSysUsageBuf;

        /* System CPU */
        private final ObservableList<XYChart.Data<Number, Double>> systemUserUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemNiceUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemSysUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemIdleUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemIOWaitUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemIRQUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemSoftIRQUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemStealUsageBuf;
        private final ObservableList<XYChart.Data<Number, Double>> systemGuestUsageBuf;

        /* Java Memory */
        private final ObservableList<XYChart.Data<Number, Long>> javaVSZUsageBuf;
        private final ObservableList<XYChart.Data<Number, Long>> javaRSSUsageBuf;

        /* Safepoints */
        private final ObservableList<XYChart.Data<Number, Long>> safepointsBuf;
        private final ObservableList<XYChart.Data<Number, Long>> safepointTimeBuf;

        /* Threads */
        private final ObservableList<XYChart.Data<Number, Long>> threadsBuf;

        /* Monitor contantion */
        private final ObservableList<XYChart.Data<Number, Long>> monitorsBuf;

        private final List<LogData> targetLogData;

        private final List<DiffData> targetDiffData;

        private long loopCount;

        private final long totalLoopCount;

        public DrawLogChartTask(List<LogData> targetLogData, List<DiffData> targetDiffData) {
            javaUserUsageBuf = FXCollections.observableArrayList();
            javaSysUsageBuf = FXCollections.observableArrayList();
            systemUserUsageBuf = FXCollections.observableArrayList();
            systemNiceUsageBuf = FXCollections.observableArrayList();
            systemSysUsageBuf = FXCollections.observableArrayList();
            systemIdleUsageBuf = FXCollections.observableArrayList();
            systemIOWaitUsageBuf = FXCollections.observableArrayList();
            systemIRQUsageBuf = FXCollections.observableArrayList();
            systemSoftIRQUsageBuf = FXCollections.observableArrayList();
            systemStealUsageBuf = FXCollections.observableArrayList();
            systemGuestUsageBuf = FXCollections.observableArrayList();
            javaVSZUsageBuf = FXCollections.observableArrayList();
            javaRSSUsageBuf = FXCollections.observableArrayList();
            safepointsBuf = FXCollections.observableArrayList();
            safepointTimeBuf = FXCollections.observableArrayList();
            threadsBuf = FXCollections.observableArrayList();
            monitorsBuf = FXCollections.observableArrayList();

            this.targetLogData = targetLogData;
            this.targetDiffData = targetDiffData;
            totalLoopCount = targetDiffData.size() + targetLogData.size();
        }

        private void addDiffData(DiffData data) {
            long time = data.getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();

            javaUserUsageBuf.add(new XYChart.Data<>(time, data.getJavaUserUsage()));
            javaSysUsageBuf.add(new XYChart.Data<>(time, data.getJavaSysUsage()));
            systemUserUsageBuf.add(new XYChart.Data<>(time, data.getCpuUserUsage()));
            systemNiceUsageBuf.add(new XYChart.Data<>(time, data.getCpuNiceUsage()));
            systemSysUsageBuf.add(new XYChart.Data<>(time, data.getCpuSysUsage()));
            systemIdleUsageBuf.add(new XYChart.Data<>(time, data.getCpuIdleUsage()));
            systemIOWaitUsageBuf.add(new XYChart.Data<>(time, data.getCpuIOWaitUsage()));
            systemIRQUsageBuf.add(new XYChart.Data<>(time, data.getCpuIRQUsage()));
            systemSoftIRQUsageBuf.add(new XYChart.Data<>(time, data.getCpuSoftIRQUsage()));
            systemStealUsageBuf.add(new XYChart.Data<>(time, data.getCpuStealUsage()));
            systemGuestUsageBuf.add(new XYChart.Data<>(time, data.getCpuGuestUsage()));
            monitorsBuf.add(new XYChart.Data<>(time, data.getJvmSyncPark()));
            safepointsBuf.add(new XYChart.Data<>(time, data.getJvmSafepoints()));
            safepointTimeBuf.add(new XYChart.Data<>(time, data.getJvmSafepointTime()));

            updateProgress();
        }

        private void addLogData(LogData data) {
            long time = data.getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();

            javaVSZUsageBuf.add(new XYChart.Data<>(time, data.getJavaVSSize() / 1024 / 1024));
            javaRSSUsageBuf.add(new XYChart.Data<>(time, data.getJavaRSSize() / 1024 / 1024));
            threadsBuf.add(new XYChart.Data<>(time, data.getJvmLiveThreads()));

            updateProgress();
        }

        private void setJavaCPUChartTooltip(int idx){
            XYChart.Data<Number, Double> userNode = javaUserUsage.getData().get(idx);
            XYChart.Data<Number, Double> sysNode = javaSysUsage.getData().get(idx);

            EventHandler<MouseEvent> handler = e -> {
                javaCPUTooltip.setText(epochTimeConverter.toString(userNode.getXValue()));
                javaUserLabel.setText(String.format("%.02f", userNode.getYValue()) + " %");
                javaSysLabel.setText(String.format("%.02f", sysNode.getYValue()) + " %");
            };

            Tooltip.install(userNode.getNode(), javaCPUTooltip);
            userNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(sysNode.getNode(), javaCPUTooltip);
            sysNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
        }

        private void setJavaMemoryChartTooltip(int idx){
            XYChart.Data<Number, Long> vszNode = javaVSZUsage.getData().get(idx);
            XYChart.Data<Number, Long> rssNode = javaRSSUsage.getData().get(idx);

            EventHandler<MouseEvent> handler = e -> {
                javaMemoryTooltip.setText(epochTimeConverter.toString(vszNode.getXValue()));
                javaMemoryVSZLabel.setText(vszNode.getYValue() + " MB");
                javaMemoryRSSLabel.setText(rssNode.getYValue() + " MB");
            };

            Tooltip.install(vszNode.getNode(), javaMemoryTooltip);
            vszNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(rssNode.getNode(), javaMemoryTooltip);
            rssNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
        }

        private void setSystemCPUChartTooltip(int idx){
            XYChart.Data<Number, Double> userNode = systemUserUsage.getData().get(idx);
            XYChart.Data<Number, Double> niceNode = systemNiceUsage.getData().get(idx);
            XYChart.Data<Number, Double> sysNode = systemSysUsage.getData().get(idx);
            XYChart.Data<Number, Double> idleNode = systemIdleUsage.getData().get(idx);
            XYChart.Data<Number, Double> iowaitNode = systemIOWaitUsage.getData().get(idx);
            XYChart.Data<Number, Double> irqNode = systemIRQUsage.getData().get(idx);
            XYChart.Data<Number, Double> softIrqNode = systemSoftIRQUsage.getData().get(idx);
            XYChart.Data<Number, Double> stealNode = systemStealUsage.getData().get(idx);
            XYChart.Data<Number, Double> guestNode = systemGuestUsage.getData().get(idx);

            EventHandler<MouseEvent> handler = e -> {
                systemCPUTooltip.setText(epochTimeConverter.toString(userNode.getXValue()));
                systemUserLabel.setText(String.format("%.02f", userNode.getYValue()) + " %");
                systemNiceLabel.setText(String.format("%.02f", niceNode.getYValue()) + " %");
                systemSysLabel.setText(String.format("%.02f", sysNode.getYValue()) + " %");
                systemIdleLabel.setText(String.format("%.02f", idleNode.getYValue()) + " %");
                systemIOWaitLabel.setText(String.format("%.02f", iowaitNode.getYValue()) + " %");
                systemIRQLabel.setText(String.format("%.02f", irqNode.getYValue()) + " %");
                systemSoftIRQLabel.setText(String.format("%.02f", softIrqNode.getYValue()) + " %");
                systemStealLabel.setText(String.format("%.02f", stealNode.getYValue()) + " %");
                systemGuestLabel.setText(String.format("%.02f", guestNode.getYValue()) + " %");
            };

            Tooltip.install(userNode.getNode(), systemCPUTooltip);
            userNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(niceNode.getNode(), systemCPUTooltip);
            niceNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(sysNode.getNode(), systemCPUTooltip);
            sysNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(idleNode.getNode(), systemCPUTooltip);
            idleNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(iowaitNode.getNode(), systemCPUTooltip);
            iowaitNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(irqNode.getNode(), systemCPUTooltip);
            irqNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(softIrqNode.getNode(), systemCPUTooltip);
            softIrqNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(stealNode.getNode(), systemCPUTooltip);
            stealNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
            Tooltip.install(guestNode.getNode(), systemCPUTooltip);
            guestNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
        }

        private void setChartData() {
            
            if(targetLogData.isEmpty() || targetDiffData.isEmpty()){
                Stream.of(javaMemoryChart, threadChart)
                      .flatMap(c -> c.getData().stream())
                      .forEach(s -> s.getData().clear());
                Stream.of(javaCPUChart, systemCPUChart, safepointChart, safepointTimeChart, monitorChart)
                      .flatMap(c -> c.getData().stream())
                      .forEach(s -> s.getData().clear());
                procSummary.getItems().clear();
                suspectList = null;
                return;
            }
            
            /* Set chart range */
            long startLogEpoch = targetLogData.get(0).getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();
            long endLogEpoch = targetLogData.get(targetLogData.size() - 1).getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();
            long startDiffEpoch = targetDiffData.get(0).getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();
            long endDiffEpoch = targetDiffData.get(targetDiffData.size() - 1).getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();
            Stream.of(javaMemoryChart, threadChart)
                  .map(c -> (NumberAxis)c.getXAxis())
                  .peek(a -> a.setTickUnit((endLogEpoch - startLogEpoch) / HeapStatsUtils.getXTickUnit()))
                  .peek(a -> a.setLowerBound(startLogEpoch))
                  .forEach(a -> a.setUpperBound(endLogEpoch));
            Stream.of(javaCPUChart, systemCPUChart, safepointChart, safepointTimeChart, monitorChart)
                  .map(c -> (NumberAxis)c.getXAxis())
                  .peek(a -> a.setTickUnit((endDiffEpoch - startDiffEpoch) / HeapStatsUtils.getXTickUnit()))
                  .peek(a -> a.setLowerBound(startDiffEpoch))
                  .forEach(a -> a.setUpperBound(endDiffEpoch));
            
            /* Replace new chart data */
            javaUserUsage.setData(javaUserUsageBuf);
            javaSysUsage.setData(javaSysUsageBuf);

            systemUserUsage.setData(systemUserUsageBuf);
            systemNiceUsage.setData(systemNiceUsageBuf);
            systemSysUsage.setData(systemSysUsageBuf);
            systemIdleUsage.setData(systemIdleUsageBuf);
            systemIOWaitUsage.setData(systemIOWaitUsageBuf);
            systemIRQUsage.setData(systemIRQUsageBuf);
            systemSoftIRQUsage.setData(systemSoftIRQUsageBuf);
            systemStealUsage.setData(systemStealUsageBuf);
            systemGuestUsage.setData(systemGuestUsageBuf);

            monitors.setData(monitorsBuf);

            safepoints.setData(safepointsBuf);
            safepointTime.setData(safepointTimeBuf);

            javaVSZUsage.setData(javaVSZUsageBuf);
            javaRSSUsage.setData(javaRSSUsageBuf);

            threads.setData(threadsBuf);

            /* Tooltip setting */
            IntStream.range(0, targetDiffData.size())
                     .peek(this::setJavaCPUChartTooltip)
                     .forEach(this::setSystemCPUChartTooltip);
            IntStream.range(0, targetLogData.size())
                     .forEach(this::setJavaMemoryChartTooltip);
            Tooltip tooltip = new Tooltip();
            Stream.of(threads, safepoints, monitors)
                  .flatMap(c -> c.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()))));
            safepointTime.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()))));

            /* Put summary data to table */
            SummaryData summary = new SummaryData(targetLogData, targetDiffData);
            procSummary.setItems(FXCollections.observableArrayList(new SummaryData.SummaryDataEntry(resource.getString("summary.cpu.average"), String.format("%.1f %%", summary.getAverageCPUUsage())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.cpu.peak"), String.format("%.1f %%", summary.getMaxCPUUsage())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.vsz.average"), String.format("%.1f MB", summary.getAverageVSZ())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.vsz.peak"), String.format("%.1f MB", summary.getMaxVSZ())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.rss.average"), String.format("%.1f MB", summary.getAverageRSS())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.rss.peak"), String.format("%.1f MB", summary.getMaxRSS())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.threads.average"), String.format("%.1f", summary.getAverageLiveThreads())),
                    new SummaryData.SummaryDataEntry(resource.getString("summary.threads.peak"), Long.toString(summary.getMaxLiveThreads()))
            ));

            /*
             * drawArchiveLine() needs positions in each chart.
             * So I call it next event.
             */
            suspectList = targetDiffData.stream()
                    .filter(d -> d.hasMinusData())
                    .map(d -> d.getDateTime())
                    .collect(Collectors.toList());

            Platform.runLater(() -> drawEventLineToChart());
        }

        private void updateProgress() {
            updateProgress(++loopCount, totalLoopCount);
        }

        @Override
        protected Void call() throws Exception {
            loopCount = 0;

            /* Generate graph data */
            targetDiffData.forEach(this::addDiffData);
            targetLogData.forEach(this::addLogData);

            Platform.runLater(this::setChartData);

            return null;
        }

    }

    /**
     * Get Task instance which draws each chart.
     *
     * @param targetLogData List of LogData to draw.
     * @param targetDiffData List of DiffData to draw.
     *
     * @return Task instance to draw charts.
     */
    public Task<Void> createDrawResourceCharts(List<LogData> targetLogData, List<DiffData> targetDiffData) {
        return new DrawLogChartTask(targetLogData, targetDiffData);
    }

    /**
     * Get HeapStats ZIP archive list as Property.
     *
     * @return List of HeapStats ZIP archive.
     */
    public ObjectProperty<ObservableList<ArchiveData>> archiveListProperty() {
        return archiveList;
    }

    /**
     * Clear all items in Resource Data tab.
     */
    @SuppressWarnings({"raw", "unchecked"})
    public void clearAllItems(){
        
        for(Node n : chartGrid.getChildren()){
            if(n instanceof StackPane){
                for(Node cn : ((StackPane)n).getChildren()){
                    if(cn instanceof AnchorPane){ // Clear archive and reboot suspect lines.
                        ((AnchorPane)cn).getChildren().clear();
}
                    else if(cn instanceof XYChart){ // Clear chart data.
                        ((XYChart)cn).getData().stream()
                                .forEach(s -> ((XYChart.Series)s).getData().clear());
                    }
                }
            }
        }
        
        /* Claer summary table. */
        procSummary.getItems().clear();
    }

}