12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)//
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// This file contains helper methods to draw the stats timeline graphs.
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Each graph represents a series of stats report for a PeerConnection,
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// e.g. 1234-0-ssrc-abcd123-bytesSent is the graph for the series of bytesSent
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// for ssrc-abcd123 of PeerConnection 0 in process 1234.
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// The graphs are drawn as CANVAS, grouped per report type per PeerConnection.
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Each group has an expand/collapse button and is collapsed initially.
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)//
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)<include src="timeline_graph_view.js"/>
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
16c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)var STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading';
17c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)var RECEIVED_PROPAGATION_DELTA_LABEL =
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    'googReceivedPacketGroupPropagationDeltaDebug';
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)var RECEIVED_PACKET_GROUP_ARRIVAL_TIME_LABEL =
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    'googReceivedPacketGroupArrivalTimeDebug';
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Specifies which stats should be drawn on the 'bweCompound' graph and how.
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var bweCompoundGraphConfig = {
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  googAvailableSendBandwidth: {color: 'red'},
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  googTargetEncBitrateCorrected: {color: 'purple'},
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  googActualEncBitrate: {color: 'orange'},
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  googRetransmitBitrate: {color: 'blue'},
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  googTransmitBitrate: {color: 'green'},
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Converts the last entry of |srcDataSeries| from the total amount to the
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// amount per second.
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var totalToPerSecond = function(srcDataSeries) {
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var length = srcDataSeries.dataPoints_.length;
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (length >= 2) {
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var secondLastDataPoint = srcDataSeries.dataPoints_[length - 2];
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (lastDataPoint.value - secondLastDataPoint.value) * 1000 /
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)           (lastDataPoint.time - secondLastDataPoint.time);
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return 0;
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Converts the value of total bytes to bits per second.
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var totalBytesToBitsPerSecond = function(srcDataSeries) {
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return totalToPerSecond(srcDataSeries) * 8;
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Specifies which stats should be converted before drawn and how.
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// |convertedName| is the name of the converted value, |convertFunction|
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// is the function used to calculate the new converted value based on the
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// original dataSeries.
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var dataConversionConfig = {
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  packetsSent: {
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertedName: 'packetsSentPerSecond',
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertFunction: totalToPerSecond,
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  },
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  bytesSent: {
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertedName: 'bitsSentPerSecond',
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertFunction: totalBytesToBitsPerSecond,
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  },
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  packetsReceived: {
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertedName: 'packetsReceivedPerSecond',
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertFunction: totalToPerSecond,
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  },
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  bytesReceived: {
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertedName: 'bitsReceivedPerSecond',
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertFunction: totalBytesToBitsPerSecond,
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  },
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // This is due to a bug of wrong units reported for googTargetEncBitrate.
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // TODO (jiayl): remove this when the unit bug is fixed.
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  googTargetEncBitrate: {
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertedName: 'googTargetEncBitrateCorrected',
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    convertFunction: function (srcDataSeries) {
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      var length = srcDataSeries.dataPoints_.length;
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if (lastDataPoint.value < 5000)
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return lastDataPoint.value * 1000;
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return lastDataPoint.value;
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
86a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
87a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// The object contains the stats names that should not be added to the graph,
88a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// even if they are numbers.
89a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)var statsNameBlackList = {
90a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  'ssrc': true,
91a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  'googTrackId': true,
92868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  'googComponent': true,
93868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  'googLocalAddress': true,
94868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  'googRemoteAddress': true,
95f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  'googFingerprint': true,
96a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)};
97a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var graphViews = {};
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
100868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Returns number parsed from |value|, or NaN if the stats name is black-listed.
101868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)function getNumberFromValue(name, value) {
102868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (statsNameBlackList[name])
103868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return NaN;
104868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  return parseFloat(value);
105868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
106868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
107b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)// Adds the stats report |report| to the timeline graph for the given
108b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)// |peerConnectionElement|.
109b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)function drawSingleReport(peerConnectionElement, report) {
110b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  var reportType = report.type;
111b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  var reportId = report.id;
112b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  var stats = report.stats;
113b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  if (!stats || !stats.values)
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return;
115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
116b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  for (var i = 0; i < stats.values.length - 1; i = i + 2) {
117b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    var rawLabel = stats.values[i];
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // Propagation deltas are handled separately.
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (rawLabel == RECEIVED_PROPAGATION_DELTA_LABEL) {
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      drawReceivedPropagationDelta(
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          peerConnectionElement, report, stats.values[i + 1]);
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      continue;
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
12490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    var rawDataSeriesId = reportId + '-' + rawLabel;
125868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    var rawValue = getNumberFromValue(rawLabel, stats.values[i + 1]);
126868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if (isNaN(rawValue)) {
127868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      // We do not draw non-numerical values, but still want to record it in the
128868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      // data series.
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      addDataSeriesPoints(peerConnectionElement,
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          rawDataSeriesId,
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          rawLabel,
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          [stats.timestamp],
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          [stats.values[i + 1]]);
134868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      continue;
135868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    }
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var finalDataSeriesId = rawDataSeriesId;
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var finalLabel = rawLabel;
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var finalValue = rawValue;
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // We need to convert the value if dataConversionConfig[rawLabel] exists.
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (dataConversionConfig[rawLabel]) {
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // Updates the original dataSeries before the conversion.
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      addDataSeriesPoints(peerConnectionElement,
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          rawDataSeriesId,
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          rawLabel,
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          [stats.timestamp],
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          [rawValue]);
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // Convert to another value to draw on graph, using the original
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // dataSeries as input.
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      finalValue = dataConversionConfig[rawLabel].convertFunction(
15290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)          peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
15390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)              rawDataSeriesId));
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      finalLabel = dataConversionConfig[rawLabel].convertedName;
15590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      finalDataSeriesId = reportId + '-' + finalLabel;
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Updates the final dataSeries to draw.
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    addDataSeriesPoints(peerConnectionElement,
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        finalDataSeriesId,
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        finalLabel,
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        [stats.timestamp],
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        [finalValue]);
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Updates the graph.
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var graphType = bweCompoundGraphConfig[finalLabel] ?
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    'bweCompound' : finalLabel;
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var graphViewId =
169a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        peerConnectionElement.id + '-' + reportId + '-' + graphType;
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (!graphViews[graphViewId]) {
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      graphViews[graphViewId] = createStatsGraphView(peerConnectionElement,
173b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)                                                     report,
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                                     graphType);
175b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      var date = new Date(stats.timestamp);
1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      graphViews[graphViewId].setDateRange(date, date);
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Adds the new dataSeries to the graphView. We have to do it here to cover
1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // both the simple and compound graph cases.
18090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    var dataSeries =
18190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
18290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)            finalDataSeriesId);
18390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    if (!graphViews[graphViewId].hasDataSeries(dataSeries))
18490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      graphViews[graphViewId].addDataSeries(dataSeries);
1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    graphViews[graphViewId].updateEndDate();
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Makes sure the TimelineDataSeries with id |dataSeriesId| is created,
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// and adds the new data points to it. |times| is the list of timestamps for
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// each data point, and |values| is the list of the data point values.
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)function addDataSeriesPoints(
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    peerConnectionElement, dataSeriesId, label, times, values) {
19490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  var dataSeries =
19590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
19690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        dataSeriesId);
19790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (!dataSeries) {
19890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    dataSeries = new TimelineDataSeries();
19990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    peerConnectionDataStore[peerConnectionElement.id].setDataSeries(
20090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        dataSeriesId, dataSeries);
2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (bweCompoundGraphConfig[label]) {
20290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      dataSeries.setColor(bweCompoundGraphConfig[label].color);
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (var i = 0; i < times.length; ++i)
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    dataSeries.addPoint(times[i], values[i]);
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Draws the received propagation deltas using the packet group arrival time as
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// the x-axis. For example, |report.stats.values| should be like
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// ['googReceivedPacketGroupArrivalTimeDebug', '[123456, 234455, 344566]',
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)//  'googReceivedPacketGroupPropagationDeltaDebug', '[23, 45, 56]', ...].
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)function drawReceivedPropagationDelta(peerConnectionElement, report, deltas) {
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var reportId = report.id;
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var stats = report.stats;
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var times = null;
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Find the packet group arrival times.
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (var i = 0; i < stats.values.length - 1; i = i + 2) {
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (stats.values[i] == RECEIVED_PACKET_GROUP_ARRIVAL_TIME_LABEL) {
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      times = stats.values[i + 1];
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      break;
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Unexpected.
2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (times == null)
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return;
2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Convert |deltas| and |times| from strings to arrays of numbers.
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  try {
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    deltas = JSON.parse(deltas);
2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    times = JSON.parse(times);
2325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } catch (e) {
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    console.log(e);
2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return;
2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Update the data series.
2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var dataSeriesId = reportId + '-' + RECEIVED_PROPAGATION_DELTA_LABEL;
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  addDataSeriesPoints(
2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      peerConnectionElement,
2415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      dataSeriesId,
2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      RECEIVED_PROPAGATION_DELTA_LABEL,
2435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      times,
2445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      deltas);
2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Update the graph.
2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var graphViewId = peerConnectionElement.id + '-' + reportId + '-' +
2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      RECEIVED_PROPAGATION_DELTA_LABEL;
2485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var date = new Date(times[times.length - 1]);
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (!graphViews[graphViewId]) {
2505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    graphViews[graphViewId] = createStatsGraphView(
2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        peerConnectionElement,
2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        report,
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        RECEIVED_PROPAGATION_DELTA_LABEL);
2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    graphViews[graphViewId].setScale(10);
2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    graphViews[graphViewId].setDateRange(date, date);
2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    var dataSeries = peerConnectionDataStore[peerConnectionElement.id]
2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        .getDataSeries(dataSeriesId);
2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    graphViews[graphViewId].addDataSeries(dataSeries);
2595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  graphViews[graphViewId].updateEndDate(date);
2612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
263c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Ensures a div container to hold all stats graphs for one track is created as
264c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// a child of |peerConnectionElement|.
265b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)function ensureStatsGraphTopContainer(peerConnectionElement, report) {
2662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var containerId = peerConnectionElement.id + '-' +
267b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      report.type + '-' + report.id + '-graph-container';
2682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var container = $(containerId);
2692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (!container) {
270c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    container = document.createElement('details');
2712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    container.id = containerId;
272c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    container.className = 'stats-graph-container';
2732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    peerConnectionElement.appendChild(container);
275c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    container.innerHTML ='<summary><span></span></summary>';
276c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    container.firstChild.firstChild.className =
277c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        STATS_GRAPH_CONTAINER_HEADING_CLASS;
278c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    container.firstChild.firstChild.textContent =
279a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        'Stats graphs for ' + report.id;
280c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
281b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    if (report.type == 'ssrc') {
282c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      var ssrcInfoElement = document.createElement('div');
283c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      container.firstChild.appendChild(ssrcInfoElement);
284b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      ssrcInfoManager.populateSsrcInfo(ssrcInfoElement,
285b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)                                       GetSsrcFromReport(report));
286c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    }
2872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
2882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return container;
2892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Creates the container elements holding a timeline graph
2922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// and the TimelineGraphView object.
293c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)function createStatsGraphView(
294b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    peerConnectionElement, report, statsName) {
2952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var topContainer = ensureStatsGraphTopContainer(peerConnectionElement,
296b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)                                                  report);
2972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
29890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  var graphViewId =
29990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      peerConnectionElement.id + '-' + report.id + '-' + statsName;
3002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var divId = graphViewId + '-div';
3012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var canvasId = graphViewId + '-canvas';
3022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var container = document.createElement("div");
3032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  container.className = 'stats-graph-sub-container';
3042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  topContainer.appendChild(container);
3062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  container.innerHTML = '<div>' + statsName + '</div>' +
3072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '<div id=' + divId + '><canvas id=' + canvasId + '></canvas></div>';
3082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (statsName == 'bweCompound') {
309c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      container.insertBefore(
31090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)          createBweCompoundLegend(peerConnectionElement, report.id),
311c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          $(divId));
3122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
3132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return new TimelineGraphView(divId, canvasId);
3142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
3152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Creates the legend section for the bweCompound graph.
3172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Returns the legend element.
31890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)function createBweCompoundLegend(peerConnectionElement, reportId) {
3192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var legend = document.createElement('div');
3202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  for (var prop in bweCompoundGraphConfig) {
3212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var div = document.createElement('div');
3222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    legend.appendChild(div);
3232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    div.innerHTML = '<input type=checkbox checked></input>' + prop;
3242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    div.style.color = bweCompoundGraphConfig[prop].color;
32590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    div.dataSeriesId = reportId + '-' + prop;
3262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    div.graphViewId =
32790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        peerConnectionElement.id + '-' + reportId + '-bweCompound';
3282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    div.firstChild.addEventListener('click', function(event) {
32990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        var target =
33090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)            peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
33190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                event.target.parentNode.dataSeriesId);
3322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        target.show(event.target.checked);
3332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        graphViews[event.target.parentNode.graphViewId].repaint();
3342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    });
3352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
3362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return legend;
3372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
338