• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/ui/base/event_presenter.html">
9<link rel="import" href="/tracing/base/sorted_array_utils.html">
10<link rel="import" href="/tracing/ui/base/elided_cache.html">
11
12<script>
13'use strict';
14
15/**
16 * @fileoverview Provides various helper methods for drawing to a provided
17 * canvas.
18 */
19tr.exportTo('tr.ui.b', function() {
20  var elidedTitleCache = new tr.ui.b.ElidedTitleCache();
21  var ColorScheme = tr.b.ColorScheme;
22  var colorsAsStrings = ColorScheme.colorsAsStrings;
23
24  var EventPresenter = tr.ui.b.EventPresenter;
25  var blackColorId = ColorScheme.getColorIdForReservedName('black');
26
27  /**
28   * This value is used to allow for consistent style UI elements.
29   * Thread time visualisation uses a smaller rectangle that has this height.
30   * @const
31   */
32  var THIN_SLICE_HEIGHT = 4;
33
34  /**
35   * This value is used to for performance considerations when drawing large
36   * zoomed out traces that feature cpu time in the slices. If the waiting
37   * width is less than the threshold, we only draw the rectangle as a solid.
38   * @const
39   */
40  var SLICE_WAITING_WIDTH_DRAW_THRESHOLD = 3;
41
42  /**
43   * If the slice has mostly been waiting to be scheduled on the cpu, the
44   * wall clock will be far greater than the cpu clock. Draw the slice
45   * only as an idle slice, if the active width is not thicker than the
46   * threshold.
47   * @const
48   */
49  var SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD = 1;
50
51  /**
52   * Should we elide text on trace labels?
53   * Without eliding, text that is too wide isn't drawn at all.
54   * Disable if you feel this causes a performance problem.
55   * This is a default value that can be overridden in tracks for testing.
56   * @const
57   */
58  var SHOULD_ELIDE_TEXT = true;
59
60  /**
61   * Draw the define line into |ctx|.
62   *
63   * @param {Context} ctx The context to draw into.
64   * @param {float} x1 The start x position of the line.
65   * @param {float} y1 The start y position of the line.
66   * @param {float} x2 The end x position of the line.
67   * @param {float} y2 The end y position of the line.
68   */
69  function drawLine(ctx, x1, y1, x2, y2) {
70    ctx.moveTo(x1, y1);
71    ctx.lineTo(x2, y2);
72  }
73
74  /**
75   * Draw the defined triangle into |ctx|.
76   *
77   * @param {Context} ctx The context to draw into.
78   * @param {float} x1 The first corner x.
79   * @param {float} y1 The first corner y.
80   * @param {float} x2 The second corner x.
81   * @param {float} y2 The second corner y.
82   * @param {float} x3 The third corner x.
83   * @param {float} y3 The third corner y.
84   */
85  function drawTriangle(ctx, x1, y1, x2, y2, x3, y3) {
86    ctx.beginPath();
87    ctx.moveTo(x1, y1);
88    ctx.lineTo(x2, y2);
89    ctx.lineTo(x3, y3);
90    ctx.closePath();
91  }
92
93  /**
94   * Draw an arrow into |ctx|.
95   *
96   * @param {Context} ctx The context to draw into.
97   * @param {float} x1 The shaft x.
98   * @param {float} y1 The shaft y.
99   * @param {float} x2 The head x.
100   * @param {float} y2 The head y.
101   * @param {float} arrowLength The length of the head.
102   * @param {float} arrowWidth The width of the head.
103   */
104  function drawArrow(ctx, x1, y1, x2, y2, arrowLength, arrowWidth) {
105    var dx = x2 - x1;
106    var dy = y2 - y1;
107    var len = Math.sqrt(dx * dx + dy * dy);
108    var perc = (len - arrowLength) / len;
109    var bx = x1 + perc * dx;
110    var by = y1 + perc * dy;
111    var ux = dx / len;
112    var uy = dy / len;
113    var ax = uy * arrowWidth;
114    var ay = -ux * arrowWidth;
115
116    ctx.beginPath();
117    drawLine(ctx, x1, y1, x2, y2);
118    ctx.stroke();
119
120    drawTriangle(ctx,
121        bx + ax, by + ay,
122        x2, y2,
123        bx - ax, by - ay);
124    ctx.fill();
125  }
126
127  /**
128   * Draw the provided slices to the screen.
129   *
130   * Each of the elements in |slices| must provide the follow methods:
131   *   * start
132   *   * duration
133   *   * colorId
134   *   * selected
135   *
136   * @param {Context} ctx The canvas context.
137   * @param {TimelineDrawTransform} dt The draw transform.
138   * @param {float} viewLWorld The left most point of the world viewport.
139   * @param {float} viewRWorld The right most point of the world viewport.
140   * @param {float} viewHeight The height of the viewport.
141   * @param {Array} slices The slices to draw.
142   * @param {bool} async Whether the slices are drawn with async style.
143   */
144  function drawSlices(ctx, dt, viewLWorld, viewRWorld, viewHeight, slices,
145                      async) {
146    var pixelRatio = window.devicePixelRatio || 1;
147    var pixWidth = dt.xViewVectorToWorld(1);
148    var height = viewHeight * pixelRatio;
149
150    var darkRectHeight = THIN_SLICE_HEIGHT * pixelRatio;
151
152    // Not enough space for both colors, use light color only.
153    if (height < darkRectHeight)
154      darkRectHeight = 0;
155
156    var lightRectHeight = height - darkRectHeight;
157
158    // Begin rendering in world space.
159    ctx.save();
160    dt.applyTransformToCanvas(ctx);
161
162    var rect = new tr.ui.b.FastRectRenderer(
163        ctx, 2 * pixWidth, 2 * pixWidth, colorsAsStrings);
164    rect.setYandH(0, height);
165
166    var lowSlice = tr.b.findLowIndexInSortedArray(
167        slices,
168        function(slice) { return slice.start + slice.duration; },
169        viewLWorld);
170
171    var hadTopLevel = false;
172
173    for (var i = lowSlice; i < slices.length; ++i) {
174      var slice = slices[i];
175      var x = slice.start;
176      if (x > viewRWorld)
177        break;
178
179      var w = pixWidth;
180      if (slice.duration > 0) {
181        w = Math.max(slice.duration, 0.000001);
182        if (w < pixWidth)
183          w = pixWidth;
184      }
185
186      var colorId = EventPresenter.getSliceColorId(slice);
187      var alpha = EventPresenter.getSliceAlpha(slice, async);
188      var lightAlpha = alpha * 0.70;
189
190      if (async && slice.isTopLevel) {
191        rect.setYandH(3, height - 3);
192        hadTopLevel = true;
193      } else {
194        rect.setYandH(0, height);
195      }
196
197      // If cpuDuration is available, draw rectangles proportional to the
198      // amount of cpu time taken.
199      if (!slice.cpuDuration) {
200        // No cpuDuration available, draw using only one alpha.
201        rect.fillRect(x, w, colorId, alpha);
202        continue;
203      }
204
205      var activeWidth = w * (slice.cpuDuration / slice.duration);
206      var waitingWidth = w - activeWidth;
207
208      // Check if we have enough screen space to draw the whole slice, with
209      // both color tones.
210      //
211      // Truncate the activeWidth to 0 if it is less than 'threshold' pixels.
212      if (activeWidth < SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD * pixWidth) {
213        activeWidth = 0;
214        waitingWidth = w;
215      }
216
217      // Truncate the waitingWidth to 0 if it is less than 'threshold' pixels.
218      if (waitingWidth < SLICE_WAITING_WIDTH_DRAW_THRESHOLD * pixWidth) {
219        activeWidth = w;
220        waitingWidth = 0;
221      }
222
223      // We now draw the two rectangles making up the event slice.
224      // NOTE: The if statements are necessary for performance considerations.
225      // We do not want to force draws, if the width of the rectangle is 0.
226      //
227      // First draw the solid color, representing the 'active' part.
228      if (activeWidth > 0) {
229        rect.fillRect(x, activeWidth, colorId, alpha);
230      }
231
232      // Next draw the two toned 'idle' part.
233      // NOTE: Substracting pixWidth and drawing one extra pixel is done to
234      // prevent drawing artifacts. Without it, the two parts of the slice,
235      // ('active' and 'idle') may appear split apart.
236      if (waitingWidth > 0) {
237        // First draw the light toned top part.
238        rect.setYandH(0, lightRectHeight);
239        rect.fillRect(x + activeWidth - pixWidth,
240            waitingWidth + pixWidth, colorId, lightAlpha);
241        // Then the solid bottom half.
242        rect.setYandH(lightRectHeight, darkRectHeight);
243        rect.fillRect(x + activeWidth - pixWidth,
244            waitingWidth + pixWidth, colorId, alpha);
245        // Reset for the next slice.
246        rect.setYandH(0, height);
247      }
248    }
249    rect.flush();
250
251    if (async && hadTopLevel) {
252      // Draw a top border over async slices in order to visually separate
253      // them from events above it.
254      // See https://github.com/google/trace-viewer/issues/725.
255      rect.setYandH(2, 1);
256      for (var i = lowSlice; i < slices.length; ++i) {
257        var slice = slices[i];
258        var x = slice.start;
259        if (x > viewRWorld)
260          break;
261
262        if (!slice.isTopLevel)
263          continue;
264
265        var w = pixWidth;
266        if (slice.duration > 0) {
267          w = Math.max(slice.duration, 0.000001);
268          if (w < pixWidth)
269            w = pixWidth;
270        }
271
272        rect.fillRect(x, w, blackColorId, 0.7);
273      }
274      rect.flush();
275    }
276
277    ctx.restore();
278  }
279
280  /**
281   * Draw the provided instant slices as lines to the screen.
282   *
283   * Each of the elements in |slices| must provide the follow methods:
284   *   * start
285   *   * duration with value of 0.
286   *   * colorId
287   *   * selected
288   *
289   * @param {Context} ctx The canvas context.
290   * @param {TimelineDrawTransform} dt The draw transform.
291   * @param {float} viewLWorld The left most point of the world viewport.
292   * @param {float} viewRWorld The right most point of the world viewport.
293   * @param {float} viewHeight The height of the viewport.
294   * @param {Array} slices The slices to draw.
295   * @param {Numer} lineWidthInPixels The width of the lines.
296   */
297  function drawInstantSlicesAsLines(
298      ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, lineWidthInPixels) {
299    var pixelRatio = window.devicePixelRatio || 1;
300    var height = viewHeight * pixelRatio;
301
302    var pixWidth = dt.xViewVectorToWorld(1);
303
304    // Begin rendering in world space.
305    ctx.save();
306    ctx.lineWidth = pixWidth * lineWidthInPixels * pixelRatio;
307    dt.applyTransformToCanvas(ctx);
308    ctx.beginPath();
309
310    var lowSlice = tr.b.findLowIndexInSortedArray(
311        slices,
312        function(slice) { return slice.start; },
313        viewLWorld);
314
315    for (var i = lowSlice; i < slices.length; ++i) {
316      var slice = slices[i];
317      var x = slice.start;
318      if (x > viewRWorld)
319        break;
320
321      ctx.strokeStyle = EventPresenter.getInstantSliceColor(slice);
322
323      ctx.beginPath();
324      ctx.moveTo(x, 0);
325      ctx.lineTo(x, height);
326      ctx.stroke();
327    }
328    ctx.restore();
329  }
330
331  /**
332   * Draws the labels for the given slices.
333   *
334   * The |slices| array must contain objects with the following API:
335   *   * start
336   *   * duration
337   *   * title
338   *   * didNotFinish (optional)
339   *
340   * @param {Context} ctx The graphics context.
341   * @param {TimelineDrawTransform} dt The draw transform.
342   * @param {float} viewLWorld The left most point of the world viewport.
343   * @param {float} viewRWorld The right most point of the world viewport.
344   * @param {Array} slices The slices to label.
345   * @param {bool} async Whether the slice labels are drawn with async style.
346   * @param {float} fontSize The font size.
347   * @param {float} yOffset The font offset.
348   */
349  function drawLabels(ctx, dt, viewLWorld, viewRWorld, slices, async,
350                      fontSize, yOffset) {
351    var pixelRatio = window.devicePixelRatio || 1;
352    var pixWidth = dt.xViewVectorToWorld(1);
353
354    ctx.save();
355
356    ctx.textAlign = 'center';
357    ctx.textBaseline = 'top';
358    ctx.font = (fontSize * pixelRatio) + 'px sans-serif';
359
360    if (async)
361      ctx.font = 'italic ' + ctx.font;
362
363    var cY = yOffset * pixelRatio;
364
365    var lowSlice = tr.b.findLowIndexInSortedArray(
366        slices,
367        function(slice) { return slice.start + slice.duration; },
368        viewLWorld);
369
370    // Don't render text until until it is 20px wide
371    var quickDiscardThresshold = pixWidth * 20;
372    for (var i = lowSlice; i < slices.length; ++i) {
373      var slice = slices[i];
374      if (slice.start > viewRWorld)
375        break;
376
377      if (slice.duration <= quickDiscardThresshold)
378        continue;
379
380      var title = slice.title +
381          (slice.didNotFinish ? ' (Did Not Finish)' : '');
382
383      var drawnTitle = title;
384      var drawnWidth = elidedTitleCache.labelWidth(ctx, drawnTitle);
385      var fullLabelWidth = elidedTitleCache.labelWidthWorld(
386          ctx, drawnTitle, pixWidth);
387      if (SHOULD_ELIDE_TEXT && fullLabelWidth > slice.duration) {
388        var elidedValues = elidedTitleCache.get(
389            ctx, pixWidth,
390            drawnTitle, drawnWidth,
391            slice.duration);
392        drawnTitle = elidedValues.string;
393        drawnWidth = elidedValues.width;
394      }
395
396      if (drawnWidth * pixWidth < slice.duration) {
397        ctx.fillStyle = EventPresenter.getTextColor(slice);
398        var cX = dt.xWorldToView(slice.start + 0.5 * slice.duration);
399        ctx.fillText(drawnTitle, cX, cY, drawnWidth);
400      }
401    }
402    ctx.restore();
403  }
404
405  return {
406    drawSlices: drawSlices,
407    drawInstantSlicesAsLines: drawInstantSlicesAsLines,
408    drawLabels: drawLabels,
409
410    drawLine: drawLine,
411    drawTriangle: drawTriangle,
412    drawArrow: drawArrow,
413
414    elidedTitleCache_: elidedTitleCache,
415
416    THIN_SLICE_HEIGHT: THIN_SLICE_HEIGHT
417  };
418});
419</script>
420