All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.adoptopenjdk.jitwatch.ui.main.JITWatchUI Maven / Gradle / Ivy

/*
 * Copyright (c) 2013-2017 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.ui.main;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_DOT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SPACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEFAULT_PACKAGE_NAME;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_CLOSE_PARENTHESES;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_EMPTY;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SLASH;
import static org.adoptopenjdk.jitwatch.util.UserInterfaceUtil.FONT_MONOSPACE_FAMILY;
import static org.adoptopenjdk.jitwatch.util.UserInterfaceUtil.FONT_MONOSPACE_SIZE;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.adoptopenjdk.jitwatch.chain.CompileChainWalker;
import org.adoptopenjdk.jitwatch.chain.CompileNode;
import org.adoptopenjdk.jitwatch.compilation.codecache.CodeCacheEventWalker;
import org.adoptopenjdk.jitwatch.compilation.codecache.CodeCacheWalkerResult;
import org.adoptopenjdk.jitwatch.core.ErrorLog;
import org.adoptopenjdk.jitwatch.core.IJITListener;
import org.adoptopenjdk.jitwatch.core.JITWatchConfig;
import org.adoptopenjdk.jitwatch.core.JITWatchConstants;
import org.adoptopenjdk.jitwatch.model.Compilation;
import org.adoptopenjdk.jitwatch.model.IMetaMember;
import org.adoptopenjdk.jitwatch.model.IReadOnlyJITDataModel;
import org.adoptopenjdk.jitwatch.model.JITEvent;
import org.adoptopenjdk.jitwatch.model.MetaClass;
import org.adoptopenjdk.jitwatch.model.PackageManager;
import org.adoptopenjdk.jitwatch.optimizedvcall.OptimizedVirtualCall;
import org.adoptopenjdk.jitwatch.optimizedvcall.OptimizedVirtualCallFinder;
import org.adoptopenjdk.jitwatch.optimizedvcall.OptimizedVirtualCallVisitable;
import org.adoptopenjdk.jitwatch.parser.ILogParseErrorListener;
import org.adoptopenjdk.jitwatch.parser.ILogParser;
import org.adoptopenjdk.jitwatch.parser.ParserFactory;
import org.adoptopenjdk.jitwatch.parser.hotspot.HotSpotLogParser;
import org.adoptopenjdk.jitwatch.report.Report;
import org.adoptopenjdk.jitwatch.report.comparator.ScoreComparator;
import org.adoptopenjdk.jitwatch.report.escapeanalysis.eliminatedallocation.EliminatedAllocationWalker;
import org.adoptopenjdk.jitwatch.report.escapeanalysis.lockelision.ElidedLocksWalker;
import org.adoptopenjdk.jitwatch.report.inlining.InliningWalker;
import org.adoptopenjdk.jitwatch.report.suggestion.SuggestionWalker;
import org.adoptopenjdk.jitwatch.ui.Dialogs;
import org.adoptopenjdk.jitwatch.ui.browser.BrowserStage;
import org.adoptopenjdk.jitwatch.ui.codecache.CodeCacheLayoutStage;
import org.adoptopenjdk.jitwatch.ui.compilechain.CompileChainStage;
import org.adoptopenjdk.jitwatch.ui.graphing.CodeCacheStage;
import org.adoptopenjdk.jitwatch.ui.graphing.HistoStage;
import org.adoptopenjdk.jitwatch.ui.graphing.TimeLineStage;
import org.adoptopenjdk.jitwatch.ui.optimizedvcall.OptimizedVirtualCallStage;
import org.adoptopenjdk.jitwatch.ui.report.ReportStage;
import org.adoptopenjdk.jitwatch.ui.report.ReportStageType;
import org.adoptopenjdk.jitwatch.ui.sandbox.SandboxStage;
import org.adoptopenjdk.jitwatch.ui.stage.IStageClosedListener;
import org.adoptopenjdk.jitwatch.ui.stage.StageManager;
import org.adoptopenjdk.jitwatch.ui.stats.StatsStage;
import org.adoptopenjdk.jitwatch.ui.toplist.TopListStage;
import org.adoptopenjdk.jitwatch.ui.triview.TriView;
import org.adoptopenjdk.jitwatch.ui.viewer.JournalViewerStage;
import org.adoptopenjdk.jitwatch.ui.viewer.TextViewerStage;
import org.adoptopenjdk.jitwatch.util.OSUtil;
import org.adoptopenjdk.jitwatch.util.UserInterfaceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;

public class JITWatchUI extends Application
		implements IJITListener, ILogParseErrorListener, IStageClosedListener, IStageAccessProxy, IMemberSelectedListener
{
	private static final Logger logger = LoggerFactory.getLogger(JITWatchUI.class);

	public static final int WINDOW_WIDTH = 1024;
	public static final int WINDOW_HEIGHT = 550;

	private static final String JAVA_VERSION_7 = "1.7";
	public static final boolean IS_JAVA_FX2;

	private boolean selectedProgrammatically = false;

	static
	{
		String version = System.getProperty("java.version", JAVA_VERSION_7);

		if (version.contains(JAVA_VERSION_7))
		{
			IS_JAVA_FX2 = true;

			if (OSUtil.getOperatingSystem() == OSUtil.OperatingSystem.MAC)
			{
				UserInterfaceUtil.initMacFonts();
			}
		}
		else
		{
			IS_JAVA_FX2 = false;
		}
	}

	private Stage stage;

	private ILogParser logParser;

	private ClassTree classTree;
	private ClassMemberList classMemberList;

	private TableView compilationTable;
	private ObservableList compilationRowList;

	private TextArea textAreaLog;

	private File jitLogFile = null;

	private String lastVmCommand = null;
	private IMetaMember lastSelectedMember = null;
	private MetaClass lastSelectedClass = null;

	private boolean isReadingLogFile = false;

	private Label lblVmVersion;
	private Label lblTweakLog;

	private Button btnStart;
	private Button btnStop;
	private Button btnConfigure;
	private Button btnTimeLine;
	private Button btnStats;
	private Button btnReset;
	private Button btnHisto;
	private Button btnTopList;
	private Button btnErrorLog;
	private Button btnCodeCacheTimeline;
	private Button btnNMethods;
	private Button btnTriView;
	private Button btnReportSuggestions;
	private Button btnReportEliminatedAllocations;
	private Button btnReportElidedLocks;
	private Button btnOptimizedVirtualCalls;
	private Button btnSandbox;

	private Label lblHeap;

	private MainConfigStage configStage;
	private TimeLineStage timeLineStage;
	private StatsStage statsStage;
	private HistoStage histoStage;
	private TopListStage topListStage;
	private CodeCacheStage codeCacheTimelineStage;
	private CodeCacheLayoutStage codeCacheBlocksStage;
	private TriView triViewStage;
	private BrowserStage browserStage;
		
	private ReportStage reportStageSuggestions;
	private ReportStage reportStageElminatedAllocations;
	private ReportStage reportStageElidedLocks;

	private OptimizedVirtualCallStage ovcStage;
	private SandboxStage sandBoxStage;

	private NothingMountedStage nothingMountedStage;

	private IMetaMember selectedMember;
	private MetaClass selectedMetaClass;

	private List reportListSuggestions = new ArrayList<>();
	private List reportListEliminatedAllocations = new ArrayList<>();
	private List reportListElidedLocks = new ArrayList<>();

	private CodeCacheWalkerResult codeCacheWalkerResult;

	private Runtime runtime = Runtime.getRuntime();

	// synchronized as buffer is drained async on GUI thread
	private StringBuffer logBuffer = new StringBuffer();

	private ErrorLog errorLog = new ErrorLog();
	private int errorCount = 0;

	private boolean repaintTree = false;
	private boolean startDelayedByConfig = false;

	// Called by JFX
	public JITWatchUI()
	{
		logParser = ParserFactory.getParser(this);
	}

	public JITWatchUI(String[] args)
	{
		launch(args);
	}

	private void readLogFile()
	{
		Thread jwThread = new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				try
				{
					logParser.processLogFile(jitLogFile, JITWatchUI.this);
				}
				catch (IOException ioe)
				{
					log("Exception during log processing: " + ioe.toString());
				}
			}
		});

		jwThread.start();
	}

	@Override
	public void handleReadStart()
	{
		startDelayedByConfig = false;

		isReadingLogFile = true;
		
		clear();
		
		Platform.runLater(new Runnable()
		{
			@Override
			public void run()
			{
				updateButtons();
			}
		});
	}
	
	private void clear()
	{
		lastVmCommand = logParser.getVMCommand();
		lastSelectedMember = selectedMember;
		lastSelectedClass = selectedMetaClass;

		selectedMember = null;

		errorCount = 0;
		errorLog.clear();
		
		reportListSuggestions.clear();
		reportListEliminatedAllocations.clear();
		reportListElidedLocks.clear();
						
		Platform.runLater(new Runnable()
		{
			@Override
			public void run()
			{
				classMemberList.clear();
				
				StageManager.clearReportStages();
				
				codeCacheWalkerResult = null;
				
				if (triViewStage != null)
				{
					triViewStage.clear();
				}
				
				classTree.handleConfigUpdate(getConfig());

				updateButtons();

				classTree.clear();
				metaClassSelectedFromClassTree(null);

				textAreaLog.clear();
				
				refreshOnce();
			}
		});
	}

	@Override
	public void handleReadComplete()
	{
		log("Finished reading log file.");

		isReadingLogFile = false;

		buildSuggestions();

		buildEliminatedAllocationReport();

		buildElidedLocksReport();

		buildCodeCacheResult();

		Platform.runLater(new Runnable()
		{
			@Override
			public void run()
			{
				updateButtons();

				refreshOnce();
			}
		});

		logParser.discardParsedLogs();
	}

	private void buildSuggestions()
	{
		log("Finding code suggestions.");

		SuggestionWalker walker = new SuggestionWalker(logParser.getModel());

		reportListSuggestions = walker.getReports(new ScoreComparator());

		log("Found " + reportListSuggestions.size() + " code suggestions.");
	}

	private void buildEliminatedAllocationReport()
	{
		log("Finding eliminated allocations");

		EliminatedAllocationWalker walker = new EliminatedAllocationWalker(logParser.getModel());

		reportListEliminatedAllocations = walker.getReports(new ScoreComparator());

		log("Found " + reportListEliminatedAllocations.size() + "  eliminated allocations.");
	}

	private void buildElidedLocksReport()
	{
		log("Finding elided locks");

		ElidedLocksWalker walker = new ElidedLocksWalker(logParser.getModel());

		reportListElidedLocks = walker.getReports(new ScoreComparator());

		log("Found " + reportListElidedLocks.size() + " elided locks.");
	}

	private void buildCodeCacheResult()
	{
		CodeCacheEventWalker compilationWalker = new CodeCacheEventWalker(logParser.getModel());

		compilationWalker.walkCompilations();

		codeCacheWalkerResult = compilationWalker.getResult();
	}

	public CodeCacheWalkerResult getCodeCacheWalkerResult()
	{
		return codeCacheWalkerResult;
	}

	@Override
	public void handleError(final String title, final String body)
	{
		logger.error(title);

		Platform.runLater(new Runnable()
		{
			@Override
			public void run()
			{
				Dialogs.showOKDialog(JITWatchUI.this.stage, title, body);
			}
		});
	}

	private void stopParsing()
	{
		if (isReadingLogFile)
		{
			logParser.stopParsing();
			isReadingLogFile = false;
			updateButtons();

			if (jitLogFile != null)
			{
				log("Stopped parsing " + jitLogFile.getAbsolutePath());
			}
		}
	}

	@Override
	public JITWatchConfig getConfig()
	{
		return logParser.getConfig();
	}

	@Override
	public void start(final Stage stage)
	{
		StageManager.registerListener(this);

		this.stage = stage;

		stage.setOnCloseRequest(new EventHandler()
		{
			@Override
			public void handle(WindowEvent arg0)
			{
				StageManager.closeStage(stage);

				stopParsing();
			}
		});

		BorderPane borderPane = new BorderPane();

		Scene scene = UserInterfaceUtil.getScene(borderPane, WINDOW_WIDTH, WINDOW_HEIGHT);

		Button btnChooseWatchFile = new Button("Open Log");
		btnChooseWatchFile.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				stopParsing();
				chooseJITLog();
			}
		});

		btnStart = new Button("Start");
		btnStart.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				if (nothingMountedStage == null)
				{
					int classCount = getConfig().getConfiguredClassLocations().size();
					int sourceCount = getConfig().getSourceLocations().size();

					if (classCount == 0 && sourceCount == 0)
					{
						if (getConfig().isShowNothingMounted())
						{
							nothingMountedStage = new NothingMountedStage(JITWatchUI.this, getConfig());

							StageManager.addAndShow(JITWatchUI.this.stage, nothingMountedStage);

							startDelayedByConfig = true;
						}
					}
				}

				if (!startDelayedByConfig)
				{
					readLogFile();
				}
			}
		});

		btnStop = new Button("Stop");
		btnStop.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				stopParsing();
			}
		});

		btnConfigure = new Button("Config");
		btnConfigure.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				openConfigStage();
			}
		});

		btnTimeLine = new Button("Timeline");
		btnTimeLine.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				timeLineStage = new TimeLineStage(JITWatchUI.this);

				StageManager.addAndShow(JITWatchUI.this.stage, timeLineStage);

				btnTimeLine.setDisable(true);
			}
		});

		btnHisto = new Button("Histo");
		btnHisto.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				histoStage = new HistoStage(JITWatchUI.this);

				StageManager.addAndShow(JITWatchUI.this.stage, histoStage);

				btnHisto.setDisable(true);
			}
		});

		btnTopList = new Button("TopList");
		btnTopList.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				topListStage = new TopListStage(JITWatchUI.this);

				StageManager.addAndShow(JITWatchUI.this.stage, topListStage);

				btnTopList.setDisable(true);
			}
		});

		btnCodeCacheTimeline = new Button("Cache");
		btnCodeCacheTimeline.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				codeCacheTimelineStage = new CodeCacheStage(JITWatchUI.this);

				StageManager.addAndShow(JITWatchUI.this.stage, codeCacheTimelineStage);

				btnCodeCacheTimeline.setDisable(true);
			}
		});

		btnNMethods = new Button("NMethods");
		btnNMethods.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				codeCacheBlocksStage = new CodeCacheLayoutStage(JITWatchUI.this);

				StageManager.addAndShow(JITWatchUI.this.stage, codeCacheBlocksStage);

				btnNMethods.setDisable(true);

				codeCacheBlocksStage.redraw();

			}
		});

		btnTriView = new Button("TriView");
		btnTriView.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				if (selectedMember == null && selectedMetaClass != null)
				{
					selectedMember = selectedMetaClass.getFirstConstructor();
				}

				openTriView(selectedMember, false);
			}
		});

		btnReportSuggestions = new Button("Suggest");
		btnReportSuggestions.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				reportStageSuggestions = new ReportStage(JITWatchUI.this, "JITWatch Code Suggestions", ReportStageType.SUGGESTION,
						reportListSuggestions);

				StageManager.addAndShow(JITWatchUI.this.stage, reportStageSuggestions);

				btnReportSuggestions.setDisable(true);
			}
		});

		btnReportEliminatedAllocations = new Button("-Allocs");
		btnReportEliminatedAllocations.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				reportStageElminatedAllocations = new ReportStage(JITWatchUI.this, "JITWatch Eliminated Allocation Report",
						ReportStageType.ELIMINATED_ALLOCATION, reportListEliminatedAllocations);

				StageManager.addAndShow(JITWatchUI.this.stage, reportStageElminatedAllocations);

				btnReportEliminatedAllocations.setDisable(true);
			}
		});

		btnReportElidedLocks = new Button("-Locks");
		btnReportElidedLocks.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				reportStageElidedLocks = new ReportStage(JITWatchUI.this, "JITWatch Elided Lock Report",
						ReportStageType.ELIDED_LOCK, reportListElidedLocks);

				StageManager.addAndShow(JITWatchUI.this.stage, reportStageElidedLocks);

				btnReportElidedLocks.setDisable(true);
			}
		});

		btnOptimizedVirtualCalls = new Button("OVCs");
		btnOptimizedVirtualCalls.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				OptimizedVirtualCallVisitable optimizedVCallVisitable = new OptimizedVirtualCallVisitable();

				List optimizedVirtualCalls = optimizedVCallVisitable
						.buildOptimizedCalleeReport(logParser.getModel(), getConfig().getAllClassLocations());

				ovcStage = new OptimizedVirtualCallStage(JITWatchUI.this, optimizedVirtualCalls);

				StageManager.addAndShow(JITWatchUI.this.stage, ovcStage);

				btnOptimizedVirtualCalls.setDisable(true);
			}
		});

		btnSandbox = new Button("Sandbox");
		btnSandbox.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				openSandbox();
			}
		});

		btnErrorLog = new Button("Errors (0)");
		btnErrorLog.setStyle("-fx-padding: 2 6;");
		btnErrorLog.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				openTextViewer("Error Log", errorLog.toString(), false, false);
			}
		});

		btnStats = new Button("Stats");
		btnStats.setStyle("-fx-padding: 2 6;");
		btnStats.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				statsStage = new StatsStage(JITWatchUI.this);

				StageManager.addAndShow(JITWatchUI.this.stage, statsStage);

				btnStats.setDisable(true);
			}
		});
		
		btnReset = new Button("Reset");
		btnReset.setStyle("-fx-padding: 2 6;");
		btnReset.setOnAction(new EventHandler()
		{
			@Override
			public void handle(ActionEvent e)
			{
				logParser.reset();
				clear();
			}
		});

		lblHeap = new Label();

		lblVmVersion = new Label();

		StringBuilder vmBuilder = new StringBuilder();

		vmBuilder.append("VM is ");
		vmBuilder.append(Runtime.class.getPackage().getImplementationVendor());
		vmBuilder.append(C_SPACE);
		vmBuilder.append(Runtime.class.getPackage().getImplementationVersion());

		lblVmVersion.setText(vmBuilder.toString());

		lblTweakLog = new Label();

		int menuBarHeight = 40;
		int textAreaHeight = 100;
		int statusBarHeight = 25;

		HBox hboxTop = new HBox();

		hboxTop.setPadding(new Insets(10));
		hboxTop.setPrefHeight(menuBarHeight);
		hboxTop.setSpacing(10);
		hboxTop.getChildren().add(btnSandbox);
		hboxTop.getChildren().add(btnChooseWatchFile);
		hboxTop.getChildren().add(btnStart);
		hboxTop.getChildren().add(btnStop);
		hboxTop.getChildren().add(btnConfigure);
		hboxTop.getChildren().add(btnTimeLine);
		hboxTop.getChildren().add(btnHisto);
		hboxTop.getChildren().add(btnTopList);
		hboxTop.getChildren().add(btnCodeCacheTimeline);
		hboxTop.getChildren().add(btnNMethods);
		hboxTop.getChildren().add(btnTriView);
		hboxTop.getChildren().add(btnReportSuggestions);
		hboxTop.getChildren().add(btnReportEliminatedAllocations);
		hboxTop.getChildren().add(btnReportElidedLocks);
		hboxTop.getChildren().add(btnOptimizedVirtualCalls);

		compilationRowList = FXCollections.observableArrayList();
		compilationTable = CompilationTableBuilder.buildTableMemberAttributes(compilationRowList);
		compilationTable.setPlaceholder(new Text("Select a JIT-compiled class member to view compilations."));

		compilationTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener()
		{
			@Override
			public void changed(ObservableValue arg0, CompilationTableRow oldVal,
					CompilationTableRow newVal)
			{
				if (!selectedProgrammatically)
				{
					if (selectedMember != null && newVal != null)
					{
						selectedMember.setSelectedCompilation(newVal.getIndex());

						openTriView(selectedMember, true);

						refreshOnce();
					}
				}
			}
		});

		SplitPane spMethodInfo = new SplitPane();
		spMethodInfo.setOrientation(Orientation.VERTICAL);

		classMemberList = new ClassMemberList(this, getConfig());
		classMemberList.registerListener(this);

		spMethodInfo.getItems().add(classMemberList);
		spMethodInfo.getItems().add(compilationTable);

		classMemberList.prefHeightProperty().bind(scene.heightProperty());
		compilationTable.prefHeightProperty().bind(scene.heightProperty());

		classTree = new ClassTree(this, getConfig());
		classTree.prefWidthProperty().bind(scene.widthProperty());

		SplitPane spMain = new SplitPane();
		spMain.setOrientation(Orientation.VERTICAL);

		SplitPane spCentre = new SplitPane();
		spCentre.getItems().add(classTree);
		spCentre.getItems().add(spMethodInfo);
		spCentre.setDividerPositions(0.33, 0.67);

		textAreaLog = new TextArea();
		textAreaLog.setStyle("-fx-font-family:" + FONT_MONOSPACE_FAMILY + ";-fx-font-size:" + FONT_MONOSPACE_SIZE + "px");
		textAreaLog.setPrefHeight(textAreaHeight);

		log("Welcome to JITWatch by Chris Newland (@chriswhocodes on Twitter) and the AdoptOpenJDK project.\n");

		log("Please send feedback to our mailing list (https://groups.google.com/forum/#!forum/jitwatch) \nor come and find us on GitHub (https://github.com/AdoptOpenJDK/jitwatch).\n");

		log("Includes assembly reference from x86asm.net licenced under http://ref.x86asm.net/index.html#License\n");

		if (jitLogFile == null)
		{
			if (logParser instanceof HotSpotLogParser)
			{
				log("HotSpot mode. Choose a JIT log file or open the Sandbox");
			}
			else
			{
				log("J9 Mode. Choose a JIT log file (Sandbox only available for HotSpot)");
			}
		}
		else
		{
			log("Using JIT log file: " + jitLogFile.getAbsolutePath());
		}
		spMain.getItems().add(spCentre);
		spMain.getItems().add(textAreaLog);
		spMain.setDividerPositions(0.68, 0.32);

		HBox hboxBottom = new HBox();

		Region springLeft = new Region();
		Region springRight = new Region();

		final String labelStyle = "-fx-padding: 3 0 0 0;";

		HBox.setHgrow(springLeft, Priority.ALWAYS);
		HBox.setHgrow(springRight, Priority.ALWAYS);

		lblHeap.setStyle(labelStyle);
		lblVmVersion.setStyle(labelStyle);

		hboxBottom.setPadding(new Insets(4));
		hboxBottom.setPrefHeight(statusBarHeight);
		hboxBottom.setSpacing(4);
		hboxBottom.getChildren().add(lblHeap);
		hboxBottom.getChildren().add(btnErrorLog);
		hboxBottom.getChildren().add(btnStats);
		hboxBottom.getChildren().add(btnReset);
		hboxBottom.getChildren().add(springLeft);
		hboxBottom.getChildren().add(lblTweakLog);
		hboxBottom.getChildren().add(springRight);
		hboxBottom.getChildren().add(lblVmVersion);

		borderPane.setTop(hboxTop);
		borderPane.setCenter(spMain);
		borderPane.setBottom(hboxBottom);

		stage.setTitle("JITWatch - Just In Time Compilation Inspector");
		stage.setScene(scene);
		stage.show();

		int refreshMillis = 1000;

		final Duration oneFrameAmt = Duration.millis(refreshMillis);

		final KeyFrame oneFrame = new KeyFrame(oneFrameAmt, new EventHandler()
		{
			@Override
			public void handle(ActionEvent arg0)
			{
				refresh();
			}
		});

		Timeline timeline = new Timeline();
		timeline.setCycleCount(Animation.INDEFINITE);
		timeline.getKeyFrames().add(oneFrame);
		timeline.play();

		updateButtons();
	}

	void openConfigStage()
	{
		if (configStage == null)
		{
			configStage = new MainConfigStage(this, this, getConfig());

			StageManager.addAndShow(this.stage, configStage);

			btnConfigure.setDisable(true);
		}
	}

	@Override
	public void openTriView(IMetaMember member, boolean force)
	{
		openTriView(member, force, 0);
	}
	
	@Override
	public void openTriView(IMetaMember member, boolean force, int highlightBCI)
	{
		if (triViewStage == null)
		{
			triViewStage = new TriView(JITWatchUI.this, getConfig());

			StageManager.addAndShow(this.stage, triViewStage);

			btnTriView.setDisable(true);
		}

		if (member != null)
		{
			triViewStage.setMember(member, force, highlightBCI);
		}
	}

	public void openSandbox()
	{
		if (sandBoxStage == null)
		{
			sandBoxStage = new SandboxStage(this, this, logParser);

			StageManager.addAndShow(this.stage, sandBoxStage);

			btnSandbox.setDisable(true);
		}
	}

	@Override
	public void openBrowser(String title, String html, String stylesheet)
	{
		if (browserStage == null)
		{
			browserStage = new BrowserStage();

			StageManager.addAndShow(this.stage, browserStage);
		}

		browserStage.setContent(title, html, stylesheet);
	}

	public IReadOnlyJITDataModel getJITDataModel()
	{
		return logParser.getModel();
	}

	private void updateButtons()
	{
		if (!(logParser instanceof HotSpotLogParser))
		{
			btnSandbox.setDisable(true);
		}
		
		btnStart.setDisable(jitLogFile == null || isReadingLogFile);
		btnStop.setDisable(!isReadingLogFile);

		btnReportSuggestions.setText("Suggestions (" + reportListSuggestions.size() + S_CLOSE_PARENTHESES);
		btnReportEliminatedAllocations.setText("-Allocs (" + reportListEliminatedAllocations.size() + S_CLOSE_PARENTHESES);
		btnReportElidedLocks.setText("-Locks (" + reportListElidedLocks.size() + S_CLOSE_PARENTHESES);
	}

	public boolean focusTreeOnClass(MetaClass metaClass)
	{
		List path = metaClass.getTreePath();

		clearAndRefreshTreeView();

		TreeItem curNode = classTree.getRootItem();

		StringBuilder builtPath = new StringBuilder();

		int pathLength = path.size();
		int pos = 0;

		int rowsAbove = 0;

		boolean found = false;

		for (String part : path)
		{
			builtPath.append(part);

			String matching;

			found = false;

			if (pos++ == pathLength - 1)
			{
				matching = part;
			}
			else
			{
				matching = builtPath.toString();
			}

			for (TreeItem node : curNode.getChildren())
			{
				rowsAbove++;

				String nodeText = node.getValue().toString();

				if (matching.equals(nodeText) || (S_EMPTY.equals(matching) && DEFAULT_PACKAGE_NAME.equals(nodeText)))
				{
					builtPath.append(C_DOT);
					curNode = node;
					curNode.setExpanded(true);
					classTree.select(curNode);
					found = true;
					break;
				}
			}
		}

		if (found)
		{
			classTree.scrollTo(rowsAbove);
			lastSelectedClass = null;
		}

		return found;
	}

	public void focusTreeOnMember(IMetaMember member, boolean openTriView)
	{
		if (member != null)
		{
			MetaClass metaClass = member.getMetaClass();

			boolean found = focusTreeOnClass(metaClass);

			if (found)
			{
				classMemberList.selectMember(member);

				setSelectedMetaMember(member, openTriView);

				lastSelectedMember = null;
			}
		}
	}

	@Override
	public void openTextViewer(String title, String content, boolean lineNumbers, boolean highlighting)
	{
		TextViewerStage tvs = new TextViewerStage(this, title, content, lineNumbers, highlighting);
		StageManager.addAndShow(this.stage, tvs);
	}

	public void openTextViewer(String title, String content)
	{
		openTextViewer(title, content, false, false);
	}

	@Override
	public void openCompileChain(IMetaMember member)
	{
		if (member != null && member.isCompiled())
		{
			CompileChainWalker walker = new CompileChainWalker(logParser.getModel());

			CompileNode root = walker.buildCallTree(member.getSelectedCompilation());

			if (root != null)
			{
				CompileChainStage ccs = new CompileChainStage(this, root);

				StageManager.addAndShow(this.stage, ccs);
			}
			else
			{
				logger.error("Could not open CompileChain - root node was null");
			}
		}
	}

	@Override
	public void openOptmizedVCallReport(IMetaMember member)
	{
		if (member.isCompiled())
		{
			OptimizedVirtualCallFinder finder = new OptimizedVirtualCallFinder(logParser.getModel(),
					getConfig().getAllClassLocations());

			List optimizedVirtualCalls = finder.findOptimizedCalls(member);

			OptimizedVirtualCallStage ovcs = new OptimizedVirtualCallStage(this, optimizedVirtualCalls);

			StageManager.addAndShow(this.stage, ovcs);

		}
	}

	@Override
	public void openInlinedIntoReport(IMetaMember member)
	{
		if (member != null)
		{
			log("Finding inlined into reports for " + member.toStringUnqualifiedMethodName(true, true));

			InliningWalker walker = new InliningWalker(logParser.getModel(), member);

			List inlinedIntoMemberList = walker.getReports(new ScoreComparator());

			log("Found " + inlinedIntoMemberList.size() + " locations.");

			ReportStage inlinedIntoStage = new ReportStage(JITWatchUI.this,
					"Inlining report for callee " + member.toStringUnqualifiedMethodName(true, true), ReportStageType.INLINING,
					inlinedIntoMemberList);
			
			StageManager.addAndShow(JITWatchUI.this.stage, inlinedIntoStage);
		}
	}

	public void openJournalViewer(String title, IMetaMember member)
	{
		if (member.isCompiled())
		{
			JournalViewerStage jvs = new JournalViewerStage(this, title, member.getSelectedCompilation());
			StageManager.addAndShow(this.stage, jvs);
		}
	}

	private void chooseJITLog()
	{
		FileChooser fc = new FileChooser();
		fc.setTitle("Choose JIT log file");

		String osNameProperty = System.getProperty("os.name");

		// don't use ExtensionFilter on OSX due to JavaFX2 missing combo bug
		if (osNameProperty != null && !osNameProperty.toLowerCase().contains("mac"))
		{
			fc.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Log Files", "*.log"),
					new FileChooser.ExtensionFilter("All Files", "*.*"));
		}

		String searchDir = getConfig().getLastLogDir();

		if (searchDir == null)
		{
			searchDir = System.getProperty("user.dir");
		}

		File dirFile = new File(searchDir);

		if (!dirFile.exists() || !dirFile.isDirectory())
		{
			dirFile = new File(System.getProperty("user.dir"));
		}

		fc.setInitialDirectory(dirFile);

		File result = fc.showOpenDialog(stage);

		if (result != null)
		{
			setJITLogFile(result);

			JITWatchConfig config = getConfig();

			if (JITWatchConstants.S_PROFILE_SANDBOX.equals(config.getProfileName()))
			{
				logParser.getConfig().switchFromSandbox();
			}
		}
	}

	// Call from UI thread
	private void setJITLogFile(File logFile)
	{
		jitLogFile = logFile;

		getConfig().setLastLogDir(jitLogFile.getParent());
		getConfig().saveConfig();

		clearTextArea();
		log("Selected log file: " + jitLogFile.getAbsolutePath());

		log("\nUsing Config: " + getConfig().getProfileName());

		log("\nClick Start button to process the JIT log");
		updateButtons();

		refreshLog();
	}

	private boolean sameVmCommand()
	{
		boolean same = false;

		if (lastVmCommand != null && logParser.getVMCommand() != null)
		{
			same = lastVmCommand.equals(logParser.getVMCommand());

			if (!same)
			{
				// vm command known and not same so flush open node history
				classTree.clearOpenPackageHistory();
				lastVmCommand = null;
			}
		}

		return same;
	}

	@Override
	public void setSelectedMetaMember(IMetaMember member, boolean openTriView)
	{
		compilationRowList.clear();

		if (member == null)
		{
			return;
		}

		if (openTriView && triViewStage != null)
		{
			triViewStage.setMember(member, false);
		}

		selectedMember = member;

		for (Compilation compilation : member.getCompilations())
		{
			CompilationTableRow row = new CompilationTableRow(compilation);

			compilationRowList.add(row);
		}

		Compilation selectedCompilation = selectedMember.getSelectedCompilation();

		if (selectedCompilation != null)
		{
			compilationTable.getSelectionModel().clearAndSelect(selectedCompilation.getIndex());
		}

		refreshOnce();
	}

	public void setCompilationOnSelectedMember(IMetaMember member, int compilationIndex)
	{
		selectedProgrammatically = true;

		selectedMember = member;

		if (selectedMember != null)
		{
			selectedMember.setSelectedCompilation(compilationIndex);

			if (selectedMember.getSelectedCompilation() != null)
			{
				compilationTable.getSelectionModel().clearAndSelect(selectedMember.getSelectedCompilation().getIndex());
			}

			focusTreeOnMember(selectedMember, true);
		}

		selectedProgrammatically = false;
	}

	private void refreshOnce()
	{
		if (codeCacheBlocksStage != null)
		{
			codeCacheBlocksStage.redraw();
		}
	}

	private void refresh()
	{
		boolean sameVmCommandAsLastRun = sameVmCommand();

		if (repaintTree)
		{
			repaintTree = false;
			classTree.showTree(sameVmCommandAsLastRun);
		}

		if (sameVmCommandAsLastRun)
		{
			if (lastSelectedMember != null)
			{
				focusTreeOnMember(lastSelectedMember, true);
			}
			else if (lastSelectedClass != null)
			{
				focusTreeOnClass(lastSelectedClass);
			}
		}

		if (timeLineStage != null)
		{
			timeLineStage.redraw();
		}

		if (codeCacheTimelineStage != null)
		{
			codeCacheTimelineStage.redraw();
		}

		if (statsStage != null)
		{
			statsStage.redraw();
		}

		if (histoStage != null)
		{
			histoStage.redraw();
		}

		if (topListStage != null)
		{
			topListStage.redraw();
		}

		if (logBuffer.length() > 0)
		{
			refreshLog();
		}

		long totalMemory = runtime.totalMemory();
		long freeMemory = runtime.freeMemory();
		long usedMemory = totalMemory - freeMemory;

		long megabyte = 1024 * 1024;

		String heapString = "Heap: " + (usedMemory / megabyte) + S_SLASH + (totalMemory / megabyte) + "M";

		lblHeap.setText(heapString);

		btnErrorLog.setText("Errors (" + errorCount + S_CLOSE_PARENTHESES);
	}

	private void clearTextArea()
	{
		textAreaLog.clear();
	}

	private void refreshLog()
	{
		textAreaLog.appendText(logBuffer.toString());
		logBuffer.delete(0, logBuffer.length());
	}

	public IMetaMember getSelectedMember()
	{
		return selectedMember;
	}

	void clearAndRefreshTreeView()
	{
		selectedMember = null;
		selectedMetaClass = null;

		classTree.clear();
		classTree.showTree(sameVmCommand());
	}

	@Override
	public void handleStageClosed(Stage stage)
	{
		if (stage instanceof TimeLineStage)
		{
			btnTimeLine.setDisable(false);
			timeLineStage = null;
		}
		else if (stage instanceof StatsStage)
		{
			btnStats.setDisable(false);
			statsStage = null;
		}
		else if (stage instanceof HistoStage)
		{
			btnHisto.setDisable(false);
			histoStage = null;
		}
		else if (stage instanceof MainConfigStage)
		{
			btnConfigure.setDisable(false);
			configStage = null;

			if (startDelayedByConfig)
			{
				readLogFile();
			}
		}
		else if (stage instanceof TopListStage)
		{
			btnTopList.setDisable(false);
			topListStage = null;
		}
		else if (stage instanceof NothingMountedStage)
		{
			nothingMountedStage = null;

			if (configStage == null && startDelayedByConfig)
			{
				readLogFile();
			}
		}
		else if (stage instanceof CodeCacheStage)
		{
			btnCodeCacheTimeline.setDisable(false);
			codeCacheTimelineStage = null;
		}
		else if (stage instanceof CodeCacheLayoutStage)
		{
			btnNMethods.setDisable(false);
			codeCacheBlocksStage = null;
		}
		else if (stage instanceof TriView)
		{
			btnTriView.setDisable(false);
			triViewStage = null;
		}
		else if (stage instanceof ReportStage)
		{
			switch (((ReportStage) stage).getType())
			{
			case SUGGESTION:
				btnReportSuggestions.setDisable(false);
				reportStageSuggestions = null;
				break;
			case ELIMINATED_ALLOCATION:
				btnReportEliminatedAllocations.setDisable(false);
				reportStageElminatedAllocations = null;
				break;
			case ELIDED_LOCK:
				btnReportElidedLocks.setDisable(false);
				reportStageElidedLocks = null;
				break;
			case INLINING:
				break;
			}
		}
		else if (stage instanceof OptimizedVirtualCallStage)
		{
			btnOptimizedVirtualCalls.setDisable(false);
			ovcStage = null;
		}
		else if (stage instanceof BrowserStage)
		{
			browserStage = null;
		}
		else if (stage instanceof SandboxStage)
		{
			btnSandbox.setDisable(false);
			sandBoxStage = null;
		}
	}

	@Override
	public void handleJITEvent(JITEvent event)
	{
		log(event.toString());
		repaintTree = true;
	}

	@Override
	public void handleLogEntry(String entry)
	{
		log(entry);
	}

	@Override
	public void handleErrorEntry(String entry)
	{
		errorLog.addEntry(entry);
		errorCount++;
	}

	private void log(final String entry)
	{
		logBuffer.append(entry);
		logBuffer.append(S_NEWLINE);
	}

	void metaClassSelectedFromClassTree(MetaClass metaClass)
	{
		classMemberList.clearClassMembers();
		selectedMetaClass = metaClass;

		setSelectedMetaMember(null, true);

		if (metaClass == null)
		{
			// nothing selected
			return;
		}

		classMemberList.setMetaClass(metaClass);
	}

	public PackageManager getPackageManager()
	{
		return logParser.getModel().getPackageManager();
	}

	@Override
	public Stage getStageForDialog()
	{
		return stage;
	}
}