1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 // This file contains helper methods to draw the stats timeline graphs.
7 // Each graph represents a series of stats report for a PeerConnection,
8 // e.g. 1234-0-ssrc-abcd123-bytesSent is the graph for the series of bytesSent
9 // for ssrc-abcd123 of PeerConnection 0 in process 1234.
10 // The graphs are drawn as CANVAS, grouped per report type per PeerConnection.
11 // Each group has an expand/collapse button and is collapsed initially.
14 <include src
="timeline_graph_view.js"/>
16 var STATS_GRAPH_CONTAINER_HEADING_CLASS
= 'stats-graph-container-heading';
18 var RECEIVED_PROPAGATION_DELTA_LABEL
=
19 'googReceivedPacketGroupPropagationDeltaDebug';
20 var RECEIVED_PACKET_GROUP_ARRIVAL_TIME_LABEL
=
21 'googReceivedPacketGroupArrivalTimeDebug';
23 // Specifies which stats should be drawn on the 'bweCompound' graph and how.
24 var bweCompoundGraphConfig
= {
25 googAvailableSendBandwidth
: {color
: 'red'},
26 googTargetEncBitrateCorrected
: {color
: 'purple'},
27 googActualEncBitrate
: {color
: 'orange'},
28 googRetransmitBitrate
: {color
: 'blue'},
29 googTransmitBitrate
: {color
: 'green'},
32 // Converts the last entry of |srcDataSeries| from the total amount to the
34 var totalToPerSecond = function(srcDataSeries
) {
35 var length
= srcDataSeries
.dataPoints_
.length
;
37 var lastDataPoint
= srcDataSeries
.dataPoints_
[length
- 1];
38 var secondLastDataPoint
= srcDataSeries
.dataPoints_
[length
- 2];
39 return (lastDataPoint
.value
- secondLastDataPoint
.value
) * 1000 /
40 (lastDataPoint
.time
- secondLastDataPoint
.time
);
46 // Converts the value of total bytes to bits per second.
47 var totalBytesToBitsPerSecond = function(srcDataSeries
) {
48 return totalToPerSecond(srcDataSeries
) * 8;
51 // Specifies which stats should be converted before drawn and how.
52 // |convertedName| is the name of the converted value, |convertFunction|
53 // is the function used to calculate the new converted value based on the
54 // original dataSeries.
55 var dataConversionConfig
= {
57 convertedName
: 'packetsSentPerSecond',
58 convertFunction
: totalToPerSecond
,
61 convertedName
: 'bitsSentPerSecond',
62 convertFunction
: totalBytesToBitsPerSecond
,
65 convertedName
: 'packetsReceivedPerSecond',
66 convertFunction
: totalToPerSecond
,
69 convertedName
: 'bitsReceivedPerSecond',
70 convertFunction
: totalBytesToBitsPerSecond
,
72 // This is due to a bug of wrong units reported for googTargetEncBitrate.
73 // TODO (jiayl): remove this when the unit bug is fixed.
74 googTargetEncBitrate
: {
75 convertedName
: 'googTargetEncBitrateCorrected',
76 convertFunction: function (srcDataSeries
) {
77 var length
= srcDataSeries
.dataPoints_
.length
;
78 var lastDataPoint
= srcDataSeries
.dataPoints_
[length
- 1];
79 if (lastDataPoint
.value
< 5000)
80 return lastDataPoint
.value
* 1000;
81 return lastDataPoint
.value
;
87 // The object contains the stats names that should not be added to the graph,
88 // even if they are numbers.
89 var statsNameBlackList
= {
92 'googComponent': true,
93 'googLocalAddress': true,
94 'googRemoteAddress': true,
95 'googFingerprint': true,
100 // Returns number parsed from |value|, or NaN if the stats name is black-listed.
101 function getNumberFromValue(name
, value
) {
102 if (statsNameBlackList
[name
])
104 return parseFloat(value
);
107 // Adds the stats report |report| to the timeline graph for the given
108 // |peerConnectionElement|.
109 function drawSingleReport(peerConnectionElement
, report
) {
110 var reportType
= report
.type
;
111 var reportId
= report
.id
;
112 var stats
= report
.stats
;
113 if (!stats
|| !stats
.values
)
116 for (var i
= 0; i
< stats
.values
.length
- 1; i
= i
+ 2) {
117 var rawLabel
= stats
.values
[i
];
118 // Propagation deltas are handled separately.
119 if (rawLabel
== RECEIVED_PROPAGATION_DELTA_LABEL
) {
120 drawReceivedPropagationDelta(
121 peerConnectionElement
, report
, stats
.values
[i
+ 1]);
124 var rawDataSeriesId
= reportId
+ '-' + rawLabel
;
125 var rawValue
= getNumberFromValue(rawLabel
, stats
.values
[i
+ 1]);
126 if (isNaN(rawValue
)) {
127 // We do not draw non-numerical values, but still want to record it in the
129 addDataSeriesPoints(peerConnectionElement
,
133 [stats
.values
[i
+ 1]]);
137 var finalDataSeriesId
= rawDataSeriesId
;
138 var finalLabel
= rawLabel
;
139 var finalValue
= rawValue
;
140 // We need to convert the value if dataConversionConfig[rawLabel] exists.
141 if (dataConversionConfig
[rawLabel
]) {
142 // Updates the original dataSeries before the conversion.
143 addDataSeriesPoints(peerConnectionElement
,
149 // Convert to another value to draw on graph, using the original
150 // dataSeries as input.
151 finalValue
= dataConversionConfig
[rawLabel
].convertFunction(
152 peerConnectionDataStore
[peerConnectionElement
.id
].getDataSeries(
154 finalLabel
= dataConversionConfig
[rawLabel
].convertedName
;
155 finalDataSeriesId
= reportId
+ '-' + finalLabel
;
158 // Updates the final dataSeries to draw.
159 addDataSeriesPoints(peerConnectionElement
,
165 // Updates the graph.
166 var graphType
= bweCompoundGraphConfig
[finalLabel
] ?
167 'bweCompound' : finalLabel
;
169 peerConnectionElement
.id
+ '-' + reportId
+ '-' + graphType
;
171 if (!graphViews
[graphViewId
]) {
172 graphViews
[graphViewId
] = createStatsGraphView(peerConnectionElement
,
175 var date
= new Date(stats
.timestamp
);
176 graphViews
[graphViewId
].setDateRange(date
, date
);
178 // Adds the new dataSeries to the graphView. We have to do it here to cover
179 // both the simple and compound graph cases.
181 peerConnectionDataStore
[peerConnectionElement
.id
].getDataSeries(
183 if (!graphViews
[graphViewId
].hasDataSeries(dataSeries
))
184 graphViews
[graphViewId
].addDataSeries(dataSeries
);
185 graphViews
[graphViewId
].updateEndDate();
189 // Makes sure the TimelineDataSeries with id |dataSeriesId| is created,
190 // and adds the new data points to it. |times| is the list of timestamps for
191 // each data point, and |values| is the list of the data point values.
192 function addDataSeriesPoints(
193 peerConnectionElement
, dataSeriesId
, label
, times
, values
) {
195 peerConnectionDataStore
[peerConnectionElement
.id
].getDataSeries(
198 dataSeries
= new TimelineDataSeries();
199 peerConnectionDataStore
[peerConnectionElement
.id
].setDataSeries(
200 dataSeriesId
, dataSeries
);
201 if (bweCompoundGraphConfig
[label
]) {
202 dataSeries
.setColor(bweCompoundGraphConfig
[label
].color
);
205 for (var i
= 0; i
< times
.length
; ++i
)
206 dataSeries
.addPoint(times
[i
], values
[i
]);
209 // Draws the received propagation deltas using the packet group arrival time as
210 // the x-axis. For example, |report.stats.values| should be like
211 // ['googReceivedPacketGroupArrivalTimeDebug', '[123456, 234455, 344566]',
212 // 'googReceivedPacketGroupPropagationDeltaDebug', '[23, 45, 56]', ...].
213 function drawReceivedPropagationDelta(peerConnectionElement
, report
, deltas
) {
214 var reportId
= report
.id
;
215 var stats
= report
.stats
;
217 // Find the packet group arrival times.
218 for (var i
= 0; i
< stats
.values
.length
- 1; i
= i
+ 2) {
219 if (stats
.values
[i
] == RECEIVED_PACKET_GROUP_ARRIVAL_TIME_LABEL
) {
220 times
= stats
.values
[i
+ 1];
228 // Convert |deltas| and |times| from strings to arrays of numbers.
230 deltas
= JSON
.parse(deltas
);
231 times
= JSON
.parse(times
);
237 // Update the data series.
238 var dataSeriesId
= reportId
+ '-' + RECEIVED_PROPAGATION_DELTA_LABEL
;
240 peerConnectionElement
,
242 RECEIVED_PROPAGATION_DELTA_LABEL
,
246 var graphViewId
= peerConnectionElement
.id
+ '-' + reportId
+ '-' +
247 RECEIVED_PROPAGATION_DELTA_LABEL
;
248 var date
= new Date(times
[times
.length
- 1]);
249 if (!graphViews
[graphViewId
]) {
250 graphViews
[graphViewId
] = createStatsGraphView(
251 peerConnectionElement
,
253 RECEIVED_PROPAGATION_DELTA_LABEL
);
254 graphViews
[graphViewId
].setScale(10);
255 graphViews
[graphViewId
].setDateRange(date
, date
);
256 var dataSeries
= peerConnectionDataStore
[peerConnectionElement
.id
]
257 .getDataSeries(dataSeriesId
);
258 graphViews
[graphViewId
].addDataSeries(dataSeries
);
260 graphViews
[graphViewId
].updateEndDate(date
);
263 // Ensures a div container to hold all stats graphs for one track is created as
264 // a child of |peerConnectionElement|.
265 function ensureStatsGraphTopContainer(peerConnectionElement
, report
) {
266 var containerId
= peerConnectionElement
.id
+ '-' +
267 report
.type
+ '-' + report
.id
+ '-graph-container';
268 var container
= $(containerId
);
270 container
= document
.createElement('details');
271 container
.id
= containerId
;
272 container
.className
= 'stats-graph-container';
274 peerConnectionElement
.appendChild(container
);
275 container
.innerHTML
='<summary><span></span></summary>';
276 container
.firstChild
.firstChild
.className
=
277 STATS_GRAPH_CONTAINER_HEADING_CLASS
;
278 container
.firstChild
.firstChild
.textContent
=
279 'Stats graphs for ' + report
.id
;
281 if (report
.type
== 'ssrc') {
282 var ssrcInfoElement
= document
.createElement('div');
283 container
.firstChild
.appendChild(ssrcInfoElement
);
284 ssrcInfoManager
.populateSsrcInfo(ssrcInfoElement
,
285 GetSsrcFromReport(report
));
291 // Creates the container elements holding a timeline graph
292 // and the TimelineGraphView object.
293 function createStatsGraphView(
294 peerConnectionElement
, report
, statsName
) {
295 var topContainer
= ensureStatsGraphTopContainer(peerConnectionElement
,
299 peerConnectionElement
.id
+ '-' + report
.id
+ '-' + statsName
;
300 var divId
= graphViewId
+ '-div';
301 var canvasId
= graphViewId
+ '-canvas';
302 var container
= document
.createElement("div");
303 container
.className
= 'stats-graph-sub-container';
305 topContainer
.appendChild(container
);
306 container
.innerHTML
= '<div>' + statsName
+ '</div>' +
307 '<div id=' + divId
+ '><canvas id=' + canvasId
+ '></canvas></div>';
308 if (statsName
== 'bweCompound') {
309 container
.insertBefore(
310 createBweCompoundLegend(peerConnectionElement
, report
.id
),
313 return new TimelineGraphView(divId
, canvasId
);
316 // Creates the legend section for the bweCompound graph.
317 // Returns the legend element.
318 function createBweCompoundLegend(peerConnectionElement
, reportId
) {
319 var legend
= document
.createElement('div');
320 for (var prop
in bweCompoundGraphConfig
) {
321 var div
= document
.createElement('div');
322 legend
.appendChild(div
);
323 div
.innerHTML
= '<input type=checkbox checked></input>' + prop
;
324 div
.style
.color
= bweCompoundGraphConfig
[prop
].color
;
325 div
.dataSeriesId
= reportId
+ '-' + prop
;
327 peerConnectionElement
.id
+ '-' + reportId
+ '-bweCompound';
328 div
.firstChild
.addEventListener('click', function(event
) {
330 peerConnectionDataStore
[peerConnectionElement
.id
].getDataSeries(
331 event
.target
.parentNode
.dataSeriesId
);
332 target
.show(event
.target
.checked
);
333 graphViews
[event
.target
.parentNode
.graphViewId
].repaint();