OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** This view displays summary statistics on bandwidth usage. */ | 5 /** This view displays summary statistics on bandwidth usage. */ |
6 var BandwidthView = (function() { | 6 var BandwidthView = (function() { |
7 'use strict'; | 7 'use strict'; |
8 | 8 |
9 // We inherit from DivView. | 9 // We inherit from DivView. |
10 var superClass = DivView; | 10 var superClass = DivView; |
11 | 11 |
12 /** | 12 /** |
13 * @constructor | 13 * @constructor |
14 */ | 14 */ |
15 function BandwidthView() { | 15 function BandwidthView() { |
16 assertFirstConstructorCall(BandwidthView); | 16 assertFirstConstructorCall(BandwidthView); |
17 | 17 |
18 // Call superclass's constructor. | 18 // Call superclass's constructor. |
19 superClass.call(this, BandwidthView.MAIN_BOX_ID); | 19 superClass.call(this, BandwidthView.MAIN_BOX_ID); |
20 | 20 |
21 // This map should match the DataReductionProxyBypassType enum. | |
mmenke
2014/12/09 21:58:52
Sorry for not saying this in the first place, but
jeremyim
2014/12/09 23:14:28
Done.
| |
22 this.bypassTypeLookup_ = {}; | |
23 this.bypassTypeLookup_[0] = 'Bypass current request'; | |
24 this.bypassTypeLookup_[1] = 'Bypass for short period'; | |
25 this.bypassTypeLookup_[2] = 'Bypass for medium period'; | |
26 this.bypassTypeLookup_[3] = 'Bypass for long period'; | |
27 this.bypassTypeLookup_[4] = 'Missing Via Header 4xx'; | |
28 this.bypassTypeLookup_[5] = 'Missing Via Header non-4xx'; | |
29 this.bypassTypeLookup_[6] = '407'; | |
30 this.bypassTypeLookup_[7] = '500'; | |
31 this.bypassTypeLookup_[8] = '502'; | |
32 this.bypassTypeLookup_[9] = '503'; | |
33 this.bypassTypeLookup_[10] = 'Network error'; | |
34 | |
21 g_browser.addSessionNetworkStatsObserver(this, true); | 35 g_browser.addSessionNetworkStatsObserver(this, true); |
22 g_browser.addHistoricNetworkStatsObserver(this, true); | 36 g_browser.addHistoricNetworkStatsObserver(this, true); |
23 | 37 |
38 // Register to receive data reduction proxy info. | |
39 g_browser.addDataReductionProxyInfoObserver(this, true); | |
40 | |
41 // Register to receive proxy settings. | |
42 g_browser.addProxySettingsObserver(this, true); | |
43 | |
44 // Register to receive bad proxy info. | |
45 g_browser.addBadProxiesObserver(this, true); | |
46 | |
24 this.sessionNetworkStats_ = null; | 47 this.sessionNetworkStats_ = null; |
25 this.historicNetworkStats_ = null; | 48 this.historicNetworkStats_ = null; |
26 } | 49 } |
27 | 50 |
28 BandwidthView.TAB_ID = 'tab-handle-bandwidth'; | 51 BandwidthView.TAB_ID = 'tab-handle-bandwidth'; |
29 BandwidthView.TAB_NAME = 'Bandwidth'; | 52 BandwidthView.TAB_NAME = 'Bandwidth'; |
30 BandwidthView.TAB_HASH = '#bandwidth'; | 53 BandwidthView.TAB_HASH = '#bandwidth'; |
31 | 54 |
32 // IDs for special HTML elements in bandwidth_view.html | 55 // IDs for special HTML elements in bandwidth_view.html |
33 BandwidthView.MAIN_BOX_ID = 'bandwidth-view-tab-content'; | 56 BandwidthView.MAIN_BOX_ID = 'bandwidth-view-tab-content'; |
57 BandwidthView.ENABLED_ID = 'data-reduction-proxy-enabled'; | |
58 BandwidthView.PRIMARY_PROXY_ID = 'data-reduction-proxy-primary'; | |
59 BandwidthView.SECONDARY_PROXY_ID = 'data-reduction-proxy-secondary'; | |
60 BandwidthView.CANARY_STATUS_ID = 'data-reduction-proxy-canary-status'; | |
61 BandwidthView.BYPASS_STATE_ID = 'data-reduction-proxy-bypass-state'; | |
62 BandwidthView.BYPASS_STATE_CONTAINER_ID = | |
63 'data-reduction-proxy-bypass-state-container'; | |
64 BandwidthView.EVENTS_TBODY_ID = 'data-reduction-proxy-view-events-tbody'; | |
65 BandwidthView.EVENTS_UL = 'data-reduction-proxy-view-events-list'; | |
66 BandwidthView.STATS_BOX_ID = 'bandwidth-stats-table'; | |
34 | 67 |
35 cr.addSingletonGetter(BandwidthView); | 68 cr.addSingletonGetter(BandwidthView); |
36 | 69 |
37 BandwidthView.prototype = { | 70 BandwidthView.prototype = { |
38 // Inherit the superclass's methods. | 71 // Inherit the superclass's methods. |
39 __proto__: superClass.prototype, | 72 __proto__: superClass.prototype, |
40 | 73 |
74 data_reduction_proxy_config_: null, | |
75 last_bypass_event_: null, | |
76 proxy_config_: null, | |
77 bad_proxy_config_: null, | |
78 | |
41 onLoadLogFinish: function(data) { | 79 onLoadLogFinish: function(data) { |
42 // Even though this information is included in log dumps, there's no real | 80 // Even though this information is included in log dumps, there's no real |
43 // reason to display it when debugging a loaded log file. | 81 // reason to display it when debugging a loaded log file. |
44 return false; | 82 return false; |
mmenke
2014/12/09 21:58:52
Note that this will hide the tab when loading log
jeremyim
2014/12/09 23:14:28
Done. I had this in a separate CL, but pulled that
| |
45 }, | 83 }, |
46 | 84 |
47 /** | 85 /** |
48 * Retains information on bandwidth usage this session. | 86 * Retains information on bandwidth usage this session. |
49 */ | 87 */ |
50 onSessionNetworkStatsChanged: function(sessionNetworkStats) { | 88 onSessionNetworkStatsChanged: function(sessionNetworkStats) { |
51 this.sessionNetworkStats_ = sessionNetworkStats; | 89 this.sessionNetworkStats_ = sessionNetworkStats; |
52 return this.updateBandwidthUsageTable_(); | 90 return this.updateBandwidthUsageTable_(); |
53 }, | 91 }, |
54 | 92 |
55 /** | 93 /** |
56 * Displays information on bandwidth usage this session and over the | 94 * Displays information on bandwidth usage this session and over the |
57 * browser's lifetime. | 95 * browser's lifetime. |
58 */ | 96 */ |
59 onHistoricNetworkStatsChanged: function(historicNetworkStats) { | 97 onHistoricNetworkStatsChanged: function(historicNetworkStats) { |
60 this.historicNetworkStats_ = historicNetworkStats; | 98 this.historicNetworkStats_ = historicNetworkStats; |
61 return this.updateBandwidthUsageTable_(); | 99 return this.updateBandwidthUsageTable_(); |
62 }, | 100 }, |
63 | 101 |
64 /** | 102 /** |
103 * Updates the UI based on receiving changes in information about the | |
104 * data reduction proxy summary. | |
105 */ | |
106 onDataReductionProxyInfoChanged: function(info) { | |
107 $(BandwidthView.EVENTS_TBODY_ID).innerHTML = ''; | |
108 | |
109 if (!info) | |
110 return false; | |
111 | |
112 if (info.enabled) { | |
113 $(BandwidthView.ENABLED_ID).innerText = 'Enabled'; | |
114 if (info.canary != null) | |
115 $(BandwidthView.CANARY_STATUS_ID).innerText = info.canary; | |
mmenke
2014/12/09 21:58:52
If canary info is null, we just keep whatever we w
jeremyim
2014/12/09 23:14:28
Done.
| |
116 if (info.last_bypass != null) | |
117 this.last_bypass_ = this.parseBypassEvent_(info.last_bypass); | |
mmenke
2014/12/09 21:58:52
As above, should to something with last_bypass_ if
jeremyim
2014/12/09 23:14:28
Done.
| |
118 this.data_reduction_proxy_config_ = info.proxy_config.params; | |
119 } else { | |
120 $(BandwidthView.ENABLED_ID).innerText = 'Disabled'; | |
121 $(BandwidthView.CANARY_STATUS_ID).innerText = 'N/A'; | |
122 this.data_reduction_proxy_config_ = null; | |
123 } | |
124 | |
125 this.updateDataReductionProxyConfig_(); | |
126 | |
127 for (var eventIndex = info.events.length; eventIndex > 0; --eventIndex) { | |
128 var event = info.events[eventIndex - 1]; | |
mmenke
2014/12/09 21:58:52
This is kinda weird...better to make the eventInde
jeremyim
2014/12/09 23:14:28
Done.
| |
129 var headerRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr'); | |
130 var detailsRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr'); | |
131 | |
132 var timeCell = addNode(headerRow, 'td'); | |
133 var actionCell = addNode(headerRow, 'td'); | |
134 var detailsCell = addNode(detailsRow, 'td'); | |
135 detailsCell.colSpan = 2; | |
136 detailsCell.className = 'data-reduction-proxy-view-events-details'; | |
137 var eventTime = timeutil.convertTimeTicksToDate(event.time); | |
138 timeutil.addNodeWithDate(timeCell, eventTime); | |
139 | |
140 switch (event.type) { | |
141 case EventType.DATA_REDUCTION_PROXY_ENABLED: | |
142 this.buildEnabledRow_(event, actionCell, detailsCell); | |
143 break; | |
144 case EventType.DATA_REDUCTION_PROXY_CANARY_REQUEST: | |
145 this.buildCanaryRow_(event, actionCell, detailsCell); | |
146 break; | |
147 case EventType.DATA_REDUCTION_PROXY_CANARY_RESPONSE_RECEIVED: | |
148 this.buildCanaryResponseRow_(event, actionCell, detailsCell); | |
149 break; | |
150 case EventType.DATA_REDUCTION_PROXY_BYPASS_REQUESTED: | |
151 this.buildBypassRow_(event, actionCell, detailsCell); | |
152 break; | |
153 case EventType.DATA_REDUCTION_PROXY_FALLBACK: | |
154 this.buildFallbackRow_(event, actionCell, detailsCell); | |
155 break; | |
156 } | |
157 } | |
158 }, | |
159 | |
160 /** | |
161 * Updates the UI based on receiving changes in information about the | |
162 * proxy settings. | |
163 */ | |
164 onProxySettingsChanged: function(proxySettings) { | |
165 var newProxySettings = []; | |
166 var effectiveSettings = proxySettings.effective; | |
167 if (effectiveSettings && effectiveSettings.proxy_per_scheme) { | |
168 for (var scheme in effectiveSettings.proxy_per_scheme) { | |
169 var schemeSettings = effectiveSettings.proxy_per_scheme[scheme]; | |
170 if (scheme != 'fallback') { | |
171 for (var i = 0; i < schemeSettings.length; ++i) { | |
172 var proxyUri = schemeSettings[i]; | |
173 if (proxyUri != 'direct://') | |
174 newProxySettings.push(proxyUri); | |
175 } | |
176 } | |
177 } | |
178 } | |
179 this.proxy_config_ = newProxySettings; | |
180 this.updateDataReductionProxyConfig_(); | |
181 }, | |
182 | |
183 /** | |
184 * Updates the UI based on receiving changes in information about bad | |
185 * proxy servers. | |
186 */ | |
187 onBadProxiesChanged: function(badProxies) { | |
188 var newBadProxies = []; | |
189 if (badProxies.length == 0) { | |
190 this.last_bypass_ = null; | |
191 } else { | |
192 for (var i = 0; i < badProxies.length; ++i) { | |
193 var entry = badProxies[i]; | |
194 newBadProxies[entry.proxy_uri] = entry.bad_until; | |
195 } | |
196 } | |
197 this.bad_proxy_config_ = newBadProxies; | |
198 this.updateDataReductionProxyConfig_(); | |
199 }, | |
200 | |
201 /** | |
65 * Update the bandwidth usage table. Returns false on failure. | 202 * Update the bandwidth usage table. Returns false on failure. |
66 */ | 203 */ |
67 updateBandwidthUsageTable_: function() { | 204 updateBandwidthUsageTable_: function() { |
68 var sessionNetworkStats = this.sessionNetworkStats_; | 205 var sessionNetworkStats = this.sessionNetworkStats_; |
69 var historicNetworkStats = this.historicNetworkStats_; | 206 var historicNetworkStats = this.historicNetworkStats_; |
70 if (!sessionNetworkStats || !historicNetworkStats) | 207 if (!sessionNetworkStats || !historicNetworkStats) |
71 return false; | 208 return false; |
72 | 209 |
73 var sessionOriginal = sessionNetworkStats.session_original_content_length; | 210 var sessionOriginal = sessionNetworkStats.session_original_content_length; |
74 var sessionReceived = sessionNetworkStats.session_received_content_length; | 211 var sessionReceived = sessionNetworkStats.session_received_content_length; |
(...skipping 21 matching lines...) Expand all Loading... | |
96 bytesToRoundedKilobytes_(historicOriginal - historicReceived) | 233 bytesToRoundedKilobytes_(historicOriginal - historicReceived) |
97 }); | 234 }); |
98 rows.push({ | 235 rows.push({ |
99 title: 'Savings (%)', | 236 title: 'Savings (%)', |
100 sessionValue: getPercentSavings_(sessionOriginal, sessionReceived), | 237 sessionValue: getPercentSavings_(sessionOriginal, sessionReceived), |
101 historicValue: getPercentSavings_(historicOriginal, | 238 historicValue: getPercentSavings_(historicOriginal, |
102 historicReceived) | 239 historicReceived) |
103 }); | 240 }); |
104 | 241 |
105 var input = new JsEvalContext({rows: rows}); | 242 var input = new JsEvalContext({rows: rows}); |
106 jstProcess(input, $(BandwidthView.MAIN_BOX_ID)); | 243 jstProcess(input, $(BandwidthView.STATS_BOX_ID)); |
107 return true; | 244 return true; |
245 }, | |
246 | |
247 /** | |
248 * Renders a data reduction proxy enabled/disabled event into the event | |
249 * tbody. | |
250 */ | |
251 buildEnabledRow_: function(event, actionCell, detailsCell) { | |
252 if (event.params.enabled == 1) { | |
253 addTextNode(actionCell, 'Proxy: Enabled'); | |
254 var proxyWrapper = addNode(detailsCell, 'div'); | |
255 addNodeWithText(proxyWrapper, 'div', 'Proxy configuration:'); | |
256 | |
257 if (event.params.primary_origin != null && | |
258 event.params.primary_origin.trim() != '') { | |
259 var proxyText = 'Primary: ' + event.params.primary_origin; | |
260 if (event.params.primary_restricted != null && | |
261 event.params.primary_restricted) | |
262 proxyText += ' (restricted)'; | |
mmenke
2014/12/09 21:58:52
use braces when condition of the if takes more tha
jeremyim
2014/12/09 23:14:28
Done.
| |
263 addNodeWithText(proxyWrapper, 'div', proxyText); | |
264 } | |
265 | |
266 if (event.params.fallback_origin != null && | |
267 event.params.fallback_origin.trim() != '') { | |
268 var proxyText = 'Fallback: ' + event.params.fallback_origin; | |
269 if (event.params.fallback_restricted != null && | |
270 event.params.fallback_restricted) | |
271 proxyText += ' (restricted)'; | |
mmenke
2014/12/09 21:58:52
use braces when condition of the if takes more tha
jeremyim
2014/12/09 23:14:28
Done.
| |
272 addNodeWithText(proxyWrapper, 'div', proxyText); | |
273 } | |
274 | |
275 if (event.params.ssl_origin != null && | |
276 event.params.ssl_origin.trim() != '') | |
277 addNodeWithText(proxyWrapper, 'div', | |
278 'SSL: ' + event.params.ssl_origin); | |
mmenke
2014/12/09 21:58:52
use braces when body of the if takes more than one
jeremyim
2014/12/09 23:14:28
Done.
| |
279 } else { | |
280 addTextNode(actionCell, 'Proxy: Disabled'); | |
281 } | |
282 }, | |
283 | |
284 /** | |
285 * Renders a data reduction proxy canary request event into the event | |
286 * tbody. | |
287 */ | |
288 buildCanaryRow_: function(event, actionCell, detailsCell) { | |
289 if (event.phase == EventPhase.PHASE_BEGIN) { | |
290 addTextNode(actionCell, 'Canary request sent'); | |
291 addTextNode(detailsCell, 'URL: ' + event.params.url); | |
292 } else if (event.phase == EventPhase.PHASE_END) { | |
293 addTextNode(actionCell, 'Canary request completed'); | |
294 if (event.params.net_error == 0) { | |
295 addTextNode(detailsCell, 'Result: OK'); | |
296 } else { | |
297 addTextNode(detailsCell, | |
298 'Result: ' + netErrorToString(event.params.net_error)); | |
299 } | |
300 } | |
301 }, | |
302 | |
303 /** | |
304 * Renders a data reduction proxy canary response event into the event | |
305 * tbody. | |
306 */ | |
307 buildCanaryResponseRow_: function(event, actionCell, detailsCell) { | |
308 addTextNode(actionCell, 'Canary response received'); | |
309 }, | |
310 | |
311 /** | |
312 * Renders a data reduction proxy bypass event into the event tbody. | |
313 */ | |
314 buildBypassRow_: function(event, actionCell, detailsCell) { | |
315 var parsedBypass = this.parseBypassEvent_(event); | |
316 | |
317 addTextNode(actionCell, | |
318 'Bypass received (' + parsedBypass.bypass_reason + ')'); | |
319 var bypassWrapper = addNode(detailsCell, 'div'); | |
320 addNodeWithText(bypassWrapper, 'div', 'URL: ' + parsedBypass.origin_url); | |
321 addNodeWithText( | |
322 bypassWrapper, 'div', | |
323 'Bypassed for ' + parsedBypass.bypass_duration_seconds + ' seconds.'); | |
324 }, | |
325 | |
326 /** | |
327 * Renders a data reduction proxy fallback event into the event tbody. | |
328 */ | |
329 buildFallbackRow_: function(event, actionCell, detailsCell) { | |
330 addTextNode(actionCell, 'Proxy fallback'); | |
331 }, | |
332 | |
333 /** | |
334 * Parses a data reduction proxy bypass event for use in the summary and | |
335 * in the event tbody. | |
336 */ | |
337 parseBypassEvent_: function(event) { | |
338 var reason; | |
339 if (event.params.action != null) { | |
340 reason = event.params.action; | |
341 } else { | |
342 reason = this.bypassTypeLookup_[event.params.bypass_type]; | |
343 if (reason == null) | |
344 reason = 'Unknown error'; | |
345 } | |
346 | |
347 var parsedBypass = { | |
348 bypass_reason: reason, | |
349 origin_url: event.params.url, | |
350 bypass_duration_seconds: event.params.bypass_duration_seconds, | |
351 bypass_expiration: event.params.expiration, | |
352 }; | |
353 | |
354 return parsedBypass; | |
355 }, | |
356 | |
357 /** | |
358 * Updates the data reduction proxy summary block. | |
359 */ | |
360 updateDataReductionProxyConfig_: function() { | |
361 $(BandwidthView.PRIMARY_PROXY_ID).innerHTML = ''; | |
362 $(BandwidthView.SECONDARY_PROXY_ID).innerHTML = ''; | |
363 setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), false); | |
364 | |
365 if (this.data_reduction_proxy_config_) { | |
366 var primaryProxy = ''; | |
367 var secondaryProxy = ''; | |
368 var hasBypassedProxy = false; | |
369 var now = timeutil.getCurrentTimeTicks(); | |
370 | |
371 if (this.last_bypass_ && +this.last_bypass_.bypass_expiration > now) { | |
372 var input = new JsEvalContext(this.last_bypass_); | |
373 jstProcess(input, $(BandwidthView.BYPASS_STATE_CONTAINER_ID)); | |
374 } else { | |
375 var input = new JsEvalContext(); | |
376 jstProcess(input, $(BandwidthView.BYPASS_STATE_CONTAINER_ID)); | |
377 } | |
378 | |
379 if (this.data_reduction_proxy_config_.ssl_origin) { | |
380 if (this.isMarkedAsBad_(this.data_reduction_proxy_config_.ssl_origin)) | |
381 hasBypassedProxy = true; | |
382 | |
383 primaryProxy = 'HTTPS Tunnel: ' + this.buildProxyString_( | |
384 this.data_reduction_proxy_config_.ssl_origin, false); | |
385 } | |
386 | |
387 if (this.data_reduction_proxy_config_.primary_origin) { | |
388 if (this.isMarkedAsBad_( | |
389 this.data_reduction_proxy_config_.primary_origin)) | |
390 hasBypassedProxy = true; | |
391 | |
392 var proxyString = this.buildProxyString_( | |
393 this.data_reduction_proxy_config_.primary_origin, | |
394 this.data_reduction_proxy_config_.primary_restricted); | |
395 | |
396 if (primaryProxy == '') | |
397 primaryProxy = proxyString; | |
398 else | |
399 secondaryProxy = proxyString; | |
400 } | |
401 | |
402 if (this.data_reduction_proxy_config_.fallback_origin) { | |
403 if (this.isMarkedAsBad_( | |
404 this.data_reduction_proxy_config_.fallback_origin)) | |
405 hasBypassedProxy = true; | |
406 | |
407 var proxyString = this.buildProxyString_( | |
408 this.data_reduction_proxy_config_.fallback_origin, | |
409 this.data_reduction_proxy_config_.fallback_restricted); | |
410 | |
411 if (primaryProxy == '') | |
412 primaryProxy = proxyString; | |
413 else if (secondaryProxy == '') | |
414 secondaryProxy = proxyString; | |
415 } | |
416 | |
417 $(BandwidthView.PRIMARY_PROXY_ID).innerText = primaryProxy; | |
418 $(BandwidthView.SECONDARY_PROXY_ID).innerText = secondaryProxy; | |
419 if (hasBypassedProxy) | |
420 setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), true); | |
421 } | |
422 }, | |
423 | |
424 /** | |
425 * Takes a data reduction proxy configuration and renders to a friendly | |
426 * string. | |
427 */ | |
428 buildProxyString_: function(proxy, restricted) { | |
429 var configured = this.isConfigured_(proxy); | |
430 var markedAsBad = this.isMarkedAsBad_(proxy); | |
431 var proxyString = ''; | |
432 if (restricted) { | |
433 proxyString += proxy + ' (RESTRICTED)'; | |
434 } else if (configured) { | |
435 proxyString += proxy; | |
436 if (markedAsBad) { | |
437 proxyString += ' (BYPASSED)'; | |
438 } else { | |
439 proxyString += ' (ACTIVE)'; | |
440 } | |
441 } | |
442 | |
443 return proxyString; | |
444 }, | |
445 | |
446 /** | |
447 * Checks to see if a proxy server is in the current configuration. | |
448 */ | |
449 isConfigured_: function(proxy) { | |
450 for (var index in this.proxy_config_) { | |
451 var entry = this.proxy_config_[index]; | |
452 if (entry == proxy) | |
453 return true; | |
454 } | |
455 | |
456 return false; | |
457 }, | |
458 | |
459 /** | |
460 * Checks to see if a proxy server is in marked as bad. | |
461 */ | |
462 isMarkedAsBad_: function(proxy) { | |
463 var now = timeutil.getCurrentTimeTicks(); | |
464 for (var entry in this.bad_proxy_config_) { | |
465 if (entry == proxy && this.bad_proxy_config_[entry] > now) | |
466 return true; | |
467 } | |
468 | |
469 return false; | |
108 } | 470 } |
109 }; | 471 }; |
110 | 472 |
111 /** | 473 /** |
112 * Converts bytes to kilobytes rounded to one decimal place. | 474 * Converts bytes to kilobytes rounded to one decimal place. |
113 */ | 475 */ |
114 function bytesToRoundedKilobytes_(val) { | 476 function bytesToRoundedKilobytes_(val) { |
115 return (val / 1024).toFixed(1); | 477 return (val / 1024).toFixed(1); |
116 } | 478 } |
117 | 479 |
118 /** | 480 /** |
119 * Returns bandwidth savings as a percent rounded to one decimal place. | 481 * Returns bandwidth savings as a percent rounded to one decimal place. |
120 */ | 482 */ |
121 function getPercentSavings_(original, received) { | 483 function getPercentSavings_(original, received) { |
122 if (original > 0) { | 484 if (original > 0) { |
123 return ((original - received) * 100 / original).toFixed(1); | 485 return ((original - received) * 100 / original).toFixed(1); |
124 } | 486 } |
125 return '0.0'; | 487 return '0.0'; |
126 } | 488 } |
127 | 489 |
128 return BandwidthView; | 490 return BandwidthView; |
129 })(); | 491 })(); |
OLD | NEW |