1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.traceview; 18 19 import org.eclipse.jface.resource.FontRegistry; 20 import org.eclipse.swt.SWT; 21 import org.eclipse.swt.custom.SashForm; 22 import org.eclipse.swt.events.MouseAdapter; 23 import org.eclipse.swt.events.MouseEvent; 24 import org.eclipse.swt.events.MouseMoveListener; 25 import org.eclipse.swt.events.MouseWheelListener; 26 import org.eclipse.swt.events.PaintEvent; 27 import org.eclipse.swt.events.PaintListener; 28 import org.eclipse.swt.graphics.Color; 29 import org.eclipse.swt.graphics.Cursor; 30 import org.eclipse.swt.graphics.FontData; 31 import org.eclipse.swt.graphics.GC; 32 import org.eclipse.swt.graphics.Image; 33 import org.eclipse.swt.graphics.Point; 34 import org.eclipse.swt.graphics.Rectangle; 35 import org.eclipse.swt.layout.FillLayout; 36 import org.eclipse.swt.layout.GridData; 37 import org.eclipse.swt.layout.GridLayout; 38 import org.eclipse.swt.widgets.Canvas; 39 import org.eclipse.swt.widgets.Composite; 40 import org.eclipse.swt.widgets.Display; 41 import org.eclipse.swt.widgets.Event; 42 import org.eclipse.swt.widgets.Listener; 43 import org.eclipse.swt.widgets.ScrollBar; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.Collections; 49 import java.util.Comparator; 50 import java.util.HashMap; 51 import java.util.Observable; 52 import java.util.Observer; 53 54 public class TimeLineView extends Composite implements Observer { 55 56 private HashMap<String, RowData> mRowByName; 57 private RowData[] mRows; 58 private Segment[] mSegments; 59 private HashMap<Integer, String> mThreadLabels; 60 private Timescale mTimescale; 61 private Surface mSurface; 62 private RowLabels mLabels; 63 private SashForm mSashForm; 64 private int mScrollOffsetY; 65 66 public static final int PixelsPerTick = 50; 67 private TickScaler mScaleInfo = new TickScaler(0, 0, 0, PixelsPerTick); 68 private static final int LeftMargin = 10; // blank space on left 69 private static final int RightMargin = 60; // blank space on right 70 71 private Color mColorBlack; 72 private Color mColorGray; 73 private Color mColorDarkGray; 74 private Color mColorForeground; 75 private Color mColorRowBack; 76 private Color mColorZoomSelection; 77 private FontRegistry mFontRegistry; 78 79 /** vertical height of drawn blocks in each row */ 80 private static final int rowHeight = 20; 81 82 /** the blank space between rows */ 83 private static final int rowYMargin = 12; 84 private static final int rowYMarginHalf = rowYMargin / 2; 85 86 /** total vertical space for row */ 87 private static final int rowYSpace = rowHeight + rowYMargin; 88 private static final int majorTickLength = 8; 89 private static final int minorTickLength = 4; 90 private static final int timeLineOffsetY = 58; 91 private static final int tickToFontSpacing = 2; 92 93 /** start of first row */ 94 private static final int topMargin = 90; 95 private int mMouseRow = -1; 96 private int mNumRows; 97 private int mStartRow; 98 private int mEndRow; 99 private TraceUnits mUnits; 100 private String mClockSource; 101 private boolean mHaveCpuTime; 102 private boolean mHaveRealTime; 103 private int mSmallFontWidth; 104 private int mSmallFontHeight; 105 private SelectionController mSelectionController; 106 private MethodData mHighlightMethodData; 107 private Call mHighlightCall; 108 private static final int MinInclusiveRange = 3; 109 110 /** Setting the fonts looks good on Linux but bad on Macs */ 111 private boolean mSetFonts = false; 112 113 public static interface Block { 114 public String getName(); 115 public MethodData getMethodData(); 116 public long getStartTime(); 117 public long getEndTime(); 118 public Color getColor(); 119 public double addWeight(int x, int y, double weight); 120 public void clearWeight(); 121 public long getExclusiveCpuTime(); 122 public long getInclusiveCpuTime(); 123 public long getExclusiveRealTime(); 124 public long getInclusiveRealTime(); 125 public boolean isContextSwitch(); 126 public boolean isIgnoredBlock(); 127 public Block getParentBlock(); 128 } 129 130 public static interface Row { 131 public int getId(); 132 public String getName(); 133 } 134 135 public static class Record { 136 Row row; 137 Block block; 138 139 public Record(Row row, Block block) { 140 this.row = row; 141 this.block = block; 142 } 143 } 144 145 public TimeLineView(Composite parent, TraceReader reader, 146 SelectionController selectionController) { 147 super(parent, SWT.NONE); 148 mRowByName = new HashMap<String, RowData>(); 149 this.mSelectionController = selectionController; 150 selectionController.addObserver(this); 151 mUnits = reader.getTraceUnits(); 152 mClockSource = reader.getClockSource(); 153 mHaveCpuTime = reader.haveCpuTime(); 154 mHaveRealTime = reader.haveRealTime(); 155 mThreadLabels = reader.getThreadLabels(); 156 157 Display display = getDisplay(); 158 mColorGray = display.getSystemColor(SWT.COLOR_GRAY); 159 mColorDarkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY); 160 mColorBlack = display.getSystemColor(SWT.COLOR_BLACK); 161 // mColorBackground = display.getSystemColor(SWT.COLOR_WHITE); 162 mColorForeground = display.getSystemColor(SWT.COLOR_BLACK); 163 mColorRowBack = new Color(display, 240, 240, 255); 164 mColorZoomSelection = new Color(display, 230, 230, 230); 165 166 mFontRegistry = new FontRegistry(display); 167 mFontRegistry.put("small", //$NON-NLS-1$ 168 new FontData[] { new FontData("Arial", 8, SWT.NORMAL) }); //$NON-NLS-1$ 169 mFontRegistry.put("courier8", //$NON-NLS-1$ 170 new FontData[] { new FontData("Courier New", 8, SWT.BOLD) }); //$NON-NLS-1$ 171 mFontRegistry.put("medium", //$NON-NLS-1$ 172 new FontData[] { new FontData("Courier New", 10, SWT.NORMAL) }); //$NON-NLS-1$ 173 174 Image image = new Image(display, new Rectangle(100, 100, 100, 100)); 175 GC gc = new GC(image); 176 if (mSetFonts) { 177 gc.setFont(mFontRegistry.get("small")); //$NON-NLS-1$ 178 } 179 mSmallFontWidth = gc.getFontMetrics().getAverageCharWidth(); 180 mSmallFontHeight = gc.getFontMetrics().getHeight(); 181 182 image.dispose(); 183 gc.dispose(); 184 185 setLayout(new FillLayout()); 186 187 // Create a sash form for holding two canvas views, one for the 188 // thread labels and one for the thread timeline. 189 mSashForm = new SashForm(this, SWT.HORIZONTAL); 190 mSashForm.setBackground(mColorGray); 191 mSashForm.SASH_WIDTH = 3; 192 193 // Create a composite for the left side of the sash 194 Composite composite = new Composite(mSashForm, SWT.NONE); 195 GridLayout layout = new GridLayout(1, true /* make columns equal width */); 196 layout.marginHeight = 0; 197 layout.marginWidth = 0; 198 layout.verticalSpacing = 1; 199 composite.setLayout(layout); 200 201 // Create a blank corner space in the upper left corner 202 BlankCorner corner = new BlankCorner(composite); 203 GridData gridData = new GridData(GridData.FILL_HORIZONTAL); 204 gridData.heightHint = topMargin; 205 corner.setLayoutData(gridData); 206 207 // Add the thread labels below the blank corner. 208 mLabels = new RowLabels(composite); 209 gridData = new GridData(GridData.FILL_BOTH); 210 mLabels.setLayoutData(gridData); 211 212 // Create another composite for the right side of the sash 213 composite = new Composite(mSashForm, SWT.NONE); 214 layout = new GridLayout(1, true /* make columns equal width */); 215 layout.marginHeight = 0; 216 layout.marginWidth = 0; 217 layout.verticalSpacing = 1; 218 composite.setLayout(layout); 219 220 mTimescale = new Timescale(composite); 221 gridData = new GridData(GridData.FILL_HORIZONTAL); 222 gridData.heightHint = topMargin; 223 mTimescale.setLayoutData(gridData); 224 225 mSurface = new Surface(composite); 226 gridData = new GridData(GridData.FILL_BOTH); 227 mSurface.setLayoutData(gridData); 228 mSashForm.setWeights(new int[] { 1, 5 }); 229 230 final ScrollBar vBar = mSurface.getVerticalBar(); 231 vBar.addListener(SWT.Selection, new Listener() { 232 @Override 233 public void handleEvent(Event e) { 234 mScrollOffsetY = vBar.getSelection(); 235 Point dim = mSurface.getSize(); 236 int newScrollOffsetY = computeVisibleRows(dim.y); 237 if (newScrollOffsetY != mScrollOffsetY) { 238 mScrollOffsetY = newScrollOffsetY; 239 vBar.setSelection(newScrollOffsetY); 240 } 241 mLabels.redraw(); 242 mSurface.redraw(); 243 } 244 }); 245 246 final ScrollBar hBar = mSurface.getHorizontalBar(); 247 hBar.addListener(SWT.Selection, new Listener() { 248 @Override 249 public void handleEvent(Event e) { 250 mSurface.setScaleFromHorizontalScrollBar(hBar.getSelection()); 251 mSurface.redraw(); 252 } 253 }); 254 255 mSurface.addListener(SWT.Resize, new Listener() { 256 @Override 257 public void handleEvent(Event e) { 258 Point dim = mSurface.getSize(); 259 260 // If we don't need the scroll bar then don't display it. 261 if (dim.y >= mNumRows * rowYSpace) { 262 vBar.setVisible(false); 263 } else { 264 vBar.setVisible(true); 265 } 266 int newScrollOffsetY = computeVisibleRows(dim.y); 267 if (newScrollOffsetY != mScrollOffsetY) { 268 mScrollOffsetY = newScrollOffsetY; 269 vBar.setSelection(newScrollOffsetY); 270 } 271 272 int spaceNeeded = mNumRows * rowYSpace; 273 vBar.setMaximum(spaceNeeded); 274 vBar.setThumb(dim.y); 275 276 mLabels.redraw(); 277 mSurface.redraw(); 278 } 279 }); 280 281 mSurface.addMouseListener(new MouseAdapter() { 282 @Override 283 public void mouseUp(MouseEvent me) { 284 mSurface.mouseUp(me); 285 } 286 287 @Override 288 public void mouseDown(MouseEvent me) { 289 mSurface.mouseDown(me); 290 } 291 292 @Override 293 public void mouseDoubleClick(MouseEvent me) { 294 mSurface.mouseDoubleClick(me); 295 } 296 }); 297 298 mSurface.addMouseMoveListener(new MouseMoveListener() { 299 @Override 300 public void mouseMove(MouseEvent me) { 301 mSurface.mouseMove(me); 302 } 303 }); 304 305 mSurface.addMouseWheelListener(new MouseWheelListener() { 306 @Override 307 public void mouseScrolled(MouseEvent me) { 308 mSurface.mouseScrolled(me); 309 } 310 }); 311 312 mTimescale.addMouseListener(new MouseAdapter() { 313 @Override 314 public void mouseUp(MouseEvent me) { 315 mTimescale.mouseUp(me); 316 } 317 318 @Override 319 public void mouseDown(MouseEvent me) { 320 mTimescale.mouseDown(me); 321 } 322 323 @Override 324 public void mouseDoubleClick(MouseEvent me) { 325 mTimescale.mouseDoubleClick(me); 326 } 327 }); 328 329 mTimescale.addMouseMoveListener(new MouseMoveListener() { 330 @Override 331 public void mouseMove(MouseEvent me) { 332 mTimescale.mouseMove(me); 333 } 334 }); 335 336 mLabels.addMouseMoveListener(new MouseMoveListener() { 337 @Override 338 public void mouseMove(MouseEvent me) { 339 mLabels.mouseMove(me); 340 } 341 }); 342 343 setData(reader.getThreadTimeRecords()); 344 } 345 346 @Override 347 public void update(Observable objservable, Object arg) { 348 // Ignore updates from myself 349 if (arg == "TimeLineView") //$NON-NLS-1$ 350 return; 351 // System.out.printf("timeline update from %s\n", arg); 352 boolean foundHighlight = false; 353 ArrayList<Selection> selections; 354 selections = mSelectionController.getSelections(); 355 for (Selection selection : selections) { 356 Selection.Action action = selection.getAction(); 357 if (action != Selection.Action.Highlight) 358 continue; 359 String name = selection.getName(); 360 // System.out.printf(" timeline highlight %s from %s\n", name, arg); 361 if (name == "MethodData") { //$NON-NLS-1$ 362 foundHighlight = true; 363 mHighlightMethodData = (MethodData) selection.getValue(); 364 // System.out.printf(" method %s\n", 365 // highlightMethodData.getName()); 366 mHighlightCall = null; 367 startHighlighting(); 368 } else if (name == "Call") { //$NON-NLS-1$ 369 foundHighlight = true; 370 mHighlightCall = (Call) selection.getValue(); 371 // System.out.printf(" call %s\n", highlightCall.getName()); 372 mHighlightMethodData = null; 373 startHighlighting(); 374 } 375 } 376 if (foundHighlight == false) 377 mSurface.clearHighlights(); 378 } 379 380 public void setData(ArrayList<Record> records) { 381 if (records == null) 382 records = new ArrayList<Record>(); 383 384 if (false) { 385 System.out.println("TimelineView() list of records:"); //$NON-NLS-1$ 386 for (Record r : records) { 387 System.out.printf("row '%s' block '%s' [%d, %d]\n", r.row //$NON-NLS-1$ 388 .getName(), r.block.getName(), r.block.getStartTime(), 389 r.block.getEndTime()); 390 if (r.block.getStartTime() > r.block.getEndTime()) { 391 System.err.printf("Error: block startTime > endTime\n"); //$NON-NLS-1$ 392 System.exit(1); 393 } 394 } 395 } 396 397 // Sort the records into increasing start time, and decreasing end time 398 Collections.sort(records, new Comparator<Record>() { 399 @Override 400 public int compare(Record r1, Record r2) { 401 long start1 = r1.block.getStartTime(); 402 long start2 = r2.block.getStartTime(); 403 if (start1 > start2) 404 return 1; 405 if (start1 < start2) 406 return -1; 407 408 // The start times are the same, so compare the end times 409 long end1 = r1.block.getEndTime(); 410 long end2 = r2.block.getEndTime(); 411 if (end1 > end2) 412 return -1; 413 if (end1 < end2) 414 return 1; 415 416 return 0; 417 } 418 }); 419 420 ArrayList<Segment> segmentList = new ArrayList<Segment>(); 421 422 // The records are sorted into increasing start time, 423 // so the minimum start time is the start time of the first record. 424 double minVal = 0; 425 if (records.size() > 0) 426 minVal = records.get(0).block.getStartTime(); 427 428 // Sum the time spent in each row and block, and 429 // keep track of the maximum end time. 430 double maxVal = 0; 431 for (Record rec : records) { 432 Row row = rec.row; 433 Block block = rec.block; 434 if (block.isIgnoredBlock()) { 435 continue; 436 } 437 438 String rowName = row.getName(); 439 RowData rd = mRowByName.get(rowName); 440 if (rd == null) { 441 rd = new RowData(row); 442 mRowByName.put(rowName, rd); 443 } 444 long blockStartTime = block.getStartTime(); 445 long blockEndTime = block.getEndTime(); 446 if (blockEndTime > rd.mEndTime) { 447 long start = Math.max(blockStartTime, rd.mEndTime); 448 rd.mElapsed += blockEndTime - start; 449 rd.mEndTime = blockEndTime; 450 } 451 if (blockEndTime > maxVal) 452 maxVal = blockEndTime; 453 454 // Keep track of nested blocks by using a stack (for each row). 455 // Create a Segment object for each visible part of a block. 456 Block top = rd.top(); 457 if (top == null) { 458 rd.push(block); 459 continue; 460 } 461 462 long topStartTime = top.getStartTime(); 463 long topEndTime = top.getEndTime(); 464 if (topEndTime >= blockStartTime) { 465 // Add this segment if it has a non-zero elapsed time. 466 if (topStartTime < blockStartTime) { 467 Segment segment = new Segment(rd, top, topStartTime, 468 blockStartTime); 469 segmentList.add(segment); 470 } 471 472 // If this block starts where the previous (top) block ends, 473 // then pop off the top block. 474 if (topEndTime == blockStartTime) 475 rd.pop(); 476 rd.push(block); 477 } else { 478 // We may have to pop several frames here. 479 popFrames(rd, top, blockStartTime, segmentList); 480 rd.push(block); 481 } 482 } 483 484 // Clean up the stack of each row 485 for (RowData rd : mRowByName.values()) { 486 Block top = rd.top(); 487 popFrames(rd, top, Integer.MAX_VALUE, segmentList); 488 } 489 490 mSurface.setRange(minVal, maxVal); 491 mSurface.setLimitRange(minVal, maxVal); 492 493 // Sort the rows into decreasing elapsed time 494 Collection<RowData> rv = mRowByName.values(); 495 mRows = rv.toArray(new RowData[rv.size()]); 496 Arrays.sort(mRows, new Comparator<RowData>() { 497 @Override 498 public int compare(RowData rd1, RowData rd2) { 499 return (int) (rd2.mElapsed - rd1.mElapsed); 500 } 501 }); 502 503 // Assign ranks to the sorted rows 504 for (int ii = 0; ii < mRows.length; ++ii) { 505 mRows[ii].mRank = ii; 506 } 507 508 // Compute the number of rows with data 509 mNumRows = 0; 510 for (int ii = 0; ii < mRows.length; ++ii) { 511 if (mRows[ii].mElapsed == 0) 512 break; 513 mNumRows += 1; 514 } 515 516 // Sort the blocks into increasing rows, and within rows into 517 // increasing start values. 518 mSegments = segmentList.toArray(new Segment[segmentList.size()]); 519 Arrays.sort(mSegments, new Comparator<Segment>() { 520 @Override 521 public int compare(Segment bd1, Segment bd2) { 522 RowData rd1 = bd1.mRowData; 523 RowData rd2 = bd2.mRowData; 524 int diff = rd1.mRank - rd2.mRank; 525 if (diff == 0) { 526 long timeDiff = bd1.mStartTime - bd2.mStartTime; 527 if (timeDiff == 0) 528 timeDiff = bd1.mEndTime - bd2.mEndTime; 529 return (int) timeDiff; 530 } 531 return diff; 532 } 533 }); 534 535 if (false) { 536 for (Segment segment : mSegments) { 537 System.out.printf("seg '%s' [%6d, %6d] %s\n", 538 segment.mRowData.mName, segment.mStartTime, 539 segment.mEndTime, segment.mBlock.getName()); 540 if (segment.mStartTime > segment.mEndTime) { 541 System.err.printf("Error: segment startTime > endTime\n"); 542 System.exit(1); 543 } 544 } 545 } 546 } 547 548 private static void popFrames(RowData rd, Block top, long startTime, 549 ArrayList<Segment> segmentList) { 550 long topEndTime = top.getEndTime(); 551 long lastEndTime = top.getStartTime(); 552 while (topEndTime <= startTime) { 553 if (topEndTime > lastEndTime) { 554 Segment segment = new Segment(rd, top, lastEndTime, topEndTime); 555 segmentList.add(segment); 556 lastEndTime = topEndTime; 557 } 558 rd.pop(); 559 top = rd.top(); 560 if (top == null) 561 return; 562 topEndTime = top.getEndTime(); 563 } 564 565 // If we get here, then topEndTime > startTime 566 if (lastEndTime < startTime) { 567 Segment bd = new Segment(rd, top, lastEndTime, startTime); 568 segmentList.add(bd); 569 } 570 } 571 572 private class RowLabels extends Canvas { 573 574 /** The space between the row label and the sash line */ 575 private static final int labelMarginX = 2; 576 577 public RowLabels(Composite parent) { 578 super(parent, SWT.NO_BACKGROUND); 579 addPaintListener(new PaintListener() { 580 @Override 581 public void paintControl(PaintEvent pe) { 582 draw(pe.display, pe.gc); 583 } 584 }); 585 } 586 587 private void mouseMove(MouseEvent me) { 588 int rownum = (me.y + mScrollOffsetY) / rowYSpace; 589 if (mMouseRow != rownum) { 590 mMouseRow = rownum; 591 redraw(); 592 mSurface.redraw(); 593 } 594 } 595 596 private void draw(Display display, GC gc) { 597 if (mSegments.length == 0) { 598 // gc.setBackground(colorBackground); 599 // gc.fillRectangle(getBounds()); 600 return; 601 } 602 Point dim = getSize(); 603 604 // Create an image for double-buffering 605 Image image = new Image(display, getBounds()); 606 607 // Set up the off-screen gc 608 GC gcImage = new GC(image); 609 if (mSetFonts) 610 gcImage.setFont(mFontRegistry.get("medium")); //$NON-NLS-1$ 611 612 if (mNumRows > 2) { 613 // Draw the row background stripes 614 gcImage.setBackground(mColorRowBack); 615 for (int ii = 1; ii < mNumRows; ii += 2) { 616 RowData rd = mRows[ii]; 617 int y1 = rd.mRank * rowYSpace - mScrollOffsetY; 618 gcImage.fillRectangle(0, y1, dim.x, rowYSpace); 619 } 620 } 621 622 // Draw the row labels 623 int offsetY = rowYMarginHalf - mScrollOffsetY; 624 for (int ii = mStartRow; ii <= mEndRow; ++ii) { 625 RowData rd = mRows[ii]; 626 int y1 = rd.mRank * rowYSpace + offsetY; 627 Point extent = gcImage.stringExtent(rd.mName); 628 int x1 = dim.x - extent.x - labelMarginX; 629 gcImage.drawString(rd.mName, x1, y1, true); 630 } 631 632 // Draw a highlight box on the row where the mouse is. 633 if (mMouseRow >= mStartRow && mMouseRow <= mEndRow) { 634 gcImage.setForeground(mColorGray); 635 int y1 = mMouseRow * rowYSpace - mScrollOffsetY; 636 gcImage.drawRectangle(0, y1, dim.x, rowYSpace); 637 } 638 639 // Draw the off-screen buffer to the screen 640 gc.drawImage(image, 0, 0); 641 642 // Clean up 643 image.dispose(); 644 gcImage.dispose(); 645 } 646 } 647 648 private class BlankCorner extends Canvas { 649 public BlankCorner(Composite parent) { 650 //super(parent, SWT.NO_BACKGROUND); 651 super(parent, SWT.NONE); 652 addPaintListener(new PaintListener() { 653 @Override 654 public void paintControl(PaintEvent pe) { 655 draw(pe.display, pe.gc); 656 } 657 }); 658 } 659 660 private void draw(Display display, GC gc) { 661 // Create a blank image and draw it to the canvas 662 Image image = new Image(display, getBounds()); 663 gc.drawImage(image, 0, 0); 664 665 // Clean up 666 image.dispose(); 667 } 668 } 669 670 private class Timescale extends Canvas { 671 private Point mMouse = new Point(LeftMargin, 0); 672 private Cursor mZoomCursor; 673 private String mMethodName = null; 674 private Color mMethodColor = null; 675 private String mDetails; 676 private int mMethodStartY; 677 private int mDetailsStartY; 678 private int mMarkStartX; 679 private int mMarkEndX; 680 681 /** The space between the colored block and the method name */ 682 private static final int METHOD_BLOCK_MARGIN = 10; 683 684 public Timescale(Composite parent) { 685 //super(parent, SWT.NO_BACKGROUND); 686 super(parent, SWT.NONE); 687 Display display = getDisplay(); 688 mZoomCursor = new Cursor(display, SWT.CURSOR_SIZEWE); 689 setCursor(mZoomCursor); 690 mMethodStartY = mSmallFontHeight + 1; 691 mDetailsStartY = mMethodStartY + mSmallFontHeight + 1; 692 addPaintListener(new PaintListener() { 693 @Override 694 public void paintControl(PaintEvent pe) { 695 draw(pe.display, pe.gc); 696 } 697 }); 698 } 699 700 public void setVbarPosition(int x) { 701 mMouse.x = x; 702 } 703 704 public void setMarkStart(int x) { 705 mMarkStartX = x; 706 } 707 708 public void setMarkEnd(int x) { 709 mMarkEndX = x; 710 } 711 712 public void setMethodName(String name) { 713 mMethodName = name; 714 } 715 716 public void setMethodColor(Color color) { 717 mMethodColor = color; 718 } 719 720 public void setDetails(String details) { 721 mDetails = details; 722 } 723 724 private void mouseMove(MouseEvent me) { 725 me.y = -1; 726 mSurface.mouseMove(me); 727 } 728 729 private void mouseDown(MouseEvent me) { 730 mSurface.startScaling(me.x); 731 mSurface.redraw(); 732 } 733 734 private void mouseUp(MouseEvent me) { 735 mSurface.stopScaling(me.x); 736 } 737 738 private void mouseDoubleClick(MouseEvent me) { 739 mSurface.resetScale(); 740 mSurface.redraw(); 741 } 742 743 private void draw(Display display, GC gc) { 744 Point dim = getSize(); 745 746 // Create an image for double-buffering 747 Image image = new Image(display, getBounds()); 748 749 // Set up the off-screen gc 750 GC gcImage = new GC(image); 751 if (mSetFonts) 752 gcImage.setFont(mFontRegistry.get("medium")); //$NON-NLS-1$ 753 754 if (mSurface.drawingSelection()) { 755 drawSelection(display, gcImage); 756 } 757 758 drawTicks(display, gcImage); 759 760 // Draw the vertical bar where the mouse is 761 gcImage.setForeground(mColorDarkGray); 762 gcImage.drawLine(mMouse.x, timeLineOffsetY, mMouse.x, dim.y); 763 764 // Draw the current millseconds 765 drawTickLegend(display, gcImage); 766 767 // Draw the method name and color, if needed 768 drawMethod(display, gcImage); 769 770 // Draw the details, if needed 771 drawDetails(display, gcImage); 772 773 // Draw the off-screen buffer to the screen 774 gc.drawImage(image, 0, 0); 775 776 // Clean up 777 image.dispose(); 778 gcImage.dispose(); 779 } 780 781 private void drawSelection(Display display, GC gc) { 782 Point dim = getSize(); 783 gc.setForeground(mColorGray); 784 gc.drawLine(mMarkStartX, timeLineOffsetY, mMarkStartX, dim.y); 785 gc.setBackground(mColorZoomSelection); 786 int x, width; 787 if (mMarkStartX < mMarkEndX) { 788 x = mMarkStartX; 789 width = mMarkEndX - mMarkStartX; 790 } else { 791 x = mMarkEndX; 792 width = mMarkStartX - mMarkEndX; 793 } 794 if (width > 1) { 795 gc.fillRectangle(x, timeLineOffsetY, width, dim.y); 796 } 797 } 798 799 private void drawTickLegend(Display display, GC gc) { 800 int mouseX = mMouse.x - LeftMargin; 801 double mouseXval = mScaleInfo.pixelToValue(mouseX); 802 String info = mUnits.labelledString(mouseXval); 803 gc.setForeground(mColorForeground); 804 gc.drawString(info, LeftMargin + 2, 1, true); 805 806 // Display the maximum data value 807 double maxVal = mScaleInfo.getMaxVal(); 808 info = mUnits.labelledString(maxVal); 809 if (mClockSource != null) { 810 info = String.format(" max %s (%s)", info, mClockSource); //$NON-NLS-1$ 811 } else { 812 info = String.format(" max %s ", info); //$NON-NLS-1$ 813 } 814 Point extent = gc.stringExtent(info); 815 Point dim = getSize(); 816 int x1 = dim.x - RightMargin - extent.x; 817 gc.drawString(info, x1, 1, true); 818 } 819 820 private void drawMethod(Display display, GC gc) { 821 if (mMethodName == null) { 822 return; 823 } 824 825 int x1 = LeftMargin; 826 int y1 = mMethodStartY; 827 gc.setBackground(mMethodColor); 828 int width = 2 * mSmallFontWidth; 829 gc.fillRectangle(x1, y1, width, mSmallFontHeight); 830 x1 += width + METHOD_BLOCK_MARGIN; 831 gc.drawString(mMethodName, x1, y1, true); 832 } 833 834 private void drawDetails(Display display, GC gc) { 835 if (mDetails == null) { 836 return; 837 } 838 839 int x1 = LeftMargin + 2 * mSmallFontWidth + METHOD_BLOCK_MARGIN; 840 int y1 = mDetailsStartY; 841 gc.drawString(mDetails, x1, y1, true); 842 } 843 844 private void drawTicks(Display display, GC gc) { 845 Point dim = getSize(); 846 int y2 = majorTickLength + timeLineOffsetY; 847 int y3 = minorTickLength + timeLineOffsetY; 848 int y4 = y2 + tickToFontSpacing; 849 gc.setForeground(mColorForeground); 850 gc.drawLine(LeftMargin, timeLineOffsetY, dim.x - RightMargin, 851 timeLineOffsetY); 852 double minVal = mScaleInfo.getMinVal(); 853 double maxVal = mScaleInfo.getMaxVal(); 854 double minMajorTick = mScaleInfo.getMinMajorTick(); 855 double tickIncrement = mScaleInfo.getTickIncrement(); 856 double minorTickIncrement = tickIncrement / 5; 857 double pixelsPerRange = mScaleInfo.getPixelsPerRange(); 858 859 // Draw the initial minor ticks, if any 860 if (minVal < minMajorTick) { 861 gc.setForeground(mColorGray); 862 double xMinor = minMajorTick; 863 for (int ii = 1; ii <= 4; ++ii) { 864 xMinor -= minorTickIncrement; 865 if (xMinor < minVal) 866 break; 867 int x1 = LeftMargin 868 + (int) (0.5 + (xMinor - minVal) * pixelsPerRange); 869 gc.drawLine(x1, timeLineOffsetY, x1, y3); 870 } 871 } 872 873 if (tickIncrement <= 10) { 874 // TODO avoid rendering the loop when tickIncrement is invalid. It can be zero 875 // or too small. 876 // System.out.println(String.format("Timescale.drawTicks error: tickIncrement=%1f", tickIncrement)); 877 return; 878 } 879 for (double x = minMajorTick; x <= maxVal; x += tickIncrement) { 880 int x1 = LeftMargin 881 + (int) (0.5 + (x - minVal) * pixelsPerRange); 882 883 // Draw a major tick 884 gc.setForeground(mColorForeground); 885 gc.drawLine(x1, timeLineOffsetY, x1, y2); 886 if (x > maxVal) 887 break; 888 889 // Draw the tick text 890 String tickString = mUnits.valueOf(x); 891 gc.drawString(tickString, x1, y4, true); 892 893 // Draw 4 minor ticks between major ticks 894 gc.setForeground(mColorGray); 895 double xMinor = x; 896 for (int ii = 1; ii <= 4; ii++) { 897 xMinor += minorTickIncrement; 898 if (xMinor > maxVal) 899 break; 900 x1 = LeftMargin 901 + (int) (0.5 + (xMinor - minVal) * pixelsPerRange); 902 gc.drawLine(x1, timeLineOffsetY, x1, y3); 903 } 904 } 905 } 906 } 907 908 private static enum GraphicsState { 909 Normal, Marking, Scaling, Animating, Scrolling 910 }; 911 912 private class Surface extends Canvas { 913 914 public Surface(Composite parent) { 915 super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL); 916 Display display = getDisplay(); 917 mNormalCursor = new Cursor(display, SWT.CURSOR_CROSS); 918 mIncreasingCursor = new Cursor(display, SWT.CURSOR_SIZEE); 919 mDecreasingCursor = new Cursor(display, SWT.CURSOR_SIZEW); 920 921 initZoomFractionsWithExp(); 922 923 addPaintListener(new PaintListener() { 924 @Override 925 public void paintControl(PaintEvent pe) { 926 draw(pe.display, pe.gc); 927 } 928 }); 929 930 mZoomAnimator = new Runnable() { 931 @Override 932 public void run() { 933 animateZoom(); 934 } 935 }; 936 937 mHighlightAnimator = new Runnable() { 938 @Override 939 public void run() { 940 animateHighlight(); 941 } 942 }; 943 } 944 945 private void initZoomFractionsWithExp() { 946 mZoomFractions = new double[ZOOM_STEPS]; 947 int next = 0; 948 for (int ii = 0; ii < ZOOM_STEPS / 2; ++ii, ++next) { 949 mZoomFractions[next] = (double) (1 << ii) 950 / (double) (1 << (ZOOM_STEPS / 2)); 951 // System.out.printf("%d %f\n", next, zoomFractions[next]); 952 } 953 for (int ii = 2; ii < 2 + ZOOM_STEPS / 2; ++ii, ++next) { 954 mZoomFractions[next] = (double) ((1 << ii) - 1) 955 / (double) (1 << ii); 956 // System.out.printf("%d %f\n", next, zoomFractions[next]); 957 } 958 } 959 960 @SuppressWarnings("unused") 961 private void initZoomFractionsWithSinWave() { 962 mZoomFractions = new double[ZOOM_STEPS]; 963 for (int ii = 0; ii < ZOOM_STEPS; ++ii) { 964 double offset = Math.PI * ii / ZOOM_STEPS; 965 mZoomFractions[ii] = (Math.sin((1.5 * Math.PI + offset)) + 1.0) / 2.0; 966 // System.out.printf("%d %f\n", ii, zoomFractions[ii]); 967 } 968 } 969 970 public void setRange(double minVal, double maxVal) { 971 mMinDataVal = minVal; 972 mMaxDataVal = maxVal; 973 mScaleInfo.setMinVal(minVal); 974 mScaleInfo.setMaxVal(maxVal); 975 } 976 977 public void setLimitRange(double minVal, double maxVal) { 978 mLimitMinVal = minVal; 979 mLimitMaxVal = maxVal; 980 } 981 982 public void resetScale() { 983 mScaleInfo.setMinVal(mLimitMinVal); 984 mScaleInfo.setMaxVal(mLimitMaxVal); 985 } 986 987 public void setScaleFromHorizontalScrollBar(int selection) { 988 double minVal = mScaleInfo.getMinVal(); 989 double maxVal = mScaleInfo.getMaxVal(); 990 double visibleRange = maxVal - minVal; 991 992 minVal = mLimitMinVal + selection; 993 maxVal = minVal + visibleRange; 994 if (maxVal > mLimitMaxVal) { 995 maxVal = mLimitMaxVal; 996 minVal = maxVal - visibleRange; 997 } 998 mScaleInfo.setMinVal(minVal); 999 mScaleInfo.setMaxVal(maxVal); 1000 1001 mGraphicsState = GraphicsState.Scrolling; 1002 } 1003 1004 private void updateHorizontalScrollBar() { 1005 double minVal = mScaleInfo.getMinVal(); 1006 double maxVal = mScaleInfo.getMaxVal(); 1007 double visibleRange = maxVal - minVal; 1008 double fullRange = mLimitMaxVal - mLimitMinVal; 1009 1010 ScrollBar hBar = getHorizontalBar(); 1011 if (fullRange > visibleRange) { 1012 hBar.setVisible(true); 1013 hBar.setMinimum(0); 1014 hBar.setMaximum((int)Math.ceil(fullRange)); 1015 hBar.setThumb((int)Math.ceil(visibleRange)); 1016 hBar.setSelection((int)Math.floor(minVal - mLimitMinVal)); 1017 } else { 1018 hBar.setVisible(false); 1019 } 1020 } 1021 1022 private void draw(Display display, GC gc) { 1023 if (mSegments.length == 0) { 1024 // gc.setBackground(colorBackground); 1025 // gc.fillRectangle(getBounds()); 1026 return; 1027 } 1028 1029 // Create an image for double-buffering 1030 Image image = new Image(display, getBounds()); 1031 1032 // Set up the off-screen gc 1033 GC gcImage = new GC(image); 1034 if (mSetFonts) 1035 gcImage.setFont(mFontRegistry.get("small")); //$NON-NLS-1$ 1036 1037 // Draw the background 1038 // gcImage.setBackground(colorBackground); 1039 // gcImage.fillRectangle(image.getBounds()); 1040 1041 if (mGraphicsState == GraphicsState.Scaling) { 1042 double diff = mMouse.x - mMouseMarkStartX; 1043 if (diff > 0) { 1044 double newMinVal = mScaleMinVal - diff / mScalePixelsPerRange; 1045 if (newMinVal < mLimitMinVal) 1046 newMinVal = mLimitMinVal; 1047 mScaleInfo.setMinVal(newMinVal); 1048 // System.out.printf("diff %f scaleMin %f newMin %f\n", 1049 // diff, scaleMinVal, newMinVal); 1050 } else if (diff < 0) { 1051 double newMaxVal = mScaleMaxVal - diff / mScalePixelsPerRange; 1052 if (newMaxVal > mLimitMaxVal) 1053 newMaxVal = mLimitMaxVal; 1054 mScaleInfo.setMaxVal(newMaxVal); 1055 // System.out.printf("diff %f scaleMax %f newMax %f\n", 1056 // diff, scaleMaxVal, newMaxVal); 1057 } 1058 } 1059 1060 // Recompute the ticks and strips only if the size has changed, 1061 // or we scrolled so that a new row is visible. 1062 Point dim = getSize(); 1063 if (mStartRow != mCachedStartRow || mEndRow != mCachedEndRow 1064 || mScaleInfo.getMinVal() != mCachedMinVal 1065 || mScaleInfo.getMaxVal() != mCachedMaxVal) { 1066 mCachedStartRow = mStartRow; 1067 mCachedEndRow = mEndRow; 1068 int xdim = dim.x - TotalXMargin; 1069 mScaleInfo.setNumPixels(xdim); 1070 boolean forceEndPoints = (mGraphicsState == GraphicsState.Scaling 1071 || mGraphicsState == GraphicsState.Animating 1072 || mGraphicsState == GraphicsState.Scrolling); 1073 mScaleInfo.computeTicks(forceEndPoints); 1074 mCachedMinVal = mScaleInfo.getMinVal(); 1075 mCachedMaxVal = mScaleInfo.getMaxVal(); 1076 if (mLimitMinVal > mScaleInfo.getMinVal()) 1077 mLimitMinVal = mScaleInfo.getMinVal(); 1078 if (mLimitMaxVal < mScaleInfo.getMaxVal()) 1079 mLimitMaxVal = mScaleInfo.getMaxVal(); 1080 1081 // Compute the strips 1082 computeStrips(); 1083 1084 // Update the horizontal scrollbar. 1085 updateHorizontalScrollBar(); 1086 } 1087 1088 if (mNumRows > 2) { 1089 // Draw the row background stripes 1090 gcImage.setBackground(mColorRowBack); 1091 for (int ii = 1; ii < mNumRows; ii += 2) { 1092 RowData rd = mRows[ii]; 1093 int y1 = rd.mRank * rowYSpace - mScrollOffsetY; 1094 gcImage.fillRectangle(0, y1, dim.x, rowYSpace); 1095 } 1096 } 1097 1098 if (drawingSelection()) { 1099 drawSelection(display, gcImage); 1100 } 1101 1102 String blockName = null; 1103 Color blockColor = null; 1104 String blockDetails = null; 1105 1106 if (mDebug) { 1107 double pixelsPerRange = mScaleInfo.getPixelsPerRange(); 1108 System.out 1109 .printf( 1110 "dim.x %d pixels %d minVal %f, maxVal %f ppr %f rpp %f\n", 1111 dim.x, dim.x - TotalXMargin, mScaleInfo 1112 .getMinVal(), mScaleInfo.getMaxVal(), 1113 pixelsPerRange, 1.0 / pixelsPerRange); 1114 } 1115 1116 // Draw the strips 1117 Block selectBlock = null; 1118 for (Strip strip : mStripList) { 1119 if (strip.mColor == null) { 1120 // System.out.printf("strip.color is null\n"); 1121 continue; 1122 } 1123 gcImage.setBackground(strip.mColor); 1124 gcImage.fillRectangle(strip.mX, strip.mY - mScrollOffsetY, strip.mWidth, 1125 strip.mHeight); 1126 if (mMouseRow == strip.mRowData.mRank) { 1127 if (mMouse.x >= strip.mX 1128 && mMouse.x < strip.mX + strip.mWidth) { 1129 Block block = strip.mSegment.mBlock; 1130 blockName = block.getName(); 1131 blockColor = strip.mColor; 1132 if (mHaveCpuTime) { 1133 if (mHaveRealTime) { 1134 blockDetails = String.format( 1135 "excl cpu %s, incl cpu %s, " 1136 + "excl real %s, incl real %s", 1137 mUnits.labelledString(block.getExclusiveCpuTime()), 1138 mUnits.labelledString(block.getInclusiveCpuTime()), 1139 mUnits.labelledString(block.getExclusiveRealTime()), 1140 mUnits.labelledString(block.getInclusiveRealTime())); 1141 } else { 1142 blockDetails = String.format( 1143 "excl cpu %s, incl cpu %s", 1144 mUnits.labelledString(block.getExclusiveCpuTime()), 1145 mUnits.labelledString(block.getInclusiveCpuTime())); 1146 } 1147 } else { 1148 blockDetails = String.format( 1149 "excl real %s, incl real %s", 1150 mUnits.labelledString(block.getExclusiveRealTime()), 1151 mUnits.labelledString(block.getInclusiveRealTime())); 1152 } 1153 } 1154 if (mMouseSelect.x >= strip.mX 1155 && mMouseSelect.x < strip.mX + strip.mWidth) { 1156 selectBlock = strip.mSegment.mBlock; 1157 } 1158 } 1159 } 1160 mMouseSelect.x = 0; 1161 mMouseSelect.y = 0; 1162 1163 if (selectBlock != null) { 1164 ArrayList<Selection> selections = new ArrayList<Selection>(); 1165 // Get the row label 1166 RowData rd = mRows[mMouseRow]; 1167 selections.add(Selection.highlight("Thread", rd.mName)); //$NON-NLS-1$ 1168 selections.add(Selection.highlight("Call", selectBlock)); //$NON-NLS-1$ 1169 1170 int mouseX = mMouse.x - LeftMargin; 1171 double mouseXval = mScaleInfo.pixelToValue(mouseX); 1172 selections.add(Selection.highlight("Time", mouseXval)); //$NON-NLS-1$ 1173 1174 mSelectionController.change(selections, "TimeLineView"); //$NON-NLS-1$ 1175 mHighlightMethodData = null; 1176 mHighlightCall = (Call) selectBlock; 1177 startHighlighting(); 1178 } 1179 1180 // Draw a highlight box on the row where the mouse is. 1181 // Except don't draw the box if we are animating the 1182 // highlighing of a call or method because the inclusive 1183 // highlight bar passes through the highlight box and 1184 // causes an annoying flashing artifact. 1185 if (mMouseRow >= 0 && mMouseRow < mNumRows && mHighlightStep == 0) { 1186 gcImage.setForeground(mColorGray); 1187 int y1 = mMouseRow * rowYSpace - mScrollOffsetY; 1188 gcImage.drawLine(0, y1, dim.x, y1); 1189 gcImage.drawLine(0, y1 + rowYSpace, dim.x, y1 + rowYSpace); 1190 } 1191 1192 // Highlight a selected method, if any 1193 drawHighlights(gcImage, dim); 1194 1195 // Draw a vertical line where the mouse is. 1196 gcImage.setForeground(mColorDarkGray); 1197 int lineEnd = Math.min(dim.y, mNumRows * rowYSpace); 1198 gcImage.drawLine(mMouse.x, 0, mMouse.x, lineEnd); 1199 1200 if (blockName != null) { 1201 mTimescale.setMethodName(blockName); 1202 mTimescale.setMethodColor(blockColor); 1203 mTimescale.setDetails(blockDetails); 1204 mShowHighlightName = false; 1205 } else if (mShowHighlightName) { 1206 // Draw the highlighted method name 1207 MethodData md = mHighlightMethodData; 1208 if (md == null && mHighlightCall != null) 1209 md = mHighlightCall.getMethodData(); 1210 if (md == null) 1211 System.out.printf("null highlight?\n"); //$NON-NLS-1$ 1212 if (md != null) { 1213 mTimescale.setMethodName(md.getProfileName()); 1214 mTimescale.setMethodColor(md.getColor()); 1215 mTimescale.setDetails(null); 1216 } 1217 } else { 1218 mTimescale.setMethodName(null); 1219 mTimescale.setMethodColor(null); 1220 mTimescale.setDetails(null); 1221 } 1222 mTimescale.redraw(); 1223 1224 // Draw the off-screen buffer to the screen 1225 gc.drawImage(image, 0, 0); 1226 1227 // Clean up 1228 image.dispose(); 1229 gcImage.dispose(); 1230 } 1231 1232 private void drawHighlights(GC gc, Point dim) { 1233 int height = mHighlightHeight; 1234 if (height <= 0) 1235 return; 1236 for (Range range : mHighlightExclusive) { 1237 gc.setBackground(range.mColor); 1238 int xStart = range.mXdim.x; 1239 int width = range.mXdim.y; 1240 gc.fillRectangle(xStart, range.mY - height - mScrollOffsetY, width, height); 1241 } 1242 1243 // Draw the inclusive lines a bit shorter 1244 height -= 1; 1245 if (height <= 0) 1246 height = 1; 1247 1248 // Highlight the inclusive ranges 1249 gc.setForeground(mColorDarkGray); 1250 gc.setBackground(mColorDarkGray); 1251 for (Range range : mHighlightInclusive) { 1252 int x1 = range.mXdim.x; 1253 int x2 = range.mXdim.y; 1254 boolean drawLeftEnd = false; 1255 boolean drawRightEnd = false; 1256 if (x1 >= LeftMargin) 1257 drawLeftEnd = true; 1258 else 1259 x1 = LeftMargin; 1260 if (x2 >= LeftMargin) 1261 drawRightEnd = true; 1262 else 1263 x2 = dim.x - RightMargin; 1264 int y1 = range.mY + rowHeight + 2 - mScrollOffsetY; 1265 1266 // If the range is very narrow, then just draw a small 1267 // rectangle. 1268 if (x2 - x1 < MinInclusiveRange) { 1269 int width = x2 - x1; 1270 if (width < 2) 1271 width = 2; 1272 gc.fillRectangle(x1, y1, width, height); 1273 continue; 1274 } 1275 if (drawLeftEnd) { 1276 if (drawRightEnd) { 1277 // Draw both ends 1278 int[] points = { x1, y1, x1, y1 + height, x2, 1279 y1 + height, x2, y1 }; 1280 gc.drawPolyline(points); 1281 } else { 1282 // Draw the left end 1283 int[] points = { x1, y1, x1, y1 + height, x2, 1284 y1 + height }; 1285 gc.drawPolyline(points); 1286 } 1287 } else { 1288 if (drawRightEnd) { 1289 // Draw the right end 1290 int[] points = { x1, y1 + height, x2, y1 + height, x2, 1291 y1 }; 1292 gc.drawPolyline(points); 1293 } else { 1294 // Draw neither end, just the line 1295 int[] points = { x1, y1 + height, x2, y1 + height }; 1296 gc.drawPolyline(points); 1297 } 1298 } 1299 1300 // Draw the arrowheads, if necessary 1301 if (drawLeftEnd == false) { 1302 int[] points = { x1 + 7, y1 + height - 4, x1, y1 + height, 1303 x1 + 7, y1 + height + 4 }; 1304 gc.fillPolygon(points); 1305 } 1306 if (drawRightEnd == false) { 1307 int[] points = { x2 - 7, y1 + height - 4, x2, y1 + height, 1308 x2 - 7, y1 + height + 4 }; 1309 gc.fillPolygon(points); 1310 } 1311 } 1312 } 1313 1314 private boolean drawingSelection() { 1315 return mGraphicsState == GraphicsState.Marking 1316 || mGraphicsState == GraphicsState.Animating; 1317 } 1318 1319 private void drawSelection(Display display, GC gc) { 1320 Point dim = getSize(); 1321 gc.setForeground(mColorGray); 1322 gc.drawLine(mMouseMarkStartX, 0, mMouseMarkStartX, dim.y); 1323 gc.setBackground(mColorZoomSelection); 1324 int width; 1325 int mouseX = (mGraphicsState == GraphicsState.Animating) ? mMouseMarkEndX : mMouse.x; 1326 int x; 1327 if (mMouseMarkStartX < mouseX) { 1328 x = mMouseMarkStartX; 1329 width = mouseX - mMouseMarkStartX; 1330 } else { 1331 x = mouseX; 1332 width = mMouseMarkStartX - mouseX; 1333 } 1334 gc.fillRectangle(x, 0, width, dim.y); 1335 } 1336 1337 private void computeStrips() { 1338 double minVal = mScaleInfo.getMinVal(); 1339 double maxVal = mScaleInfo.getMaxVal(); 1340 1341 // Allocate space for the pixel data 1342 Pixel[] pixels = new Pixel[mNumRows]; 1343 for (int ii = 0; ii < mNumRows; ++ii) 1344 pixels[ii] = new Pixel(); 1345 1346 // Clear the per-block pixel data 1347 for (int ii = 0; ii < mSegments.length; ++ii) { 1348 mSegments[ii].mBlock.clearWeight(); 1349 } 1350 1351 mStripList.clear(); 1352 mHighlightExclusive.clear(); 1353 mHighlightInclusive.clear(); 1354 MethodData callMethod = null; 1355 long callStart = 0; 1356 long callEnd = -1; 1357 RowData callRowData = null; 1358 int prevMethodStart = -1; 1359 int prevMethodEnd = -1; 1360 int prevCallStart = -1; 1361 int prevCallEnd = -1; 1362 if (mHighlightCall != null) { 1363 int callPixelStart = -1; 1364 int callPixelEnd = -1; 1365 callStart = mHighlightCall.getStartTime(); 1366 callEnd = mHighlightCall.getEndTime(); 1367 callMethod = mHighlightCall.getMethodData(); 1368 if (callStart >= minVal) 1369 callPixelStart = mScaleInfo.valueToPixel(callStart); 1370 if (callEnd <= maxVal) 1371 callPixelEnd = mScaleInfo.valueToPixel(callEnd); 1372 // System.out.printf("callStart,End %d,%d minVal,maxVal %f,%f 1373 // callPixelStart,End %d,%d\n", 1374 // callStart, callEnd, minVal, maxVal, callPixelStart, 1375 // callPixelEnd); 1376 int threadId = mHighlightCall.getThreadId(); 1377 String threadName = mThreadLabels.get(threadId); 1378 callRowData = mRowByName.get(threadName); 1379 int y1 = callRowData.mRank * rowYSpace + rowYMarginHalf; 1380 Color color = callMethod.getColor(); 1381 mHighlightInclusive.add(new Range(callPixelStart + LeftMargin, 1382 callPixelEnd + LeftMargin, y1, color)); 1383 } 1384 for (Segment segment : mSegments) { 1385 if (segment.mEndTime <= minVal) 1386 continue; 1387 if (segment.mStartTime >= maxVal) 1388 continue; 1389 1390 Block block = segment.mBlock; 1391 1392 // Skip over blocks that were not assigned a color, including the 1393 // top level block and others that have zero inclusive time. 1394 Color color = block.getColor(); 1395 if (color == null) 1396 continue; 1397 1398 double recordStart = Math.max(segment.mStartTime, minVal); 1399 double recordEnd = Math.min(segment.mEndTime, maxVal); 1400 if (recordStart == recordEnd) 1401 continue; 1402 int pixelStart = mScaleInfo.valueToPixel(recordStart); 1403 int pixelEnd = mScaleInfo.valueToPixel(recordEnd); 1404 int width = pixelEnd - pixelStart; 1405 boolean isContextSwitch = segment.mIsContextSwitch; 1406 1407 RowData rd = segment.mRowData; 1408 MethodData md = block.getMethodData(); 1409 1410 // We will add the scroll offset later when we draw the strips 1411 int y1 = rd.mRank * rowYSpace + rowYMarginHalf; 1412 1413 // If we can't display any more rows, then quit 1414 if (rd.mRank > mEndRow) 1415 break; 1416 1417 // System.out.printf("segment %s val: [%.1f, %.1f] frac [%f, %f] 1418 // pixel: [%d, %d] pix.start %d weight %.2f %s\n", 1419 // block.getName(), recordStart, recordEnd, 1420 // scaleInfo.valueToPixelFraction(recordStart), 1421 // scaleInfo.valueToPixelFraction(recordEnd), 1422 // pixelStart, pixelEnd, pixels[rd.rank].start, 1423 // pixels[rd.rank].maxWeight, 1424 // pixels[rd.rank].segment != null 1425 // ? pixels[rd.rank].segment.block.getName() 1426 // : "null"); 1427 1428 if (mHighlightMethodData != null) { 1429 if (mHighlightMethodData == md) { 1430 if (prevMethodStart != pixelStart || prevMethodEnd != pixelEnd) { 1431 prevMethodStart = pixelStart; 1432 prevMethodEnd = pixelEnd; 1433 int rangeWidth = width; 1434 if (rangeWidth == 0) 1435 rangeWidth = 1; 1436 mHighlightExclusive.add(new Range(pixelStart 1437 + LeftMargin, rangeWidth, y1, color)); 1438 callStart = block.getStartTime(); 1439 int callPixelStart = -1; 1440 if (callStart >= minVal) 1441 callPixelStart = mScaleInfo.valueToPixel(callStart); 1442 int callPixelEnd = -1; 1443 callEnd = block.getEndTime(); 1444 if (callEnd <= maxVal) 1445 callPixelEnd = mScaleInfo.valueToPixel(callEnd); 1446 if (prevCallStart != callPixelStart || prevCallEnd != callPixelEnd) { 1447 prevCallStart = callPixelStart; 1448 prevCallEnd = callPixelEnd; 1449 mHighlightInclusive.add(new Range( 1450 callPixelStart + LeftMargin, 1451 callPixelEnd + LeftMargin, y1, color)); 1452 } 1453 } 1454 } else if (mFadeColors) { 1455 color = md.getFadedColor(); 1456 } 1457 } else if (mHighlightCall != null) { 1458 if (segment.mStartTime >= callStart 1459 && segment.mEndTime <= callEnd && callMethod == md 1460 && callRowData == rd) { 1461 if (prevMethodStart != pixelStart || prevMethodEnd != pixelEnd) { 1462 prevMethodStart = pixelStart; 1463 prevMethodEnd = pixelEnd; 1464 int rangeWidth = width; 1465 if (rangeWidth == 0) 1466 rangeWidth = 1; 1467 mHighlightExclusive.add(new Range(pixelStart 1468 + LeftMargin, rangeWidth, y1, color)); 1469 } 1470 } else if (mFadeColors) { 1471 color = md.getFadedColor(); 1472 } 1473 } 1474 1475 // Cases: 1476 // 1. This segment starts on a different pixel than the 1477 // previous segment started on. In this case, emit 1478 // the pixel strip, if any, and: 1479 // A. If the width is 0, then add this segment's 1480 // weight to the Pixel. 1481 // B. If the width > 0, then emit a strip for this 1482 // segment (no partial Pixel data). 1483 // 1484 // 2. Otherwise (the new segment starts on the same 1485 // pixel as the previous segment): add its "weight" 1486 // to the current pixel, and: 1487 // A. If the new segment has width 1, 1488 // then emit the pixel strip and then 1489 // add the segment's weight to the pixel. 1490 // B. If the new segment has width > 1, 1491 // then emit the pixel strip, and emit the rest 1492 // of the strip for this segment (no partial Pixel 1493 // data). 1494 1495 Pixel pix = pixels[rd.mRank]; 1496 if (pix.mStart != pixelStart) { 1497 if (pix.mSegment != null) { 1498 // Emit the pixel strip. This also clears the pixel. 1499 emitPixelStrip(rd, y1, pix); 1500 } 1501 1502 if (width == 0) { 1503 // Compute the "weight" of this segment for the first 1504 // pixel. For a pixel N, the "weight" of a segment is 1505 // how much of the region [N - 0.5, N + 0.5] is covered 1506 // by the segment. 1507 double weight = computeWeight(recordStart, recordEnd, 1508 isContextSwitch, pixelStart); 1509 weight = block.addWeight(pixelStart, rd.mRank, weight); 1510 if (weight > pix.mMaxWeight) { 1511 pix.setFields(pixelStart, weight, segment, color, 1512 rd); 1513 } 1514 } else { 1515 int x1 = pixelStart + LeftMargin; 1516 Strip strip = new Strip( 1517 x1, isContextSwitch ? y1 + rowHeight - 1 : y1, 1518 width, isContextSwitch ? 1 : rowHeight, 1519 rd, segment, color); 1520 mStripList.add(strip); 1521 } 1522 } else { 1523 double weight = computeWeight(recordStart, recordEnd, 1524 isContextSwitch, pixelStart); 1525 weight = block.addWeight(pixelStart, rd.mRank, weight); 1526 if (weight > pix.mMaxWeight) { 1527 pix.setFields(pixelStart, weight, segment, color, rd); 1528 } 1529 if (width == 1) { 1530 // Emit the pixel strip. This also clears the pixel. 1531 emitPixelStrip(rd, y1, pix); 1532 1533 // Compute the weight for the next pixel 1534 pixelStart += 1; 1535 weight = computeWeight(recordStart, recordEnd, 1536 isContextSwitch, pixelStart); 1537 weight = block.addWeight(pixelStart, rd.mRank, weight); 1538 pix.setFields(pixelStart, weight, segment, color, rd); 1539 } else if (width > 1) { 1540 // Emit the pixel strip. This also clears the pixel. 1541 emitPixelStrip(rd, y1, pix); 1542 1543 // Emit a strip for the rest of the segment. 1544 pixelStart += 1; 1545 width -= 1; 1546 int x1 = pixelStart + LeftMargin; 1547 Strip strip = new Strip( 1548 x1, isContextSwitch ? y1 + rowHeight - 1 : y1, 1549 width, isContextSwitch ? 1 : rowHeight, 1550 rd,segment, color); 1551 mStripList.add(strip); 1552 } 1553 } 1554 } 1555 1556 // Emit the last pixels of each row, if any 1557 for (int ii = 0; ii < mNumRows; ++ii) { 1558 Pixel pix = pixels[ii]; 1559 if (pix.mSegment != null) { 1560 RowData rd = pix.mRowData; 1561 int y1 = rd.mRank * rowYSpace + rowYMarginHalf; 1562 // Emit the pixel strip. This also clears the pixel. 1563 emitPixelStrip(rd, y1, pix); 1564 } 1565 } 1566 1567 if (false) { 1568 System.out.printf("computeStrips()\n"); 1569 for (Strip strip : mStripList) { 1570 System.out.printf("%3d, %3d width %3d height %d %s\n", 1571 strip.mX, strip.mY, strip.mWidth, strip.mHeight, 1572 strip.mSegment.mBlock.getName()); 1573 } 1574 } 1575 } 1576 1577 private double computeWeight(double start, double end, 1578 boolean isContextSwitch, int pixel) { 1579 if (isContextSwitch) { 1580 return 0; 1581 } 1582 double pixelStartFraction = mScaleInfo.valueToPixelFraction(start); 1583 double pixelEndFraction = mScaleInfo.valueToPixelFraction(end); 1584 double leftEndPoint = Math.max(pixelStartFraction, pixel - 0.5); 1585 double rightEndPoint = Math.min(pixelEndFraction, pixel + 0.5); 1586 double weight = rightEndPoint - leftEndPoint; 1587 return weight; 1588 } 1589 1590 private void emitPixelStrip(RowData rd, int y, Pixel pixel) { 1591 Strip strip; 1592 1593 if (pixel.mSegment == null) 1594 return; 1595 1596 int x = pixel.mStart + LeftMargin; 1597 // Compute the percentage of the row height proportional to 1598 // the weight of this pixel. But don't let the proportion 1599 // exceed 3/4 of the row height so that we can easily see 1600 // if a given time range includes more than one method. 1601 int height = (int) (pixel.mMaxWeight * rowHeight * 0.75); 1602 if (height < mMinStripHeight) 1603 height = mMinStripHeight; 1604 int remainder = rowHeight - height; 1605 if (remainder > 0) { 1606 strip = new Strip(x, y, 1, remainder, rd, pixel.mSegment, 1607 mFadeColors ? mColorGray : mColorBlack); 1608 mStripList.add(strip); 1609 // System.out.printf("emitPixel (%d, %d) height %d black\n", 1610 // x, y, remainder); 1611 } 1612 strip = new Strip(x, y + remainder, 1, height, rd, pixel.mSegment, 1613 pixel.mColor); 1614 mStripList.add(strip); 1615 // System.out.printf("emitPixel (%d, %d) height %d %s\n", 1616 // x, y + remainder, height, pixel.segment.block.getName()); 1617 pixel.mSegment = null; 1618 pixel.mMaxWeight = 0.0; 1619 } 1620 1621 private void mouseMove(MouseEvent me) { 1622 if (false) { 1623 if (mHighlightMethodData != null) { 1624 mHighlightMethodData = null; 1625 // Force a recomputation of the strip colors 1626 mCachedEndRow = -1; 1627 } 1628 } 1629 Point dim = mSurface.getSize(); 1630 int x = me.x; 1631 if (x < LeftMargin) 1632 x = LeftMargin; 1633 if (x > dim.x - RightMargin) 1634 x = dim.x - RightMargin; 1635 mMouse.x = x; 1636 mMouse.y = me.y; 1637 mTimescale.setVbarPosition(x); 1638 if (mGraphicsState == GraphicsState.Marking) { 1639 mTimescale.setMarkEnd(x); 1640 } 1641 1642 if (mGraphicsState == GraphicsState.Normal) { 1643 // Set the cursor to the normal state. 1644 mSurface.setCursor(mNormalCursor); 1645 } else if (mGraphicsState == GraphicsState.Marking) { 1646 // Make the cursor point in the direction of the sweep 1647 if (mMouse.x >= mMouseMarkStartX) 1648 mSurface.setCursor(mIncreasingCursor); 1649 else 1650 mSurface.setCursor(mDecreasingCursor); 1651 } 1652 int rownum = (mMouse.y + mScrollOffsetY) / rowYSpace; 1653 if (me.y < 0 || me.y >= dim.y) { 1654 rownum = -1; 1655 } 1656 if (mMouseRow != rownum) { 1657 mMouseRow = rownum; 1658 mLabels.redraw(); 1659 } 1660 redraw(); 1661 } 1662 1663 private void mouseDown(MouseEvent me) { 1664 Point dim = mSurface.getSize(); 1665 int x = me.x; 1666 if (x < LeftMargin) 1667 x = LeftMargin; 1668 if (x > dim.x - RightMargin) 1669 x = dim.x - RightMargin; 1670 mMouseMarkStartX = x; 1671 mGraphicsState = GraphicsState.Marking; 1672 mSurface.setCursor(mIncreasingCursor); 1673 mTimescale.setMarkStart(mMouseMarkStartX); 1674 mTimescale.setMarkEnd(mMouseMarkStartX); 1675 redraw(); 1676 } 1677 1678 private void mouseUp(MouseEvent me) { 1679 mSurface.setCursor(mNormalCursor); 1680 if (mGraphicsState != GraphicsState.Marking) { 1681 mGraphicsState = GraphicsState.Normal; 1682 return; 1683 } 1684 mGraphicsState = GraphicsState.Animating; 1685 Point dim = mSurface.getSize(); 1686 1687 // If the user released the mouse outside the drawing area then 1688 // cancel the zoom. 1689 if (me.y <= 0 || me.y >= dim.y) { 1690 mGraphicsState = GraphicsState.Normal; 1691 redraw(); 1692 return; 1693 } 1694 1695 int x = me.x; 1696 if (x < LeftMargin) 1697 x = LeftMargin; 1698 if (x > dim.x - RightMargin) 1699 x = dim.x - RightMargin; 1700 mMouseMarkEndX = x; 1701 1702 // If the user clicked and released the mouse at the same point 1703 // (+/- a pixel or two) then cancel the zoom (but select the 1704 // method). 1705 int dist = mMouseMarkEndX - mMouseMarkStartX; 1706 if (dist < 0) 1707 dist = -dist; 1708 if (dist <= 2) { 1709 mGraphicsState = GraphicsState.Normal; 1710 1711 // Select the method underneath the mouse 1712 mMouseSelect.x = mMouseMarkStartX; 1713 mMouseSelect.y = me.y; 1714 redraw(); 1715 return; 1716 } 1717 1718 // Make mouseEndX be the higher end point 1719 if (mMouseMarkEndX < mMouseMarkStartX) { 1720 int temp = mMouseMarkEndX; 1721 mMouseMarkEndX = mMouseMarkStartX; 1722 mMouseMarkStartX = temp; 1723 } 1724 1725 // If the zoom area is the whole window (or nearly the whole 1726 // window) then cancel the zoom. 1727 if (mMouseMarkStartX <= LeftMargin + MinZoomPixelMargin 1728 && mMouseMarkEndX >= dim.x - RightMargin - MinZoomPixelMargin) { 1729 mGraphicsState = GraphicsState.Normal; 1730 redraw(); 1731 return; 1732 } 1733 1734 // Compute some variables needed for zooming. 1735 // It's probably easiest to explain by an example. There 1736 // are two scales (or dimensions) involved: one for the pixels 1737 // and one for the values (microseconds). To keep the example 1738 // simple, suppose we have pixels in the range [0,16] and 1739 // values in the range [100, 260], and suppose the user 1740 // selects a zoom window from pixel 4 to pixel 8. 1741 // 1742 // usec: 100 140 180 260 1743 // |-------|ZZZZZZZ|---------------| 1744 // pixel: 0 4 8 16 1745 // 1746 // I've drawn the pixels starting at zero for simplicity, but 1747 // in fact the drawable area is offset from the left margin 1748 // by the value of "LeftMargin". 1749 // 1750 // The "pixels-per-range" (ppr) in this case is 0.1 (a tenth of 1751 // a pixel per usec). What we want is to redraw the screen in 1752 // several steps, each time increasing the zoom window until the 1753 // zoom window fills the screen. For simplicity, assume that 1754 // we want to zoom in four equal steps. Then the snapshots 1755 // of the screen at each step would look something like this: 1756 // 1757 // usec: 100 140 180 260 1758 // |-------|ZZZZZZZ|---------------| 1759 // pixel: 0 4 8 16 1760 // 1761 // usec: ? 140 180 ? 1762 // |-----|ZZZZZZZZZZZZZ|-----------| 1763 // pixel: 0 3 10 16 1764 // 1765 // usec: ? 140 180 ? 1766 // |---|ZZZZZZZZZZZZZZZZZZZ|-------| 1767 // pixel: 0 2 12 16 1768 // 1769 // usec: ?140 180 ? 1770 // |-|ZZZZZZZZZZZZZZZZZZZZZZZZZ|---| 1771 // pixel: 0 1 14 16 1772 // 1773 // usec: 140 180 1774 // |ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ| 1775 // pixel: 0 16 1776 // 1777 // The problem is how to compute the endpoints (denoted by ?) 1778 // for each step. This is a little tricky. We first need to 1779 // compute the "fixed point": this is the point in the selection 1780 // that doesn't move left or right. Then we can recompute the 1781 // "ppr" (pixels per range) at each step and then find the 1782 // endpoints. The computation of the end points is done 1783 // in animateZoom(). This method computes the fixed point 1784 // and some other variables needed in animateZoom(). 1785 1786 double minVal = mScaleInfo.getMinVal(); 1787 double maxVal = mScaleInfo.getMaxVal(); 1788 double ppr = mScaleInfo.getPixelsPerRange(); 1789 mZoomMin = minVal + ((mMouseMarkStartX - LeftMargin) / ppr); 1790 mZoomMax = minVal + ((mMouseMarkEndX - LeftMargin) / ppr); 1791 1792 // Clamp the min and max values to the actual data min and max 1793 if (mZoomMin < mMinDataVal) 1794 mZoomMin = mMinDataVal; 1795 if (mZoomMax > mMaxDataVal) 1796 mZoomMax = mMaxDataVal; 1797 1798 // Snap the min and max points to the grid determined by the 1799 // TickScaler 1800 // before we zoom. 1801 int xdim = dim.x - TotalXMargin; 1802 TickScaler scaler = new TickScaler(mZoomMin, mZoomMax, xdim, 1803 PixelsPerTick); 1804 scaler.computeTicks(false); 1805 mZoomMin = scaler.getMinVal(); 1806 mZoomMax = scaler.getMaxVal(); 1807 1808 // Also snap the mouse points (in pixel space) to be consistent with 1809 // zoomMin and zoomMax (in value space). 1810 mMouseMarkStartX = (int) ((mZoomMin - minVal) * ppr + LeftMargin); 1811 mMouseMarkEndX = (int) ((mZoomMax - minVal) * ppr + LeftMargin); 1812 mTimescale.setMarkStart(mMouseMarkStartX); 1813 mTimescale.setMarkEnd(mMouseMarkEndX); 1814 1815 // Compute the mouse selection end point distances 1816 mMouseEndDistance = dim.x - RightMargin - mMouseMarkEndX; 1817 mMouseStartDistance = mMouseMarkStartX - LeftMargin; 1818 mZoomMouseStart = mMouseMarkStartX; 1819 mZoomMouseEnd = mMouseMarkEndX; 1820 mZoomStep = 0; 1821 1822 // Compute the fixed point in both value space and pixel space. 1823 mMin2ZoomMin = mZoomMin - minVal; 1824 mZoomMax2Max = maxVal - mZoomMax; 1825 mZoomFixed = mZoomMin + (mZoomMax - mZoomMin) * mMin2ZoomMin 1826 / (mMin2ZoomMin + mZoomMax2Max); 1827 mZoomFixedPixel = (mZoomFixed - minVal) * ppr + LeftMargin; 1828 mFixedPixelStartDistance = mZoomFixedPixel - LeftMargin; 1829 mFixedPixelEndDistance = dim.x - RightMargin - mZoomFixedPixel; 1830 1831 mZoomMin2Fixed = mZoomFixed - mZoomMin; 1832 mFixed2ZoomMax = mZoomMax - mZoomFixed; 1833 1834 getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator); 1835 redraw(); 1836 update(); 1837 } 1838 1839 private void mouseScrolled(MouseEvent me) { 1840 mGraphicsState = GraphicsState.Scrolling; 1841 double tMin = mScaleInfo.getMinVal(); 1842 double tMax = mScaleInfo.getMaxVal(); 1843 double zoomFactor = 2; 1844 double tMinRef = mLimitMinVal; 1845 double tMaxRef = mLimitMaxVal; 1846 double t; // the fixed point 1847 double tMinNew; 1848 double tMaxNew; 1849 if (me.count > 0) { 1850 // we zoom in 1851 Point dim = mSurface.getSize(); 1852 int x = me.x; 1853 if (x < LeftMargin) 1854 x = LeftMargin; 1855 if (x > dim.x - RightMargin) 1856 x = dim.x - RightMargin; 1857 double ppr = mScaleInfo.getPixelsPerRange(); 1858 t = tMin + ((x - LeftMargin) / ppr); 1859 tMinNew = Math.max(tMinRef, t - (t - tMin) / zoomFactor); 1860 tMaxNew = Math.min(tMaxRef, t + (tMax - t) / zoomFactor); 1861 } else { 1862 // we zoom out 1863 double factor = (tMax - tMin) / (tMaxRef - tMinRef); 1864 if (factor < 1) { 1865 t = (factor * tMinRef - tMin) / (factor - 1); 1866 tMinNew = Math.max(tMinRef, t - zoomFactor * (t - tMin)); 1867 tMaxNew = Math.min(tMaxRef, t + zoomFactor * (tMax - t)); 1868 } else { 1869 return; 1870 } 1871 } 1872 mScaleInfo.setMinVal(tMinNew); 1873 mScaleInfo.setMaxVal(tMaxNew); 1874 mSurface.redraw(); 1875 } 1876 1877 // No defined behavior yet for double-click. 1878 private void mouseDoubleClick(MouseEvent me) { 1879 } 1880 1881 public void startScaling(int mouseX) { 1882 Point dim = mSurface.getSize(); 1883 int x = mouseX; 1884 if (x < LeftMargin) 1885 x = LeftMargin; 1886 if (x > dim.x - RightMargin) 1887 x = dim.x - RightMargin; 1888 mMouseMarkStartX = x; 1889 mGraphicsState = GraphicsState.Scaling; 1890 mScalePixelsPerRange = mScaleInfo.getPixelsPerRange(); 1891 mScaleMinVal = mScaleInfo.getMinVal(); 1892 mScaleMaxVal = mScaleInfo.getMaxVal(); 1893 } 1894 1895 public void stopScaling(int mouseX) { 1896 mGraphicsState = GraphicsState.Normal; 1897 } 1898 1899 private void animateHighlight() { 1900 mHighlightStep += 1; 1901 if (mHighlightStep >= HIGHLIGHT_STEPS) { 1902 mFadeColors = false; 1903 mHighlightStep = 0; 1904 // Force a recomputation of the strip colors 1905 mCachedEndRow = -1; 1906 } else { 1907 mFadeColors = true; 1908 mShowHighlightName = true; 1909 mHighlightHeight = highlightHeights[mHighlightStep]; 1910 getDisplay().timerExec(HIGHLIGHT_TIMER_INTERVAL, mHighlightAnimator); 1911 } 1912 redraw(); 1913 } 1914 1915 private void clearHighlights() { 1916 // System.out.printf("clearHighlights()\n"); 1917 mShowHighlightName = false; 1918 mHighlightHeight = 0; 1919 mHighlightMethodData = null; 1920 mHighlightCall = null; 1921 mFadeColors = false; 1922 mHighlightStep = 0; 1923 // Force a recomputation of the strip colors 1924 mCachedEndRow = -1; 1925 redraw(); 1926 } 1927 1928 private void animateZoom() { 1929 mZoomStep += 1; 1930 if (mZoomStep > ZOOM_STEPS) { 1931 mGraphicsState = GraphicsState.Normal; 1932 // Force a normal recomputation 1933 mCachedMinVal = mScaleInfo.getMinVal() + 1; 1934 } else if (mZoomStep == ZOOM_STEPS) { 1935 mScaleInfo.setMinVal(mZoomMin); 1936 mScaleInfo.setMaxVal(mZoomMax); 1937 mMouseMarkStartX = LeftMargin; 1938 Point dim = getSize(); 1939 mMouseMarkEndX = dim.x - RightMargin; 1940 mTimescale.setMarkStart(mMouseMarkStartX); 1941 mTimescale.setMarkEnd(mMouseMarkEndX); 1942 getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator); 1943 } else { 1944 // Zoom in slowly at first, then speed up, then slow down. 1945 // The zoom fractions are precomputed to save time. 1946 double fraction = mZoomFractions[mZoomStep]; 1947 mMouseMarkStartX = (int) (mZoomMouseStart - fraction * mMouseStartDistance); 1948 mMouseMarkEndX = (int) (mZoomMouseEnd + fraction * mMouseEndDistance); 1949 mTimescale.setMarkStart(mMouseMarkStartX); 1950 mTimescale.setMarkEnd(mMouseMarkEndX); 1951 1952 // Compute the new pixels-per-range. Avoid division by zero. 1953 double ppr; 1954 if (mZoomMin2Fixed >= mFixed2ZoomMax) 1955 ppr = (mZoomFixedPixel - mMouseMarkStartX) / mZoomMin2Fixed; 1956 else 1957 ppr = (mMouseMarkEndX - mZoomFixedPixel) / mFixed2ZoomMax; 1958 double newMin = mZoomFixed - mFixedPixelStartDistance / ppr; 1959 double newMax = mZoomFixed + mFixedPixelEndDistance / ppr; 1960 mScaleInfo.setMinVal(newMin); 1961 mScaleInfo.setMaxVal(newMax); 1962 1963 getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator); 1964 } 1965 redraw(); 1966 } 1967 1968 private static final int TotalXMargin = LeftMargin + RightMargin; 1969 private static final int yMargin = 1; // blank space on top 1970 // The minimum margin on each side of the zoom window, in pixels. 1971 private static final int MinZoomPixelMargin = 10; 1972 private GraphicsState mGraphicsState = GraphicsState.Normal; 1973 private Point mMouse = new Point(LeftMargin, 0); 1974 private int mMouseMarkStartX; 1975 private int mMouseMarkEndX; 1976 private boolean mDebug = false; 1977 private ArrayList<Strip> mStripList = new ArrayList<Strip>(); 1978 private ArrayList<Range> mHighlightExclusive = new ArrayList<Range>(); 1979 private ArrayList<Range> mHighlightInclusive = new ArrayList<Range>(); 1980 private int mMinStripHeight = 2; 1981 private double mCachedMinVal; 1982 private double mCachedMaxVal; 1983 private int mCachedStartRow; 1984 private int mCachedEndRow; 1985 private double mScalePixelsPerRange; 1986 private double mScaleMinVal; 1987 private double mScaleMaxVal; 1988 private double mLimitMinVal; 1989 private double mLimitMaxVal; 1990 private double mMinDataVal; 1991 private double mMaxDataVal; 1992 private Cursor mNormalCursor; 1993 private Cursor mIncreasingCursor; 1994 private Cursor mDecreasingCursor; 1995 private static final int ZOOM_TIMER_INTERVAL = 10; 1996 private static final int HIGHLIGHT_TIMER_INTERVAL = 50; 1997 private static final int ZOOM_STEPS = 8; // must be even 1998 private int mHighlightHeight = 4; 1999 private final int[] highlightHeights = { 0, 2, 4, 5, 6, 5, 4, 2, 4, 5, 2000 6 }; 2001 private final int HIGHLIGHT_STEPS = highlightHeights.length; 2002 private boolean mFadeColors; 2003 private boolean mShowHighlightName; 2004 private double[] mZoomFractions; 2005 private int mZoomStep; 2006 private int mZoomMouseStart; 2007 private int mZoomMouseEnd; 2008 private int mMouseStartDistance; 2009 private int mMouseEndDistance; 2010 private Point mMouseSelect = new Point(0, 0); 2011 private double mZoomFixed; 2012 private double mZoomFixedPixel; 2013 private double mFixedPixelStartDistance; 2014 private double mFixedPixelEndDistance; 2015 private double mZoomMin2Fixed; 2016 private double mMin2ZoomMin; 2017 private double mFixed2ZoomMax; 2018 private double mZoomMax2Max; 2019 private double mZoomMin; 2020 private double mZoomMax; 2021 private Runnable mZoomAnimator; 2022 private Runnable mHighlightAnimator; 2023 private int mHighlightStep; 2024 } 2025 2026 private int computeVisibleRows(int ydim) { 2027 // If we resize, then move the bottom row down. Don't allow the scroll 2028 // to waste space at the bottom. 2029 int offsetY = mScrollOffsetY; 2030 int spaceNeeded = mNumRows * rowYSpace; 2031 if (offsetY + ydim > spaceNeeded) { 2032 offsetY = spaceNeeded - ydim; 2033 if (offsetY < 0) { 2034 offsetY = 0; 2035 } 2036 } 2037 mStartRow = offsetY / rowYSpace; 2038 mEndRow = (offsetY + ydim) / rowYSpace; 2039 if (mEndRow >= mNumRows) { 2040 mEndRow = mNumRows - 1; 2041 } 2042 2043 return offsetY; 2044 } 2045 2046 private void startHighlighting() { 2047 // System.out.printf("startHighlighting()\n"); 2048 mSurface.mHighlightStep = 0; 2049 mSurface.mFadeColors = true; 2050 // Force a recomputation of the color strips 2051 mSurface.mCachedEndRow = -1; 2052 getDisplay().timerExec(0, mSurface.mHighlightAnimator); 2053 } 2054 2055 private static class RowData { 2056 RowData(Row row) { 2057 mName = row.getName(); 2058 mStack = new ArrayList<Block>(); 2059 } 2060 2061 public void push(Block block) { 2062 mStack.add(block); 2063 } 2064 2065 public Block top() { 2066 if (mStack.size() == 0) 2067 return null; 2068 return mStack.get(mStack.size() - 1); 2069 } 2070 2071 public void pop() { 2072 if (mStack.size() == 0) 2073 return; 2074 mStack.remove(mStack.size() - 1); 2075 } 2076 2077 private String mName; 2078 private int mRank; 2079 private long mElapsed; 2080 private long mEndTime; 2081 private ArrayList<Block> mStack; 2082 } 2083 2084 private static class Segment { 2085 Segment(RowData rowData, Block block, long startTime, long endTime) { 2086 mRowData = rowData; 2087 if (block.isContextSwitch()) { 2088 mBlock = block.getParentBlock(); 2089 mIsContextSwitch = true; 2090 } else { 2091 mBlock = block; 2092 } 2093 mStartTime = startTime; 2094 mEndTime = endTime; 2095 } 2096 2097 private RowData mRowData; 2098 private Block mBlock; 2099 private long mStartTime; 2100 private long mEndTime; 2101 private boolean mIsContextSwitch; 2102 } 2103 2104 private static class Strip { 2105 Strip(int x, int y, int width, int height, RowData rowData, 2106 Segment segment, Color color) { 2107 mX = x; 2108 mY = y; 2109 mWidth = width; 2110 mHeight = height; 2111 mRowData = rowData; 2112 mSegment = segment; 2113 mColor = color; 2114 } 2115 2116 int mX; 2117 int mY; 2118 int mWidth; 2119 int mHeight; 2120 RowData mRowData; 2121 Segment mSegment; 2122 Color mColor; 2123 } 2124 2125 private static class Pixel { 2126 public void setFields(int start, double weight, Segment segment, 2127 Color color, RowData rowData) { 2128 mStart = start; 2129 mMaxWeight = weight; 2130 mSegment = segment; 2131 mColor = color; 2132 mRowData = rowData; 2133 } 2134 2135 int mStart = -2; // some value that won't match another pixel 2136 double mMaxWeight; 2137 Segment mSegment; 2138 Color mColor; // we need the color here because it may be faded 2139 RowData mRowData; 2140 } 2141 2142 private static class Range { 2143 Range(int xStart, int width, int y, Color color) { 2144 mXdim.x = xStart; 2145 mXdim.y = width; 2146 mY = y; 2147 mColor = color; 2148 } 2149 2150 Point mXdim = new Point(0, 0); 2151 int mY; 2152 Color mColor; 2153 } 2154 } 2155