- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / resources / media / stats_graph_helper.js
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.
4
5 //
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.
12 //
13
14 <include src="timeline_graph_view.js"/>
15
16 var STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading';
17
18 // Specifies which stats should be drawn on the 'bweCompound' graph and how.
19 var bweCompoundGraphConfig = {
20   googAvailableSendBandwidth: {color: 'red'},
21   googTargetEncBitrateCorrected: {color: 'purple'},
22   googActualEncBitrate: {color: 'orange'},
23   googRetransmitBitrate: {color: 'blue'},
24   googTransmitBitrate: {color: 'green'},
25 };
26
27 // Converts the last entry of |srcDataSeries| from the total amount to the
28 // amount per second.
29 var totalToPerSecond = function(srcDataSeries) {
30   var length = srcDataSeries.dataPoints_.length;
31   if (length >= 2) {
32     var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
33     var secondLastDataPoint = srcDataSeries.dataPoints_[length - 2];
34     return (lastDataPoint.value - secondLastDataPoint.value) * 1000 /
35            (lastDataPoint.time - secondLastDataPoint.time);
36   }
37
38   return 0;
39 };
40
41 // Converts the value of total bytes to bits per second.
42 var totalBytesToBitsPerSecond = function(srcDataSeries) {
43   return totalToPerSecond(srcDataSeries) * 8;
44 };
45
46 // Specifies which stats should be converted before drawn and how.
47 // |convertedName| is the name of the converted value, |convertFunction|
48 // is the function used to calculate the new converted value based on the
49 // original dataSeries.
50 var dataConversionConfig = {
51   packetsSent: {
52     convertedName: 'packetsSentPerSecond',
53     convertFunction: totalToPerSecond,
54   },
55   bytesSent: {
56     convertedName: 'bitsSentPerSecond',
57     convertFunction: totalBytesToBitsPerSecond,
58   },
59   packetsReceived: {
60     convertedName: 'packetsReceivedPerSecond',
61     convertFunction: totalToPerSecond,
62   },
63   bytesReceived: {
64     convertedName: 'bitsReceivedPerSecond',
65     convertFunction: totalBytesToBitsPerSecond,
66   },
67   // This is due to a bug of wrong units reported for googTargetEncBitrate.
68   // TODO (jiayl): remove this when the unit bug is fixed.
69   googTargetEncBitrate: {
70     convertedName: 'googTargetEncBitrateCorrected',
71     convertFunction: function (srcDataSeries) {
72       var length = srcDataSeries.dataPoints_.length;
73       var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
74       if (lastDataPoint.value < 5000)
75         return lastDataPoint.value * 1000;
76       return lastDataPoint.value;
77     }
78   }
79 };
80
81
82 // The object contains the stats names that should not be added to the graph,
83 // even if they are numbers.
84 var statsNameBlackList = {
85   'ssrc': true,
86   'googTrackId': true,
87   'googComponent': true,
88   'googLocalAddress': true,
89   'googRemoteAddress': true,
90 };
91
92 var graphViews = {};
93
94 // Returns number parsed from |value|, or NaN if the stats name is black-listed.
95 function getNumberFromValue(name, value) {
96   if (statsNameBlackList[name])
97     return NaN;
98   return parseFloat(value);
99 }
100
101 // Adds the stats report |report| to the timeline graph for the given
102 // |peerConnectionElement|.
103 function drawSingleReport(peerConnectionElement, report) {
104   var reportType = report.type;
105   var reportId = report.id;
106   var stats = report.stats;
107   if (!stats || !stats.values)
108     return;
109
110   for (var i = 0; i < stats.values.length - 1; i = i + 2) {
111     var rawLabel = stats.values[i];
112     var rawDataSeriesId = reportId + '-' + rawLabel;
113     var rawValue = getNumberFromValue(rawLabel, stats.values[i + 1]);
114     if (isNaN(rawValue)) {
115       // We do not draw non-numerical values, but still want to record it in the
116       // data series.
117       addDataSeriesPoint(peerConnectionElement,
118                          rawDataSeriesId, stats.timestamp,
119                          rawLabel, stats.values[i + 1]);
120       continue;
121     }
122
123     var finalDataSeriesId = rawDataSeriesId;
124     var finalLabel = rawLabel;
125     var finalValue = rawValue;
126     // We need to convert the value if dataConversionConfig[rawLabel] exists.
127     if (dataConversionConfig[rawLabel]) {
128       // Updates the original dataSeries before the conversion.
129       addDataSeriesPoint(peerConnectionElement,
130                          rawDataSeriesId, stats.timestamp,
131                          rawLabel, rawValue);
132
133       // Convert to another value to draw on graph, using the original
134       // dataSeries as input.
135       finalValue = dataConversionConfig[rawLabel].convertFunction(
136           peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
137               rawDataSeriesId));
138       finalLabel = dataConversionConfig[rawLabel].convertedName;
139       finalDataSeriesId = reportId + '-' + finalLabel;
140     }
141
142     // Updates the final dataSeries to draw.
143     addDataSeriesPoint(peerConnectionElement,
144                        finalDataSeriesId,
145                        stats.timestamp,
146                        finalLabel,
147                        finalValue);
148
149     // Updates the graph.
150     var graphType = bweCompoundGraphConfig[finalLabel] ?
151                     'bweCompound' : finalLabel;
152     var graphViewId =
153         peerConnectionElement.id + '-' + reportId + '-' + graphType;
154
155     if (!graphViews[graphViewId]) {
156       graphViews[graphViewId] = createStatsGraphView(peerConnectionElement,
157                                                      report,
158                                                      graphType);
159       var date = new Date(stats.timestamp);
160       graphViews[graphViewId].setDateRange(date, date);
161     }
162     // Adds the new dataSeries to the graphView. We have to do it here to cover
163     // both the simple and compound graph cases.
164     var dataSeries =
165         peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
166             finalDataSeriesId);
167     if (!graphViews[graphViewId].hasDataSeries(dataSeries))
168       graphViews[graphViewId].addDataSeries(dataSeries);
169     graphViews[graphViewId].updateEndDate();
170   }
171 }
172
173 // Makes sure the TimelineDataSeries with id |dataSeriesId| is created,
174 // and adds the new data point to it.
175 function addDataSeriesPoint(
176     peerConnectionElement, dataSeriesId, time, label, value) {
177   var dataSeries =
178     peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
179         dataSeriesId);
180   if (!dataSeries) {
181     dataSeries = new TimelineDataSeries();
182     peerConnectionDataStore[peerConnectionElement.id].setDataSeries(
183         dataSeriesId, dataSeries);
184     if (bweCompoundGraphConfig[label]) {
185       dataSeries.setColor(bweCompoundGraphConfig[label].color);
186     }
187   }
188   dataSeries.addPoint(time, value);
189 }
190
191 // Ensures a div container to hold all stats graphs for one track is created as
192 // a child of |peerConnectionElement|.
193 function ensureStatsGraphTopContainer(peerConnectionElement, report) {
194   var containerId = peerConnectionElement.id + '-' +
195       report.type + '-' + report.id + '-graph-container';
196   var container = $(containerId);
197   if (!container) {
198     container = document.createElement('details');
199     container.id = containerId;
200     container.className = 'stats-graph-container';
201
202     peerConnectionElement.appendChild(container);
203     container.innerHTML ='<summary><span></span></summary>';
204     container.firstChild.firstChild.className =
205         STATS_GRAPH_CONTAINER_HEADING_CLASS;
206     container.firstChild.firstChild.textContent =
207         'Stats graphs for ' + report.id;
208
209     if (report.type == 'ssrc') {
210       var ssrcInfoElement = document.createElement('div');
211       container.firstChild.appendChild(ssrcInfoElement);
212       ssrcInfoManager.populateSsrcInfo(ssrcInfoElement,
213                                        GetSsrcFromReport(report));
214     }
215   }
216   return container;
217 }
218
219 // Creates the container elements holding a timeline graph
220 // and the TimelineGraphView object.
221 function createStatsGraphView(
222     peerConnectionElement, report, statsName) {
223   var topContainer = ensureStatsGraphTopContainer(peerConnectionElement,
224                                                   report);
225
226   var graphViewId =
227       peerConnectionElement.id + '-' + report.id + '-' + statsName;
228   var divId = graphViewId + '-div';
229   var canvasId = graphViewId + '-canvas';
230   var container = document.createElement("div");
231   container.className = 'stats-graph-sub-container';
232
233   topContainer.appendChild(container);
234   container.innerHTML = '<div>' + statsName + '</div>' +
235       '<div id=' + divId + '><canvas id=' + canvasId + '></canvas></div>';
236   if (statsName == 'bweCompound') {
237       container.insertBefore(
238           createBweCompoundLegend(peerConnectionElement, report.id),
239           $(divId));
240   }
241   return new TimelineGraphView(divId, canvasId);
242 }
243
244 // Creates the legend section for the bweCompound graph.
245 // Returns the legend element.
246 function createBweCompoundLegend(peerConnectionElement, reportId) {
247   var legend = document.createElement('div');
248   for (var prop in bweCompoundGraphConfig) {
249     var div = document.createElement('div');
250     legend.appendChild(div);
251     div.innerHTML = '<input type=checkbox checked></input>' + prop;
252     div.style.color = bweCompoundGraphConfig[prop].color;
253     div.dataSeriesId = reportId + '-' + prop;
254     div.graphViewId =
255         peerConnectionElement.id + '-' + reportId + '-bweCompound';
256     div.firstChild.addEventListener('click', function(event) {
257         var target =
258             peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
259                 event.target.parentNode.dataSeriesId);
260         target.show(event.target.checked);
261         graphViews[event.target.parentNode.graphViewId].repaint();
262     });
263   }
264   return legend;
265 }