/* * Copyright (c) 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.codecache; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import org.adoptopenjdk.jitwatch.compilation.codecache.CodeCacheWalkerResult; import org.adoptopenjdk.jitwatch.model.CodeCacheEvent; import org.adoptopenjdk.jitwatch.model.Compilation; import org.adoptopenjdk.jitwatch.model.IMetaMember; import org.adoptopenjdk.jitwatch.ui.main.JITWatchUI; import org.adoptopenjdk.jitwatch.util.UserInterfaceUtil; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_EMPTY; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextField; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.shape.Rectangle; import javafx.scene.shape.Shape; import javafx.stage.Stage; import javafx.stage.StageStyle; public class CodeCacheLayoutStage extends Stage { protected JITWatchUI mainUI; private CodeCacheWalkerResult codeCacheData; private long lowAddress; private long highAddress; private long addressRange; private double width; private double height; private BorderPane borderPane; private Pane pane; private ScrollPane scrollPane; private NMethodInfo nMethodInfo; private double zoom; private Label lblNMethodCount; private Label lblLowAddress; private Label lblHighAddress; private Label lblAddressRange; private Button btnZoomIn; private Button btnZoomOut; private Button btnZoomReset; private Button btnAnimate; private CheckBox checkC1; private CheckBox checkC2; private boolean drawC1 = true; private boolean drawC2 = true; private TextField txtAnimationSeconds; private static final Color NOT_LATEST_COMPILATION = Color.rgb(96, 0, 0); private static final Color LATEST_COMPILATION = Color.rgb(0, 96, 0); private static final Color SELECTED_COMPILATION = Color.rgb(0, 255, 255); private static final Color OTHER_MEMBER_COMPILATIONS = Color.rgb(0, 0, 128); private long lastResizeRedraw = 0; private boolean redrawRequired = false; public CodeCacheLayoutStage(final JITWatchUI parent) { this.mainUI = parent; this.zoom = 1; initStyle(StageStyle.DECORATED); borderPane = new BorderPane(); scrollPane = new ScrollPane(); pane = new Pane(); pane.setStyle("-fx-background-color: #000000"); scrollPane.setContent(pane); nMethodInfo = new NMethodInfo(this); borderPane.setTop(scrollPane); VBox vBoxControls = buildControls(); borderPane.setCenter(vBoxControls); borderPane.setBottom(nMethodInfo); Scene scene = UserInterfaceUtil.getScene(borderPane, JITWatchUI.WINDOW_WIDTH, JITWatchUI.WINDOW_HEIGHT); scrollPane.prefWidthProperty().bind(scene.widthProperty()); scrollPane.prefHeightProperty().bind(scene.heightProperty().multiply(0.5)); scrollPane.setFitToHeight(true); pane.prefHeightProperty().bind(scrollPane.heightProperty()); nMethodInfo.prefHeightProperty().bind(scrollPane.heightProperty()); vBoxControls.prefWidthProperty().bind(scene.widthProperty()); class SceneResizeListener implements ChangeListener<Number> { @Override public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) { long now = System.currentTimeMillis(); if (now - lastResizeRedraw > 200) { redraw(); redrawRequired = true; lastResizeRedraw = now; new Thread(new Runnable() { @Override public void run() // off UI thread { try { Thread.sleep(500); // wait is off UI thread Platform.runLater(new Runnable() { @Override public void run() // on UI thread { if (redrawRequired) { redraw(); } } }); } catch (InterruptedException e) { } } }).start(); } else { redrawRequired = true; // we skipped a redraw } } } SceneResizeListener rl = new SceneResizeListener(); scene.widthProperty().addListener(rl); scene.heightProperty().addListener(rl); setTitle("Code Cache Layout"); setScene(scene); } private VBox buildControls() { VBox vBoxControls = new VBox(); vBoxControls.getChildren().addAll(buildControlButtons(), buildControlInfo()); return vBoxControls; } private HBox buildControlButtons() { HBox hboxControls = new HBox(); hboxControls.setSpacing(10); hboxControls.setAlignment(Pos.CENTER_LEFT); hboxControls.setPadding(new Insets(4, 0, 0, 8)); btnZoomIn = new Button("Zoom In"); btnZoomOut = new Button("Zoom Out"); btnZoomReset = new Button("Reset"); btnAnimate = new Button("Animate"); checkC1 = new CheckBox("Show C1"); checkC1.setSelected(drawC1); checkC1.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { drawC1 = checkC1.isSelected(); redraw(); } }); checkC2 = new CheckBox("Show C2"); checkC2.setSelected(drawC2); checkC2.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { drawC2 = checkC2.isSelected(); redraw(); } }); txtAnimationSeconds = new TextField("5"); txtAnimationSeconds.getStyleClass().add("readonly-label"); txtAnimationSeconds.setMaxWidth(40); btnZoomIn.setMinWidth(60); btnZoomOut.setMinWidth(60); btnZoomReset.setMinWidth(40); btnAnimate.setMinWidth(60); btnZoomIn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { zoom += 0.2; redraw(); } }); btnZoomOut.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { zoom -= 0.2; zoom = Math.max(zoom, 1); redraw(); } }); btnZoomReset.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { zoom = 1; redraw(); } }); btnAnimate.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { try { double animateOverSeconds = Double.parseDouble(txtAnimationSeconds.getText()); animate(animateOverSeconds); } catch (NumberFormatException nfe) { } } }); hboxControls.getChildren().addAll(checkC1, checkC2, btnZoomIn, btnZoomOut, btnZoomReset, btnAnimate, txtAnimationSeconds); return hboxControls; } private HBox buildControlInfo() { HBox hboxControls = new HBox(); hboxControls.setSpacing(10); hboxControls.setAlignment(Pos.CENTER_LEFT); hboxControls.setPadding(new Insets(12, 0, 0, 8)); int addressLabelWidth = 128; lblNMethodCount = new Label(); lblNMethodCount.setMinWidth(addressLabelWidth); lblNMethodCount.getStyleClass().add("readonly-label"); lblLowAddress = new Label(); lblLowAddress.setMinWidth(addressLabelWidth); lblLowAddress.getStyleClass().add("readonly-label"); lblHighAddress = new Label(); lblHighAddress.setMinWidth(addressLabelWidth); lblHighAddress.getStyleClass().add("readonly-label"); lblAddressRange = new Label(); lblAddressRange.setMinWidth(addressLabelWidth); lblAddressRange.getStyleClass().add("readonly-label"); hboxControls.getChildren().addAll(new Label("NMethods"), lblNMethodCount, new Label("Lowest Address"), lblLowAddress, new Label("Highest Address"), lblHighAddress, new Label("Address Range Size"), lblAddressRange); return hboxControls; } private boolean preDraw() { boolean ok = false; pane.getChildren().clear(); lblNMethodCount.setText(S_EMPTY); lblLowAddress.setText(S_EMPTY); lblHighAddress.setText(S_EMPTY); lblAddressRange.setText(S_EMPTY); nMethodInfo.clear(); codeCacheData = mainUI.getCodeCacheWalkerResult(); if (codeCacheData != null) { lowAddress = codeCacheData.getLowestAddress(); highAddress = codeCacheData.getHighestAddress(); addressRange = highAddress - lowAddress; addressRange *= 1.005; width = scrollPane.getWidth() * zoom; height = pane.getHeight(); pane.setPrefWidth(width); int eventCount = codeCacheData.getEvents().size(); lblNMethodCount.setText(Integer.toString(eventCount)); lblLowAddress.setText(Long.toHexString(lowAddress)); lblHighAddress.setText(Long.toHexString(highAddress)); lblAddressRange.setText(NumberFormat.getNumberInstance().format(addressRange)); ok = true; } return ok; } public void redraw() { if (!preDraw()) { return; } // long start = System.currentTimeMillis(); IMetaMember selectedMember = mainUI.getSelectedMember(); Compilation selectedCompilation = selectedMember == null ? null : selectedMember.getSelectedCompilation(); List<CodeCacheEvent> eventsOfSelectedMember = new ArrayList<>(); Color fillColour; for (CodeCacheEvent event : codeCacheData.getEvents()) { if (!showEvent(event)) { continue; } final Compilation eventCompilation = event.getCompilation(); final IMetaMember compilationMember = eventCompilation.getMember(); if (eventCompilation != null) { if (selectedMember != null && selectedMember.equals(compilationMember)) { eventsOfSelectedMember.add(event); } else { long addressOffset = event.getNativeAddress() - lowAddress; double scaledAddress = (double) addressOffset / (double) addressRange; double scaledSize = (double) event.getNativeCodeSize() / (double) addressRange; int latestCompilationIndex = compilationMember.getCompilations().size() - 1; if (eventCompilation.getIndex() == latestCompilationIndex) { fillColour = LATEST_COMPILATION; } else { fillColour = NOT_LATEST_COMPILATION; } double x = scaledAddress * width; double y = 0; double w = scaledSize * width; double h = height; plotCompilation(x, y, w, h, fillColour, compilationMember, eventCompilation.getIndex(), true); } } } for (CodeCacheEvent event : eventsOfSelectedMember) { long addressOffset = event.getNativeAddress() - lowAddress; double scaledAddress = (double) addressOffset / (double) addressRange; double scaledSize = (double) event.getNativeCodeSize() / (double) addressRange; double x = scaledAddress * width; double y = 0; double w = scaledSize * width; double h = height; final Compilation eventCompilation = event.getCompilation(); if (event.getCompilation().equals(selectedCompilation)) { fillColour = SELECTED_COMPILATION; nMethodInfo.setInfo(event, eventCompilation); } else { fillColour = OTHER_MEMBER_COMPILATIONS; } plotCompilation(x, y, w, h, fillColour, selectedMember, eventCompilation.getIndex(), true); plotMarker(x, h, w, selectedMember, eventCompilation.getIndex()); } // long stop = System.currentTimeMillis(); // System.out.println("redraw " + (stop - start)); } private boolean showEvent(CodeCacheEvent event) { boolean result = true; int level = event.getCompilationLevel(); if (!drawC1 && level >= 1 && level <= 3) { result = false; } if (!drawC2 && level == 4) { result = false; } return result; } private void plotMarker(double x, double h, double w, IMetaMember compilationMember, int compilationIndex) { double side = h * 0.04; double centre = x + w / 2; double top = h - side; double left = centre - side / 2; double right = centre + side / 2; double bottom = h; Polygon triangle = new Polygon(); triangle.getPoints().addAll(new Double[] { left, bottom, centre, top, right, bottom }); triangle.setFill(Color.WHITE); triangle.setStroke(Color.BLACK); attachListener(triangle, compilationMember, compilationIndex); pane.getChildren().add(triangle); } private void plotCompilation(double x, double y, double w, double h, Color fillColour, IMetaMember compilationMember, int compilationIndex, boolean clickHandler) { Rectangle rect = new Rectangle(x, y, w, h); rect.setFill(fillColour); if (clickHandler) { attachListener(rect, compilationMember, compilationIndex); } pane.getChildren().add(rect); } private void attachListener(Shape shape, final IMetaMember compilationMember, final int compilationIndex) { shape.setOnMouseClicked(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent arg0) { mainUI.setCompilationOnSelectedMember(compilationMember, compilationIndex); } }); } private void animate(double targetSeconds) { if (!preDraw()) { return; } final List<CodeCacheEvent> events = codeCacheData.getEvents(); final int eventCount = events.size(); double framesPerSecond = 60; double frameCount = targetSeconds * framesPerSecond; final double eventsPerFrame = (double) eventCount / frameCount; final double secondsPerEvent = targetSeconds / (double) eventCount; final double nanoSecondsPerEvent = 1_000_000_000 * secondsPerEvent; AnimationTimer timer = new AnimationTimer() { private int currentEvent; private long lastHandledAt = 0; @Override public void handle(long now) { double realEventsPerFrame = eventsPerFrame; if (eventsPerFrame < 1.0) { realEventsPerFrame = 1; if (now - lastHandledAt < nanoSecondsPerEvent) { return; } lastHandledAt = now; } for (int i = 0; i < realEventsPerFrame; i++) { if (currentEvent >= eventCount) { stop(); break; } CodeCacheEvent event = events.get(currentEvent++); if (!showEvent(event)) { continue; } final Compilation eventCompilation = event.getCompilation(); final IMetaMember compilationMember = eventCompilation.getMember(); if (eventCompilation != null) { long addressOffset = event.getNativeAddress() - lowAddress; double scaledAddress = (double) addressOffset / (double) addressRange; double scaledSize = (double) event.getNativeCodeSize() / (double) addressRange; int latestCompilationIndex = compilationMember.getCompilations().size() - 1; Color fillColour; if (eventCompilation.getIndex() == latestCompilationIndex) { fillColour = LATEST_COMPILATION; } else { fillColour = NOT_LATEST_COMPILATION; } double x = scaledAddress * width; double y = 0; double w = scaledSize * width; double h = height; plotCompilation(x, y, w, h, fillColour, compilationMember, eventCompilation.getIndex(), false); } } } @Override public void start() { super.start(); } @Override public void stop() { super.stop(); btnAnimate.setDisable(false); redraw(); } }; btnAnimate.setDisable(true); timer.start(); } void selectPrevCompilation() { IMetaMember selectedMember = mainUI.getSelectedMember(); if (selectedMember != null && selectedMember.getSelectedCompilation() != null) { int prevIndex = selectedMember.getSelectedCompilation().getIndex() - 1; mainUI.setCompilationOnSelectedMember(selectedMember, prevIndex); } } void selectNextCompilation() { IMetaMember selectedMember = mainUI.getSelectedMember(); if (selectedMember != null && selectedMember.getSelectedCompilation() != null) { int nextIndex = selectedMember.getSelectedCompilation().getIndex() + 1; mainUI.setCompilationOnSelectedMember(selectedMember, nextIndex); } } }