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 g_browser.addSessionNetworkStatsObserver(this, true); | 21 g_browser.addSessionNetworkStatsObserver(this, true); |
22 g_browser.addHistoricNetworkStatsObserver(this, true); | 22 g_browser.addHistoricNetworkStatsObserver(this, true); |
23 | 23 |
24 // Register to receive data reduction proxy info. | |
25 g_browser.addDataReductionProxyInfoObserver(this, true); | |
26 | |
27 // Register to receive proxy settings. | |
28 g_browser.addProxySettingsObserver(this, true); | |
29 | |
30 // Register to receive bad proxy info. | |
31 g_browser.addBadProxiesObserver(this, true); | |
32 | |
24 this.sessionNetworkStats_ = null; | 33 this.sessionNetworkStats_ = null; |
25 this.historicNetworkStats_ = null; | 34 this.historicNetworkStats_ = null; |
26 } | 35 } |
27 | 36 |
28 BandwidthView.TAB_ID = 'tab-handle-bandwidth'; | 37 BandwidthView.TAB_ID = 'tab-handle-bandwidth'; |
29 BandwidthView.TAB_NAME = 'Bandwidth'; | 38 BandwidthView.TAB_NAME = 'Bandwidth'; |
30 BandwidthView.TAB_HASH = '#bandwidth'; | 39 BandwidthView.TAB_HASH = '#bandwidth'; |
31 | 40 |
32 // IDs for special HTML elements in bandwidth_view.html | 41 // IDs for special HTML elements in bandwidth_view.html |
33 BandwidthView.MAIN_BOX_ID = 'bandwidth-view-tab-content'; | 42 BandwidthView.MAIN_BOX_ID = 'bandwidth-view-tab-content'; |
43 BandwidthView.ENABLED_ID = 'data-reduction-proxy-enabled'; | |
44 BandwidthView.PRIMARY_PROXY_ID = 'data-reduction-proxy-primary'; | |
45 BandwidthView.SECONDARY_PROXY_ID = 'data-reduction-proxy-secondary'; | |
46 BandwidthView.CANARY_STATUS_ID = 'data-reduction-proxy-canary-status'; | |
47 BandwidthView.BYPASS_STATE_ID = 'data-reduction-proxy-bypass-state'; | |
48 BandwidthView.BYPASS_STATE_CONTAINER_ID = | |
49 'data-reduction-proxy-bypass-state-container'; | |
50 BandwidthView.EVENTS_TBODY_ID = 'data-reduction-proxy-view-events-tbody'; | |
51 BandwidthView.EVENTS_UL = 'data-reduction-proxy-view-events-list'; | |
52 BandwidthView.STATS_BOX_ID = 'bandwidth-stats-table'; | |
34 | 53 |
35 cr.addSingletonGetter(BandwidthView); | 54 cr.addSingletonGetter(BandwidthView); |
36 | 55 |
37 BandwidthView.prototype = { | 56 BandwidthView.prototype = { |
38 // Inherit the superclass's methods. | 57 // Inherit the superclass's methods. |
39 __proto__: superClass.prototype, | 58 __proto__: superClass.prototype, |
40 | 59 |
60 data_reduction_proxy_config_: null, | |
61 last_bypass_event_: null, | |
62 proxy_config_: null, | |
63 bad_proxy_config_: null, | |
64 | |
41 onLoadLogFinish: function(data) { | 65 onLoadLogFinish: function(data) { |
42 // Even though this information is included in log dumps, there's no real | 66 // Even though this information is included in log dumps, there's no real |
43 // reason to display it when debugging a loaded log file. | 67 // reason to display it when debugging a loaded log file. |
44 return false; | 68 return false; |
45 }, | 69 }, |
46 | 70 |
47 /** | 71 /** |
48 * Retains information on bandwidth usage this session. | 72 * Retains information on bandwidth usage this session. |
49 */ | 73 */ |
50 onSessionNetworkStatsChanged: function(sessionNetworkStats) { | 74 onSessionNetworkStatsChanged: function(sessionNetworkStats) { |
51 this.sessionNetworkStats_ = sessionNetworkStats; | 75 this.sessionNetworkStats_ = sessionNetworkStats; |
52 return this.updateBandwidthUsageTable_(); | 76 return this.updateBandwidthUsageTable_(); |
53 }, | 77 }, |
54 | 78 |
55 /** | 79 /** |
56 * Displays information on bandwidth usage this session and over the | 80 * Displays information on bandwidth usage this session and over the |
57 * browser's lifetime. | 81 * browser's lifetime. |
58 */ | 82 */ |
59 onHistoricNetworkStatsChanged: function(historicNetworkStats) { | 83 onHistoricNetworkStatsChanged: function(historicNetworkStats) { |
60 this.historicNetworkStats_ = historicNetworkStats; | 84 this.historicNetworkStats_ = historicNetworkStats; |
61 return this.updateBandwidthUsageTable_(); | 85 return this.updateBandwidthUsageTable_(); |
62 }, | 86 }, |
63 | 87 |
88 onDataReductionProxyInfoChanged: function(info) { | |
mmenke
2014/12/05 16:18:25
I'm not going to review all this, but it seems lik
jeremyim
2014/12/05 21:31:12
For mobile readability, we split the events across
| |
89 $(BandwidthView.EVENTS_TBODY_ID).innerHTML = ''; | |
90 | |
91 if (!info) { | |
92 return false; | |
93 } | |
mmenke
2014/12/05 16:18:25
nit: Preferred style is not to use braces for two
jeremyim
2014/12/05 21:31:12
Done.
| |
94 | |
95 if (info.enabled) { | |
96 $(BandwidthView.ENABLED_ID).innerText = 'Enabled'; | |
97 if (info.canary != null) { | |
98 $(BandwidthView.CANARY_STATUS_ID).innerText = info.canary; | |
99 } | |
mmenke
2014/12/05 16:18:25
nit: Remove braces.
jeremyim
2014/12/05 21:31:11
Done.
| |
100 if (info.last_bypass != null) { | |
101 this.last_bypass_ = this.parseBypassEvent_(info.last_bypass); | |
102 } | |
mmenke
2014/12/05 16:18:25
nit: Remove braces.
jeremyim
2014/12/05 21:31:12
Done.
| |
103 this.data_reduction_proxy_config_ = info.proxy_config.params; | |
104 } else { | |
105 $(BandwidthView.ENABLED_ID).innerText = 'Disabled'; | |
106 $(BandwidthView.CANARY_STATUS_ID).innerText = 'N/A'; | |
107 this.data_reduction_proxy_config_ = null; | |
108 } | |
109 | |
110 this.updateDataReductionProxyConfig_(); | |
111 | |
112 for (var eventIndex in info.events) { | |
113 var event = info.events[eventIndex]; | |
114 var headerRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr'); | |
115 var detailsRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr'); | |
116 | |
117 var timeCell = addNode(headerRow, 'td'); | |
118 var actionCell = addNode(headerRow, 'td'); | |
119 var detailsCell = addNode(detailsRow, 'td'); | |
120 detailsCell.colSpan = 2; | |
121 detailsCell.className = 'data-reduction-proxy-view-events-details'; | |
122 var eventTime = timeutil.convertTimeTicksToDate(event.time); | |
123 timeutil.addNodeWithDate(timeCell, eventTime); | |
124 | |
125 switch (event.type) { | |
126 case EventType.DATA_REDUCTION_PROXY_ENABLED: | |
127 this.buildEnabledRow_(event, actionCell, detailsCell); | |
128 break; | |
129 case EventType.DATA_REDUCTION_PROXY_CANARY_REQUEST: | |
130 this.buildCanaryRow_(event, actionCell, detailsCell); | |
131 break; | |
132 case EventType.DATA_REDUCTION_PROXY_CANARY_RESPONSE_RECEIVED: | |
133 this.buildCanaryResponseRow_(event, actionCell, detailsCell); | |
134 break; | |
135 case EventType.DATA_REDUCTION_PROXY_BYPASS_REQUESTED: | |
136 this.buildBypassRow_(event, actionCell, detailsCell); | |
137 break; | |
138 case EventType.DATA_REDUCTION_PROXY_FALLBACK: | |
139 this.buildFallbackRow_(event, actionCell, detailsCell); | |
140 break; | |
141 } | |
142 } | |
143 }, | |
144 | |
145 onProxySettingsChanged: function(proxySettings) { | |
146 var newProxySettings = []; | |
147 var effectiveSettings = proxySettings.effective; | |
148 if (effectiveSettings && effectiveSettings.proxy_per_scheme) { | |
149 for (var scheme in effectiveSettings.proxy_per_scheme) { | |
150 var schemeSettings = effectiveSettings.proxy_per_scheme[scheme]; | |
151 if (scheme != 'fallback') { | |
152 for (var i = 0; i < schemeSettings.length; ++i) { | |
153 var proxyUri = schemeSettings[i]; | |
154 if (proxyUri != 'direct://') { | |
155 newProxySettings.push(proxyUri); | |
156 } | |
mmenke
2014/12/05 16:18:25
nit: Remove braces. (There are a number of other
jeremyim
2014/12/05 21:31:11
Done.
| |
157 } | |
158 } | |
159 } | |
160 } | |
161 this.proxy_config_ = newProxySettings; | |
162 this.updateDataReductionProxyConfig_(); | |
163 }, | |
164 | |
165 onBadProxiesChanged: function(badProxies) { | |
166 var newBadProxies = []; | |
167 if (badProxies.length == 0) { | |
168 this.last_bypass_ = null; | |
169 } else { | |
170 for (var i = 0; i < badProxies.length; ++i) { | |
171 var entry = badProxies[i]; | |
172 newBadProxies[entry.proxy_uri] = entry.bad_until; | |
173 } | |
174 } | |
175 this.bad_proxy_config_ = newBadProxies; | |
176 this.updateDataReductionProxyConfig_(); | |
177 }, | |
178 | |
64 /** | 179 /** |
65 * Update the bandwidth usage table. Returns false on failure. | 180 * Update the bandwidth usage table. Returns false on failure. |
66 */ | 181 */ |
67 updateBandwidthUsageTable_: function() { | 182 updateBandwidthUsageTable_: function() { |
68 var sessionNetworkStats = this.sessionNetworkStats_; | 183 var sessionNetworkStats = this.sessionNetworkStats_; |
69 var historicNetworkStats = this.historicNetworkStats_; | 184 var historicNetworkStats = this.historicNetworkStats_; |
70 if (!sessionNetworkStats || !historicNetworkStats) | 185 if (!sessionNetworkStats || !historicNetworkStats) |
71 return false; | 186 return false; |
72 | 187 |
73 var sessionOriginal = sessionNetworkStats.session_original_content_length; | 188 var sessionOriginal = sessionNetworkStats.session_original_content_length; |
(...skipping 22 matching lines...) Expand all Loading... | |
96 bytesToRoundedKilobytes_(historicOriginal - historicReceived) | 211 bytesToRoundedKilobytes_(historicOriginal - historicReceived) |
97 }); | 212 }); |
98 rows.push({ | 213 rows.push({ |
99 title: 'Savings (%)', | 214 title: 'Savings (%)', |
100 sessionValue: getPercentSavings_(sessionOriginal, sessionReceived), | 215 sessionValue: getPercentSavings_(sessionOriginal, sessionReceived), |
101 historicValue: getPercentSavings_(historicOriginal, | 216 historicValue: getPercentSavings_(historicOriginal, |
102 historicReceived) | 217 historicReceived) |
103 }); | 218 }); |
104 | 219 |
105 var input = new JsEvalContext({rows: rows}); | 220 var input = new JsEvalContext({rows: rows}); |
106 jstProcess(input, $(BandwidthView.MAIN_BOX_ID)); | 221 jstProcess(input, $(BandwidthView.STATS_BOX_ID)); |
107 return true; | 222 return true; |
223 }, | |
224 | |
225 buildEnabledRow_: function(event, actionCell, detailsCell) { | |
226 if (event.params.enabled == 1) { | |
227 addTextNode(actionCell, 'Proxy: Enabled'); | |
228 var proxyWrapper = addNode(detailsCell, 'div'); | |
229 addNodeWithText(proxyWrapper, 'div', 'Proxy configuration:'); | |
230 | |
231 if (event.params.primary_origin != null && | |
232 event.params.primary_origin.trim() != '') { | |
233 var proxyText = 'Primary: ' + event.params.primary_origin; | |
234 if (event.params.primary_restricted != null && | |
235 event.params.primary_restricted) { | |
236 proxyText += ' (restricted)'; | |
237 } | |
238 addNodeWithText(proxyWrapper, 'div', proxyText); | |
239 } | |
240 | |
241 if (event.params.fallback_origin != null && | |
242 event.params.fallback_origin.trim() != '') { | |
243 var proxyText = 'Fallback: ' + event.params.fallback_origin; | |
244 if (event.params.fallback_restricted != null && | |
245 event.params.fallback_restricted) { | |
246 proxyText += ' (restricted)'; | |
247 } | |
248 addNodeWithText(proxyWrapper, 'div', proxyText); | |
249 } | |
250 | |
251 if (event.params.ssl_origin != null && | |
252 event.params.ssl_origin.trim() != '') { | |
253 addNodeWithText(proxyWrapper, 'div', | |
254 'SSL: ' + event.params.ssl_origin); | |
255 } | |
256 } else { | |
257 addTextNode(actionCell, 'Proxy: Disabled'); | |
258 } | |
259 }, | |
260 | |
261 buildCanaryRow_: function(event, actionCell, detailsCell) { | |
262 if (event.phase == EventPhase.PHASE_BEGIN) { | |
263 addTextNode(actionCell, 'Canary request sent'); | |
264 addTextNode(detailsCell, 'URL: ' + event.params.url); | |
265 } else if (event.phase == EventPhase.PHASE_END) { | |
266 addTextNode(actionCell, 'Canary request completed'); | |
267 if (event.params.net_error == 0) { | |
268 addTextNode(detailsCell, 'Result: OK'); | |
269 } else { | |
270 addTextNode(detailsCell, | |
271 'Result: ' + netErrorToString(event.params.net_error)); | |
272 } | |
273 } | |
274 }, | |
275 | |
276 buildCanaryResponseRow_: function(event, actionCell, detailsCell) { | |
277 addTextNode(actionCell, 'Canary response received'); | |
278 }, | |
279 | |
280 buildBypassRow_: function(event, actionCell, detailsCell) { | |
281 var parsedBypass = this.parseBypassEvent_(event); | |
282 | |
283 addTextNode(actionCell, | |
284 'Bypass received (' + parsedBypass.bypass_reason + ')'); | |
285 var bypassWrapper = addNode(detailsCell, 'div'); | |
286 addNodeWithText(bypassWrapper, 'div', 'URL: ' + parsedBypass.origin_url); | |
287 addNodeWithText( | |
288 bypassWrapper, 'div', | |
289 'Bypassed for ' + parsedBypass.bypass_duration_seconds + ' seconds.'); | |
290 }, | |
291 | |
292 buildFallbackRow_: function(event, actionCell, detailsCell) { | |
293 addTextNode(actionCell, 'Proxy fallback'); | |
294 }, | |
295 | |
296 parseBypassEvent_: function(event) { | |
297 var reason; | |
298 if (event.params.action != null) { | |
299 reason = event.params.action; | |
300 } else { | |
301 switch (event.params.bypass_type) { | |
mmenke
2014/12/05 16:18:25
Suggest a dictionary here instead. More compact a
jeremyim
2014/12/05 21:31:12
Done.
| |
302 case 0: | |
303 reason = 'Bypass current request'; | |
304 break; | |
305 case 1: | |
306 reason = 'Bypass for short period'; | |
307 break; | |
308 case 2: | |
309 reason = 'Bypass for medium period'; | |
310 break; | |
311 case 3: | |
312 reason = 'Bypass for long period'; | |
313 break; | |
314 case 4: | |
315 reason = 'Missing Via Header 4xx'; | |
316 break; | |
317 case 5: | |
318 reason = 'Missing Via Header non-4xx'; | |
319 break; | |
320 case 6: | |
321 reason = '407'; | |
322 break; | |
323 case 7: | |
324 reason = '500'; | |
325 break; | |
326 case 8: | |
327 reason = '502'; | |
328 break; | |
329 case 9: | |
330 reason = '503'; | |
331 break; | |
332 case 10: | |
333 reason = 'Network error'; | |
334 break; | |
335 default: | |
336 reason = 'Unknown error'; | |
337 break; | |
338 } | |
339 } | |
340 | |
341 var parsedBypass = { | |
342 bypass_reason: reason, | |
343 origin_url: event.params.url, | |
344 bypass_duration_seconds: event.params.bypass_duration_seconds, | |
345 bypass_expiration: event.params.expiration, | |
346 }; | |
347 | |
348 return parsedBypass; | |
349 }, | |
350 | |
351 updateDataReductionProxyConfig_: function() { | |
352 $(BandwidthView.PRIMARY_PROXY_ID).innerHTML = ''; | |
353 $(BandwidthView.SECONDARY_PROXY_ID).innerHTML = ''; | |
354 setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), false); | |
355 | |
356 if (this.data_reduction_proxy_config_) { | |
357 var primaryProxy = ''; | |
358 var secondaryProxy = ''; | |
359 var now = timeutil.getCurrentTimeTicks(); | |
360 | |
361 if (this.last_bypass_ && +this.last_bypass_.bypass_expiration > now) { | |
mmenke
2014/12/05 16:18:25
+?
jeremyim
2014/12/05 21:31:12
Forces the expiration ticks (a string) into a numb
| |
362 var input = new JsEvalContext(this.last_bypass_); | |
363 jstProcess(input, $(BandwidthView.BYPASS_STATE_CONTAINER_ID)); | |
364 } else { | |
365 var input = new JsEvalContext(); | |
366 jstProcess(input, $(BandwidthView.BYPASS_STATE_CONTAINER_ID)); | |
367 } | |
368 | |
369 if (this.data_reduction_proxy_config_.ssl_origin) { | |
370 primaryProxy = 'HTTPS Tunnel: ' + this.buildProxyString_( | |
371 this.data_reduction_proxy_config_.ssl_origin, false); | |
372 } | |
373 | |
374 if (this.data_reduction_proxy_config_.primary_origin) { | |
375 var proxyString = this.buildProxyString_( | |
376 this.data_reduction_proxy_config_.primary_origin, | |
377 this.data_reduction_proxy_config_.primary_restricted); | |
378 | |
379 if (primaryProxy == '') { | |
380 primaryProxy = proxyString; | |
381 } else { | |
382 secondaryProxy = proxyString; | |
383 } | |
384 } | |
385 | |
386 if (this.data_reduction_proxy_config_.fallback_origin) { | |
387 var proxyString = this.buildProxyString_( | |
388 this.data_reduction_proxy_config_.fallback_origin, | |
389 this.data_reduction_proxy_config_.fallback_restricted); | |
390 | |
391 if (primaryProxy == '') { | |
392 primaryProxy = proxyString; | |
393 } else if (secondaryProxy == '') { | |
394 secondaryProxy = proxyString; | |
395 } | |
396 } | |
397 | |
398 $(BandwidthView.PRIMARY_PROXY_ID).innerText = primaryProxy; | |
399 $(BandwidthView.SECONDARY_PROXY_ID).innerText = secondaryProxy; | |
400 } | |
401 }, | |
402 | |
403 buildProxyString_: function(proxy, restricted) { | |
404 var normalizedProxy = this.normalize_(proxy); | |
405 var configured = this.isConfigured_(normalizedProxy); | |
406 var bypassed = this.isBypassed_(normalizedProxy); | |
407 var proxyString = ''; | |
408 if (restricted) { | |
409 proxyString += proxy + ' (RESTRICTED)'; | |
410 } | |
411 else if (configured) { | |
mmenke
2014/12/05 16:18:25
merge these two lines.
jeremyim
2014/12/05 21:31:12
Done.
| |
412 proxyString += proxy; | |
413 if (bypassed) { | |
414 proxyString += ' (BYPASSED)'; | |
415 setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), true); | |
mmenke
2014/12/05 16:18:25
This is a really weird side effect to have in "bui
jeremyim
2014/12/05 21:31:12
Done.
| |
416 } else { | |
417 proxyString += ' (ACTIVE)'; | |
418 } | |
419 } | |
420 | |
421 return proxyString; | |
422 }, | |
423 | |
424 normalize_: function(proxy) { | |
mmenke
2014/12/05 16:18:25
This file is badly in need of comments.
jeremyim
2014/12/05 21:31:11
Done.
| |
425 function startsWith(str, prefix) { | |
426 return str.lastIndexOf(prefix, 0) == 0; | |
427 } | |
428 | |
429 function strContains(str, substr, startIndex) { | |
430 return str.indexOf(substr, startIndex) != -1; | |
431 } | |
432 | |
433 function endsWith(str, suffix) { | |
434 return str.indexOf(suffix, str.length - suffix.length) !== -1; | |
435 } | |
436 | |
437 function stripSuffix(str, suffix) { | |
438 if (!endsWith(str, suffix)) | |
439 return str; | |
440 return str.substring(str, str.length - suffix.length); | |
441 } | |
442 | |
443 var normalized = stripSuffix(proxy, '/'); | |
mmenke
2014/12/05 16:18:25
Can we provide a proxy string from C++ that's clos
jeremyim
2014/12/05 21:31:12
Done.
| |
444 | |
445 if (startsWith(normalized, 'http://')) { | |
446 normalized = normalized.substr(7); | |
447 } | |
448 | |
449 if (startsWith(normalized, 'https://')) { | |
450 if (!strContains(normalized, ':', 7)) { | |
451 normalized += ':443'; | |
452 } | |
453 } else if (!strContains(normalized, ':', 0)) { | |
454 normalized += ':80'; | |
455 } | |
456 | |
457 return normalized; | |
458 }, | |
459 | |
460 isConfigured_: function(proxy) { | |
461 for (var index in this.proxy_config_) { | |
462 var entry = this.proxy_config_[index]; | |
463 if (entry == proxy) { | |
464 return true; | |
465 } | |
466 } | |
467 | |
468 return false; | |
469 }, | |
470 | |
471 isBypassed_: function(proxy) { | |
mmenke
2014/12/05 16:18:25
Using different terminology van be confusing (bypa
jeremyim
2014/12/05 21:31:12
Done.
| |
472 var now = timeutil.getCurrentTimeTicks(); | |
473 for (var entry in this.bad_proxy_config_) { | |
474 if (entry == proxy && this.bad_proxy_config_[entry] > now) { | |
475 return true; | |
476 } | |
477 } | |
478 | |
479 return false; | |
108 } | 480 } |
109 }; | 481 }; |
110 | 482 |
111 /** | 483 /** |
112 * Converts bytes to kilobytes rounded to one decimal place. | 484 * Converts bytes to kilobytes rounded to one decimal place. |
113 */ | 485 */ |
114 function bytesToRoundedKilobytes_(val) { | 486 function bytesToRoundedKilobytes_(val) { |
115 return (val / 1024).toFixed(1); | 487 return (val / 1024).toFixed(1); |
116 } | 488 } |
117 | 489 |
118 /** | 490 /** |
119 * Returns bandwidth savings as a percent rounded to one decimal place. | 491 * Returns bandwidth savings as a percent rounded to one decimal place. |
120 */ | 492 */ |
121 function getPercentSavings_(original, received) { | 493 function getPercentSavings_(original, received) { |
122 if (original > 0) { | 494 if (original > 0) { |
123 return ((original - received) * 100 / original).toFixed(1); | 495 return ((original - received) * 100 / original).toFixed(1); |
124 } | 496 } |
125 return '0.0'; | 497 return '0.0'; |
126 } | 498 } |
127 | 499 |
128 return BandwidthView; | 500 return BandwidthView; |
129 })(); | 501 })(); |
OLD | NEW |