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