Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: tools/perf/page_sets/webrtc_cases/peerconnection_audio_files/graph.js

Issue 2761163003: Use local pages for webrtc telemetry tests. (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree.
7 */
8 // taken from chrome://webrtc-internals with jshint adaptions
9
10 'use strict';
11 /* exported TimelineDataSeries, TimelineGraphView */
12
13 // The maximum number of data points bufferred for each stats. Old data points
14 // will be shifted out when the buffer is full.
15 var MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
16
17 var TimelineDataSeries = (function() {
18 /**
19 * @constructor
20 */
21 function TimelineDataSeries() {
22 // List of DataPoints in chronological order.
23 this.dataPoints_ = [];
24
25 // Default color. Should always be overridden prior to display.
26 this.color_ = 'red';
27 // Whether or not the data series should be drawn.
28 this.isVisible_ = true;
29
30 this.cacheStartTime_ = null;
31 this.cacheStepSize_ = 0;
32 this.cacheValues_ = [];
33 }
34
35 TimelineDataSeries.prototype = {
36 /**
37 * @override
38 */
39 toJSON: function() {
40 if (this.dataPoints_.length < 1) {
41 return {};
42 }
43
44 var values = [];
45 for (var i = 0; i < this.dataPoints_.length; ++i) {
46 values.push(this.dataPoints_[i].value);
47 }
48 return {
49 startTime: this.dataPoints_[0].time,
50 endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
51 values: JSON.stringify(values),
52 };
53 },
54
55 /**
56 * Adds a DataPoint to |this| with the specified time and value.
57 * DataPoints are assumed to be received in chronological order.
58 */
59 addPoint: function(timeTicks, value) {
60 var time = new Date(timeTicks);
61 this.dataPoints_.push(new DataPoint(time, value));
62
63 if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {
64 this.dataPoints_.shift();
65 }
66 },
67
68 isVisible: function() {
69 return this.isVisible_;
70 },
71
72 show: function(isVisible) {
73 this.isVisible_ = isVisible;
74 },
75
76 getColor: function() {
77 return this.color_;
78 },
79
80 setColor: function(color) {
81 this.color_ = color;
82 },
83
84 getCount: function() {
85 return this.dataPoints_.length;
86 },
87 /**
88 * Returns a list containing the values of the data series at |count|
89 * points, starting at |startTime|, and |stepSize| milliseconds apart.
90 * Caches values, so showing/hiding individual data series is fast.
91 */
92 getValues: function(startTime, stepSize, count) {
93 // Use cached values, if we can.
94 if (this.cacheStartTime_ === startTime &&
95 this.cacheStepSize_ === stepSize &&
96 this.cacheValues_.length === count) {
97 return this.cacheValues_;
98 }
99
100 // Do all the work.
101 this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
102 this.cacheStartTime_ = startTime;
103 this.cacheStepSize_ = stepSize;
104
105 return this.cacheValues_;
106 },
107
108 /**
109 * Returns the cached |values| in the specified time period.
110 */
111 getValuesInternal_: function(startTime, stepSize, count) {
112 var values = [];
113 var nextPoint = 0;
114 var currentValue = 0;
115 var time = startTime;
116 for (var i = 0; i < count; ++i) {
117 while (nextPoint < this.dataPoints_.length &&
118 this.dataPoints_[nextPoint].time < time) {
119 currentValue = this.dataPoints_[nextPoint].value;
120 ++nextPoint;
121 }
122 values[i] = currentValue;
123 time += stepSize;
124 }
125 return values;
126 }
127 };
128
129 /**
130 * A single point in a data series. Each point has a time, in the form of
131 * milliseconds since the Unix epoch, and a numeric value.
132 * @constructor
133 */
134 function DataPoint(time, value) {
135 this.time = time;
136 this.value = value;
137 }
138
139 return TimelineDataSeries;
140 })();
141
142 var TimelineGraphView = (function() {
143 // Maximum number of labels placed vertically along the sides of the graph.
144 var MAX_VERTICAL_LABELS = 6;
145
146 // Vertical spacing between labels and between the graph and labels.
147 var LABEL_VERTICAL_SPACING = 4;
148 // Horizontal spacing between vertically placed labels and the edges of the
149 // graph.
150 var LABEL_HORIZONTAL_SPACING = 3;
151 // Horizintal spacing between two horitonally placed labels along the bottom
152 // of the graph.
153 //var LABEL_LABEL_HORIZONTAL_SPACING = 25;
154
155 // Length of ticks, in pixels, next to y-axis labels. The x-axis only has
156 // one set of labels, so it can use lines instead.
157 var Y_AXIS_TICK_LENGTH = 10;
158
159 var GRID_COLOR = '#CCC';
160 var TEXT_COLOR = '#000';
161 var BACKGROUND_COLOR = '#FFF';
162
163 var MAX_DECIMAL_PRECISION = 2;
164 /**
165 * @constructor
166 */
167 function TimelineGraphView(divId, canvasId) {
168 this.scrollbar_ = {position_: 0, range_: 0};
169
170 this.graphDiv_ = document.getElementById(divId);
171 this.canvas_ = document.getElementById(canvasId);
172
173 // Set the range and scale of the graph. Times are in milliseconds since
174 // the Unix epoch.
175
176 // All measurements we have must be after this time.
177 this.startTime_ = 0;
178 // The current rightmost position of the graph is always at most this.
179 this.endTime_ = 1;
180
181 this.graph_ = null;
182
183 // Horizontal scale factor, in terms of milliseconds per pixel.
184 this.scale_ = 1000;
185
186 // Initialize the scrollbar.
187 this.updateScrollbarRange_(true);
188 }
189
190 TimelineGraphView.prototype = {
191 setScale: function(scale) {
192 this.scale_ = scale;
193 },
194
195 // Returns the total length of the graph, in pixels.
196 getLength_: function() {
197 var timeRange = this.endTime_ - this.startTime_;
198 // Math.floor is used to ignore the last partial area, of length less
199 // than this.scale_.
200 return Math.floor(timeRange / this.scale_);
201 },
202
203 /**
204 * Returns true if the graph is scrolled all the way to the right.
205 */
206 graphScrolledToRightEdge_: function() {
207 return this.scrollbar_.position_ === this.scrollbar_.range_;
208 },
209
210 /**
211 * Update the range of the scrollbar. If |resetPosition| is true, also
212 * sets the slider to point at the rightmost position and triggers a
213 * repaint.
214 */
215 updateScrollbarRange_: function(resetPosition) {
216 var scrollbarRange = this.getLength_() - this.canvas_.width;
217 if (scrollbarRange < 0) {
218 scrollbarRange = 0;
219 }
220
221 // If we've decreased the range to less than the current scroll position,
222 // we need to move the scroll position.
223 if (this.scrollbar_.position_ > scrollbarRange) {
224 resetPosition = true;
225 }
226
227 this.scrollbar_.range_ = scrollbarRange;
228 if (resetPosition) {
229 this.scrollbar_.position_ = scrollbarRange;
230 this.repaint();
231 }
232 },
233
234 /**
235 * Sets the date range displayed on the graph, switches to the default
236 * scale factor, and moves the scrollbar all the way to the right.
237 */
238 setDateRange: function(startDate, endDate) {
239 this.startTime_ = startDate.getTime();
240 this.endTime_ = endDate.getTime();
241
242 // Safety check.
243 if (this.endTime_ <= this.startTime_) {
244 this.startTime_ = this.endTime_ - 1;
245 }
246
247 this.updateScrollbarRange_(true);
248 },
249
250 /**
251 * Updates the end time at the right of the graph to be the current time.
252 * Specifically, updates the scrollbar's range, and if the scrollbar is
253 * all the way to the right, keeps it all the way to the right. Otherwise,
254 * leaves the view as-is and doesn't redraw anything.
255 */
256 updateEndDate: function(optDate) {
257 this.endTime_ = optDate || (new Date()).getTime();
258 this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
259 },
260
261 getStartDate: function() {
262 return new Date(this.startTime_);
263 },
264
265 /**
266 * Replaces the current TimelineDataSeries with |dataSeries|.
267 */
268 setDataSeries: function(dataSeries) {
269 // Simply recreates the Graph.
270 this.graph_ = new Graph();
271 for (var i = 0; i < dataSeries.length; ++i) {
272 this.graph_.addDataSeries(dataSeries[i]);
273 }
274 this.repaint();
275 },
276
277 /**
278 * Adds |dataSeries| to the current graph.
279 */
280 addDataSeries: function(dataSeries) {
281 if (!this.graph_) {
282 this.graph_ = new Graph();
283 }
284 this.graph_.addDataSeries(dataSeries);
285 this.repaint();
286 },
287
288 /**
289 * Draws the graph on |canvas_|.
290 */
291 repaint: function() {
292 this.repaintTimerRunning_ = false;
293
294 var width = this.canvas_.width;
295 var height = this.canvas_.height;
296 var context = this.canvas_.getContext('2d');
297
298 // Clear the canvas.
299 context.fillStyle = BACKGROUND_COLOR;
300 context.fillRect(0, 0, width, height);
301
302 // Try to get font height in pixels. Needed for layout.
303 var fontHeightString = context.font.match(/([0-9]+)px/)[1];
304 var fontHeight = parseInt(fontHeightString);
305
306 // Safety check, to avoid drawing anything too ugly.
307 if (fontHeightString.length === 0 || fontHeight <= 0 ||
308 fontHeight * 4 > height || width < 50) {
309 return;
310 }
311
312 // Save current transformation matrix so we can restore it later.
313 context.save();
314
315 // The center of an HTML canvas pixel is technically at (0.5, 0.5). This
316 // makes near straight lines look bad, due to anti-aliasing. This
317 // translation reduces the problem a little.
318 context.translate(0.5, 0.5);
319
320 // Figure out what time values to display.
321 var position = this.scrollbar_.position_;
322 // If the entire time range is being displayed, align the right edge of
323 // the graph to the end of the time range.
324 if (this.scrollbar_.range_ === 0) {
325 position = this.getLength_() - this.canvas_.width;
326 }
327 var visibleStartTime = this.startTime_ + position * this.scale_;
328
329 // Make space at the bottom of the graph for the time labels, and then
330 // draw the labels.
331 var textHeight = height;
332 height -= fontHeight + LABEL_VERTICAL_SPACING;
333 this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
334
335 // Draw outline of the main graph area.
336 context.strokeStyle = GRID_COLOR;
337 context.strokeRect(0, 0, width - 1, height - 1);
338
339 if (this.graph_) {
340 // Layout graph and have them draw their tick marks.
341 this.graph_.layout(
342 width, height, fontHeight, visibleStartTime, this.scale_);
343 this.graph_.drawTicks(context);
344
345 // Draw the lines of all graphs, and then draw their labels.
346 this.graph_.drawLines(context);
347 this.graph_.drawLabels(context);
348 }
349
350 // Restore original transformation matrix.
351 context.restore();
352 },
353
354 /**
355 * Draw time labels below the graph. Takes in start time as an argument
356 * since it may not be |startTime_|, when we're displaying the entire
357 * time range.
358 */
359 drawTimeLabels: function(context, width, height, textHeight, startTime) {
360 // Draw the labels 1 minute apart.
361 var timeStep = 1000 * 60;
362
363 // Find the time for the first label. This time is a perfect multiple of
364 // timeStep because of how UTC times work.
365 var time = Math.ceil(startTime / timeStep) * timeStep;
366
367 context.textBaseline = 'bottom';
368 context.textAlign = 'center';
369 context.fillStyle = TEXT_COLOR;
370 context.strokeStyle = GRID_COLOR;
371
372 // Draw labels and vertical grid lines.
373 while (true) {
374 var x = Math.round((time - startTime) / this.scale_);
375 if (x >= width) {
376 break;
377 }
378 var text = (new Date(time)).toLocaleTimeString();
379 context.fillText(text, x, textHeight);
380 context.beginPath();
381 context.lineTo(x, 0);
382 context.lineTo(x, height);
383 context.stroke();
384 time += timeStep;
385 }
386 },
387
388 getDataSeriesCount: function() {
389 if (this.graph_) {
390 return this.graph_.dataSeries_.length;
391 }
392 return 0;
393 },
394
395 hasDataSeries: function(dataSeries) {
396 if (this.graph_) {
397 return this.graph_.hasDataSeries(dataSeries);
398 }
399 return false;
400 },
401
402 };
403
404 /**
405 * A Graph is responsible for drawing all the TimelineDataSeries that have
406 * the same data type. Graphs are responsible for scaling the values, laying
407 * out labels, and drawing both labels and lines for its data series.
408 */
409 var Graph = (function() {
410 /**
411 * @constructor
412 */
413 function Graph() {
414 this.dataSeries_ = [];
415
416 // Cached properties of the graph, set in layout.
417 this.width_ = 0;
418 this.height_ = 0;
419 this.fontHeight_ = 0;
420 this.startTime_ = 0;
421 this.scale_ = 0;
422
423 // The lowest/highest values adjusted by the vertical label step size
424 // in the displayed range of the graph. Used for scaling and setting
425 // labels. Set in layoutLabels.
426 this.min_ = 0;
427 this.max_ = 0;
428
429 // Cached text of equally spaced labels. Set in layoutLabels.
430 this.labels_ = [];
431 }
432
433 /**
434 * A Label is the label at a particular position along the y-axis.
435 * @constructor
436 */
437 /*
438 function Label(height, text) {
439 this.height = height;
440 this.text = text;
441 }
442 */
443
444 Graph.prototype = {
445 addDataSeries: function(dataSeries) {
446 this.dataSeries_.push(dataSeries);
447 },
448
449 hasDataSeries: function(dataSeries) {
450 for (var i = 0; i < this.dataSeries_.length; ++i) {
451 if (this.dataSeries_[i] === dataSeries) {
452 return true;
453 }
454 }
455 return false;
456 },
457
458 /**
459 * Returns a list of all the values that should be displayed for a given
460 * data series, using the current graph layout.
461 */
462 getValues: function(dataSeries) {
463 if (!dataSeries.isVisible()) {
464 return null;
465 }
466 return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
467 },
468
469 /**
470 * Updates the graph's layout. In particular, both the max value and
471 * label positions are updated. Must be called before calling any of the
472 * drawing functions.
473 */
474 layout: function(width, height, fontHeight, startTime, scale) {
475 this.width_ = width;
476 this.height_ = height;
477 this.fontHeight_ = fontHeight;
478 this.startTime_ = startTime;
479 this.scale_ = scale;
480
481 // Find largest value.
482 var max = 0;
483 var min = 0;
484 for (var i = 0; i < this.dataSeries_.length; ++i) {
485 var values = this.getValues(this.dataSeries_[i]);
486 if (!values) {
487 continue;
488 }
489 for (var j = 0; j < values.length; ++j) {
490 if (values[j] > max) {
491 max = values[j];
492 } else if (values[j] < min) {
493 min = values[j];
494 }
495 }
496 }
497
498 this.layoutLabels_(min, max);
499 },
500
501 /**
502 * Lays out labels and sets |max_|/|min_|, taking the time units into
503 * consideration. |maxValue| is the actual maximum value, and
504 * |max_| will be set to the value of the largest label, which
505 * will be at least |maxValue|. Similar for |min_|.
506 */
507 layoutLabels_: function(minValue, maxValue) {
508 if (maxValue - minValue < 1024) {
509 this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
510 return;
511 }
512
513 // Find appropriate units to use.
514 var units = ['', 'k', 'M', 'G', 'T', 'P'];
515 // Units to use for labels. 0 is '1', 1 is K, etc.
516 // We start with 1, and work our way up.
517 var unit = 1;
518 minValue /= 1024;
519 maxValue /= 1024;
520 while (units[unit + 1] && maxValue - minValue >= 1024) {
521 minValue /= 1024;
522 maxValue /= 1024;
523 ++unit;
524 }
525
526 // Calculate labels.
527 this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
528
529 // Append units to labels.
530 for (var i = 0; i < this.labels_.length; ++i) {
531 this.labels_[i] += ' ' + units[unit];
532 }
533
534 // Convert |min_|/|max_| back to unit '1'.
535 this.min_ *= Math.pow(1024, unit);
536 this.max_ *= Math.pow(1024, unit);
537 },
538
539 /**
540 * Same as layoutLabels_, but ignores units. |maxDecimalDigits| is the
541 * maximum number of decimal digits allowed. The minimum allowed
542 * difference between two adjacent labels is 10^-|maxDecimalDigits|.
543 */
544 layoutLabelsBasic_: function(minValue, maxValue, maxDecimalDigits) {
545 this.labels_ = [];
546 var range = maxValue - minValue;
547 // No labels if the range is 0.
548 if (range === 0) {
549 this.min_ = this.max_ = maxValue;
550 return;
551 }
552
553 // The maximum number of equally spaced labels allowed. |fontHeight_|
554 // is doubled because the top two labels are both drawn in the same
555 // gap.
556 var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
557
558 // The + 1 is for the top label.
559 var maxLabels = 1 + this.height_ / minLabelSpacing;
560 if (maxLabels < 2) {
561 maxLabels = 2;
562 } else if (maxLabels > MAX_VERTICAL_LABELS) {
563 maxLabels = MAX_VERTICAL_LABELS;
564 }
565
566 // Initial try for step size between conecutive labels.
567 var stepSize = Math.pow(10, -maxDecimalDigits);
568 // Number of digits to the right of the decimal of |stepSize|.
569 // Used for formating label strings.
570 var stepSizeDecimalDigits = maxDecimalDigits;
571
572 // Pick a reasonable step size.
573 while (true) {
574 // If we use a step size of |stepSize| between labels, we'll need:
575 //
576 // Math.ceil(range / stepSize) + 1
577 //
578 // labels. The + 1 is because we need labels at both at 0 and at
579 // the top of the graph.
580
581 // Check if we can use steps of size |stepSize|.
582 if (Math.ceil(range / stepSize) + 1 <= maxLabels) {
583 break;
584 }
585 // Check |stepSize| * 2.
586 if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {
587 stepSize *= 2;
588 break;
589 }
590 // Check |stepSize| * 5.
591 if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {
592 stepSize *= 5;
593 break;
594 }
595 stepSize *= 10;
596 if (stepSizeDecimalDigits > 0) {
597 --stepSizeDecimalDigits;
598 }
599 }
600
601 // Set the min/max so it's an exact multiple of the chosen step size.
602 this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
603 this.min_ = Math.floor(minValue / stepSize) * stepSize;
604
605 // Create labels.
606 for (var label = this.max_; label >= this.min_; label -= stepSize) {
607 this.labels_.push(label.toFixed(stepSizeDecimalDigits));
608 }
609 },
610
611 /**
612 * Draws tick marks for each of the labels in |labels_|.
613 */
614 drawTicks: function(context) {
615 var x1;
616 var x2;
617 x1 = this.width_ - 1;
618 x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
619
620 context.fillStyle = GRID_COLOR;
621 context.beginPath();
622 for (var i = 1; i < this.labels_.length - 1; ++i) {
623 // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
624 // lines.
625 var y = Math.round(this.height_ * i / (this.labels_.length - 1));
626 context.moveTo(x1, y);
627 context.lineTo(x2, y);
628 }
629 context.stroke();
630 },
631
632 /**
633 * Draws a graph line for each of the data series.
634 */
635 drawLines: function(context) {
636 // Factor by which to scale all values to convert them to a number from
637 // 0 to height - 1.
638 var scale = 0;
639 var bottom = this.height_ - 1;
640 if (this.max_) {
641 scale = bottom / (this.max_ - this.min_);
642 }
643
644 // Draw in reverse order, so earlier data series are drawn on top of
645 // subsequent ones.
646 for (var i = this.dataSeries_.length - 1; i >= 0; --i) {
647 var values = this.getValues(this.dataSeries_[i]);
648 if (!values) {
649 continue;
650 }
651 context.strokeStyle = this.dataSeries_[i].getColor();
652 context.beginPath();
653 for (var x = 0; x < values.length; ++x) {
654 // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
655 // horizontal lines.
656 context.lineTo(
657 x, bottom - Math.round((values[x] - this.min_) * scale));
658 }
659 context.stroke();
660 }
661 },
662
663 /**
664 * Draw labels in |labels_|.
665 */
666 drawLabels: function(context) {
667 if (this.labels_.length === 0) {
668 return;
669 }
670 var x = this.width_ - LABEL_HORIZONTAL_SPACING;
671
672 // Set up the context.
673 context.fillStyle = TEXT_COLOR;
674 context.textAlign = 'right';
675
676 // Draw top label, which is the only one that appears below its tick
677 // mark.
678 context.textBaseline = 'top';
679 context.fillText(this.labels_[0], x, 0);
680
681 // Draw all the other labels.
682 context.textBaseline = 'bottom';
683 var step = (this.height_ - 1) / (this.labels_.length - 1);
684 for (var i = 1; i < this.labels_.length; ++i) {
685 context.fillText(this.labels_[i], x, step * i);
686 }
687 }
688 };
689
690 return Graph;
691 })();
692
693 return TimelineGraphView;
694 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698