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.PROBE_STATUS_ID = 'data-reduction-proxy-probe-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_: 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 return this.onBadProxiesChanged(data.badProxies) && |
43 // reason to display it when debugging a loaded log file. | 67 this.onProxySettingsChanged(data.proxySettings) && |
44 return false; | 68 this.onDataReductionProxyInfoChanged(data.dataReductionProxyInfo) && |
| 69 (this.onSessionNetworkStatsChanged(data.sessionNetworkStats) || |
| 70 this.onHistoricNetworkStatsChanged(data.historicNetworkStats)); |
45 }, | 71 }, |
46 | 72 |
47 /** | 73 /** |
48 * Retains information on bandwidth usage this session. | 74 * Retains information on bandwidth usage this session. |
49 */ | 75 */ |
50 onSessionNetworkStatsChanged: function(sessionNetworkStats) { | 76 onSessionNetworkStatsChanged: function(sessionNetworkStats) { |
51 this.sessionNetworkStats_ = sessionNetworkStats; | 77 this.sessionNetworkStats_ = sessionNetworkStats; |
52 return this.updateBandwidthUsageTable_(); | 78 return this.updateBandwidthUsageTable_(); |
53 }, | 79 }, |
54 | 80 |
55 /** | 81 /** |
56 * Displays information on bandwidth usage this session and over the | 82 * Displays information on bandwidth usage this session and over the |
57 * browser's lifetime. | 83 * browser's lifetime. |
58 */ | 84 */ |
59 onHistoricNetworkStatsChanged: function(historicNetworkStats) { | 85 onHistoricNetworkStatsChanged: function(historicNetworkStats) { |
60 this.historicNetworkStats_ = historicNetworkStats; | 86 this.historicNetworkStats_ = historicNetworkStats; |
61 return this.updateBandwidthUsageTable_(); | 87 return this.updateBandwidthUsageTable_(); |
62 }, | 88 }, |
63 | 89 |
64 /** | 90 /** |
| 91 * Updates the UI based on receiving changes in information about the |
| 92 * data reduction proxy summary. |
| 93 */ |
| 94 onDataReductionProxyInfoChanged: function(info) { |
| 95 $(BandwidthView.EVENTS_TBODY_ID).innerHTML = ''; |
| 96 |
| 97 if (!info) |
| 98 return false; |
| 99 |
| 100 if (info.enabled) { |
| 101 $(BandwidthView.ENABLED_ID).innerText = 'Enabled'; |
| 102 $(BandwidthView.PROBE_STATUS_ID).innerText = |
| 103 info.probe != null ? info.probe : 'N/A'; |
| 104 this.last_bypass_ = info.last_bypass != null ? |
| 105 this.parseBypassEvent_(info.last_bypass) : null; |
| 106 this.data_reduction_proxy_config_ = info.proxy_config.params; |
| 107 } else { |
| 108 $(BandwidthView.ENABLED_ID).innerText = 'Disabled'; |
| 109 $(BandwidthView.PROBE_STATUS_ID).innerText = 'N/A'; |
| 110 this.data_reduction_proxy_config_ = null; |
| 111 } |
| 112 |
| 113 this.updateDataReductionProxyConfig_(); |
| 114 |
| 115 for (var eventIndex = info.events.length - 1; eventIndex >= 0; |
| 116 --eventIndex) { |
| 117 var event = info.events[eventIndex]; |
| 118 var headerRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr'); |
| 119 var detailsRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr'); |
| 120 |
| 121 var timeCell = addNode(headerRow, 'td'); |
| 122 var actionCell = addNode(headerRow, 'td'); |
| 123 var detailsCell = addNode(detailsRow, 'td'); |
| 124 detailsCell.colSpan = 2; |
| 125 detailsCell.className = 'data-reduction-proxy-view-events-details'; |
| 126 var eventTime = timeutil.convertTimeTicksToDate(event.time); |
| 127 timeutil.addNodeWithDate(timeCell, eventTime); |
| 128 |
| 129 switch (event.type) { |
| 130 case EventType.DATA_REDUCTION_PROXY_ENABLED: |
| 131 this.buildEnabledRow_(event, actionCell, detailsCell); |
| 132 break; |
| 133 case EventType.DATA_REDUCTION_PROXY_CANARY_REQUEST: |
| 134 this.buildProbeRow_(event, actionCell, detailsCell); |
| 135 break; |
| 136 case EventType.DATA_REDUCTION_PROXY_CANARY_RESPONSE_RECEIVED: |
| 137 this.buildProbeResponseRow_(event, actionCell, detailsCell); |
| 138 break; |
| 139 case EventType.DATA_REDUCTION_PROXY_BYPASS_REQUESTED: |
| 140 this.buildBypassRow_(event, actionCell, detailsCell); |
| 141 break; |
| 142 case EventType.DATA_REDUCTION_PROXY_FALLBACK: |
| 143 this.buildFallbackRow_(event, actionCell, detailsCell); |
| 144 break; |
| 145 } |
| 146 } |
| 147 |
| 148 return true; |
| 149 }, |
| 150 |
| 151 /** |
| 152 * Updates the UI based on receiving changes in information about the |
| 153 * proxy settings. |
| 154 */ |
| 155 onProxySettingsChanged: function(proxySettings) { |
| 156 if (!proxySettings) |
| 157 return false; |
| 158 |
| 159 var newProxySettings = []; |
| 160 var effectiveSettings = proxySettings.effective; |
| 161 if (effectiveSettings && effectiveSettings.proxy_per_scheme) { |
| 162 for (var scheme in effectiveSettings.proxy_per_scheme) { |
| 163 var schemeSettings = effectiveSettings.proxy_per_scheme[scheme]; |
| 164 if (scheme != 'fallback') { |
| 165 for (var i = 0; i < schemeSettings.length; ++i) { |
| 166 var proxyUri = schemeSettings[i]; |
| 167 if (proxyUri != 'direct://') |
| 168 newProxySettings.push(proxyUri); |
| 169 } |
| 170 } |
| 171 } |
| 172 } |
| 173 this.proxy_config_ = newProxySettings; |
| 174 this.updateDataReductionProxyConfig_(); |
| 175 |
| 176 return true; |
| 177 }, |
| 178 |
| 179 /** |
| 180 * Updates the UI based on receiving changes in information about bad |
| 181 * proxy servers. |
| 182 */ |
| 183 onBadProxiesChanged: function(badProxies) { |
| 184 if (!badProxies) |
| 185 return false; |
| 186 |
| 187 var newBadProxies = []; |
| 188 if (badProxies.length == 0) { |
| 189 this.last_bypass_ = null; |
| 190 } else { |
| 191 for (var i = 0; i < badProxies.length; ++i) { |
| 192 var entry = badProxies[i]; |
| 193 newBadProxies[entry.proxy_uri] = entry.bad_until; |
| 194 } |
| 195 } |
| 196 this.bad_proxy_config_ = newBadProxies; |
| 197 this.updateDataReductionProxyConfig_(); |
| 198 |
| 199 return true; |
| 200 }, |
| 201 |
| 202 /** |
65 * Update the bandwidth usage table. Returns false on failure. | 203 * Update the bandwidth usage table. Returns false on failure. |
66 */ | 204 */ |
67 updateBandwidthUsageTable_: function() { | 205 updateBandwidthUsageTable_: function() { |
68 var sessionNetworkStats = this.sessionNetworkStats_; | 206 var sessionNetworkStats = this.sessionNetworkStats_; |
69 var historicNetworkStats = this.historicNetworkStats_; | 207 var historicNetworkStats = this.historicNetworkStats_; |
70 if (!sessionNetworkStats || !historicNetworkStats) | 208 if (!sessionNetworkStats || !historicNetworkStats) |
71 return false; | 209 return false; |
72 | 210 |
73 var sessionOriginal = sessionNetworkStats.session_original_content_length; | 211 var sessionOriginal = sessionNetworkStats.session_original_content_length; |
74 var sessionReceived = sessionNetworkStats.session_received_content_length; | 212 var sessionReceived = sessionNetworkStats.session_received_content_length; |
(...skipping 21 matching lines...) Expand all Loading... |
96 bytesToRoundedKilobytes_(historicOriginal - historicReceived) | 234 bytesToRoundedKilobytes_(historicOriginal - historicReceived) |
97 }); | 235 }); |
98 rows.push({ | 236 rows.push({ |
99 title: 'Savings (%)', | 237 title: 'Savings (%)', |
100 sessionValue: getPercentSavings_(sessionOriginal, sessionReceived), | 238 sessionValue: getPercentSavings_(sessionOriginal, sessionReceived), |
101 historicValue: getPercentSavings_(historicOriginal, | 239 historicValue: getPercentSavings_(historicOriginal, |
102 historicReceived) | 240 historicReceived) |
103 }); | 241 }); |
104 | 242 |
105 var input = new JsEvalContext({rows: rows}); | 243 var input = new JsEvalContext({rows: rows}); |
106 jstProcess(input, $(BandwidthView.MAIN_BOX_ID)); | 244 jstProcess(input, $(BandwidthView.STATS_BOX_ID)); |
107 return true; | 245 return true; |
| 246 }, |
| 247 |
| 248 /** |
| 249 * Renders a data reduction proxy enabled/disabled event into the event |
| 250 * tbody. |
| 251 */ |
| 252 buildEnabledRow_: function(event, actionCell, detailsCell) { |
| 253 if (event.params.enabled == 1) { |
| 254 addTextNode(actionCell, 'Proxy: Enabled'); |
| 255 var proxyWrapper = addNode(detailsCell, 'div'); |
| 256 addNodeWithText(proxyWrapper, 'div', 'Proxy configuration:'); |
| 257 |
| 258 if (event.params.primary_origin != null && |
| 259 event.params.primary_origin.trim() != '') { |
| 260 var proxyText = 'Primary: ' + event.params.primary_origin; |
| 261 if (event.params.primary_restricted != null && |
| 262 event.params.primary_restricted) { |
| 263 proxyText += ' (restricted)'; |
| 264 } |
| 265 addNodeWithText(proxyWrapper, 'div', proxyText); |
| 266 } |
| 267 |
| 268 if (event.params.fallback_origin != null && |
| 269 event.params.fallback_origin.trim() != '') { |
| 270 var proxyText = 'Fallback: ' + event.params.fallback_origin; |
| 271 if (event.params.fallback_restricted != null && |
| 272 event.params.fallback_restricted) { |
| 273 proxyText += ' (restricted)'; |
| 274 } |
| 275 addNodeWithText(proxyWrapper, 'div', proxyText); |
| 276 } |
| 277 |
| 278 if (event.params.ssl_origin != null && |
| 279 event.params.ssl_origin.trim() != '') { |
| 280 addNodeWithText(proxyWrapper, 'div', |
| 281 'SSL: ' + event.params.ssl_origin); |
| 282 } |
| 283 } else { |
| 284 addTextNode(actionCell, 'Proxy: Disabled'); |
| 285 } |
| 286 }, |
| 287 |
| 288 /** |
| 289 * Renders a Data Reduction Proxy probe request event into the event |
| 290 * tbody. |
| 291 */ |
| 292 buildProbeRow_: function(event, actionCell, detailsCell) { |
| 293 if (event.phase == EventPhase.PHASE_BEGIN) { |
| 294 addTextNode(actionCell, 'Probe request sent'); |
| 295 addTextNode(detailsCell, 'URL: ' + event.params.url); |
| 296 } else if (event.phase == EventPhase.PHASE_END) { |
| 297 addTextNode(actionCell, 'Probe request completed'); |
| 298 if (event.params.net_error == 0) { |
| 299 addTextNode(detailsCell, 'Result: OK'); |
| 300 } else { |
| 301 addTextNode(detailsCell, |
| 302 'Result: ' + netErrorToString(event.params.net_error)); |
| 303 } |
| 304 } |
| 305 }, |
| 306 |
| 307 /** |
| 308 * Renders a Data Reduction Proxy probe response event into the event |
| 309 * tbody. |
| 310 */ |
| 311 buildProbeResponseRow_: function(event, actionCell, detailsCell) { |
| 312 addTextNode(actionCell, 'Probe response received'); |
| 313 }, |
| 314 |
| 315 /** |
| 316 * Renders a data reduction proxy bypass event into the event tbody. |
| 317 */ |
| 318 buildBypassRow_: function(event, actionCell, detailsCell) { |
| 319 var parsedBypass = this.parseBypassEvent_(event); |
| 320 |
| 321 addTextNode(actionCell, |
| 322 'Bypass received (' + parsedBypass.bypass_reason + ')'); |
| 323 var bypassWrapper = addNode(detailsCell, 'div'); |
| 324 addNodeWithText(bypassWrapper, 'div', 'URL: ' + parsedBypass.origin_url); |
| 325 addNodeWithText( |
| 326 bypassWrapper, 'div', |
| 327 'Bypassed for ' + parsedBypass.bypass_duration_seconds + ' seconds.'); |
| 328 }, |
| 329 |
| 330 /** |
| 331 * Renders a data reduction proxy fallback event into the event tbody. |
| 332 */ |
| 333 buildFallbackRow_: function(event, actionCell, detailsCell) { |
| 334 addTextNode(actionCell, 'Proxy fallback'); |
| 335 }, |
| 336 |
| 337 /** |
| 338 * Parses a data reduction proxy bypass event for use in the summary and |
| 339 * in the event tbody. |
| 340 */ |
| 341 parseBypassEvent_: function(event) { |
| 342 var reason; |
| 343 if (event.params.action != null) { |
| 344 reason = event.params.action; |
| 345 } else { |
| 346 reason = getKeyWithValue( |
| 347 DataReductionProxyBypassEventType, event.params.bypass_type); |
| 348 } |
| 349 |
| 350 var parsedBypass = { |
| 351 bypass_reason: reason, |
| 352 origin_url: event.params.url, |
| 353 bypass_duration_seconds: event.params.bypass_duration_seconds, |
| 354 bypass_expiration: event.params.expiration, |
| 355 }; |
| 356 |
| 357 return parsedBypass; |
| 358 }, |
| 359 |
| 360 /** |
| 361 * Updates the data reduction proxy summary block. |
| 362 */ |
| 363 updateDataReductionProxyConfig_: function() { |
| 364 $(BandwidthView.PRIMARY_PROXY_ID).innerHTML = ''; |
| 365 $(BandwidthView.SECONDARY_PROXY_ID).innerHTML = ''; |
| 366 setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), false); |
| 367 |
| 368 if (this.data_reduction_proxy_config_) { |
| 369 var primaryProxy = ''; |
| 370 var secondaryProxy = ''; |
| 371 var hasBypassedProxy = false; |
| 372 var now = timeutil.getCurrentTimeTicks(); |
| 373 |
| 374 if (this.last_bypass_ && |
| 375 this.hasTimePassedLogTime_(+this.last_bypass_.bypass_expiration)) { |
| 376 var input = new JsEvalContext(this.last_bypass_); |
| 377 jstProcess(input, $(BandwidthView.BYPASS_STATE_CONTAINER_ID)); |
| 378 } else { |
| 379 var input = new JsEvalContext(); |
| 380 jstProcess(input, $(BandwidthView.BYPASS_STATE_CONTAINER_ID)); |
| 381 } |
| 382 |
| 383 if (this.data_reduction_proxy_config_.ssl_origin) { |
| 384 if (this.isMarkedAsBad_(this.data_reduction_proxy_config_.ssl_origin)) |
| 385 hasBypassedProxy = true; |
| 386 |
| 387 primaryProxy = 'HTTPS Tunnel: ' + this.buildProxyString_( |
| 388 this.data_reduction_proxy_config_.ssl_origin, false); |
| 389 } |
| 390 |
| 391 if (this.data_reduction_proxy_config_.primary_origin) { |
| 392 if (this.isMarkedAsBad_( |
| 393 this.data_reduction_proxy_config_.primary_origin)) |
| 394 hasBypassedProxy = true; |
| 395 |
| 396 var proxyString = this.buildProxyString_( |
| 397 this.data_reduction_proxy_config_.primary_origin, |
| 398 this.data_reduction_proxy_config_.primary_restricted); |
| 399 |
| 400 if (primaryProxy == '') |
| 401 primaryProxy = proxyString; |
| 402 else |
| 403 secondaryProxy = proxyString; |
| 404 } |
| 405 |
| 406 if (this.data_reduction_proxy_config_.fallback_origin) { |
| 407 if (this.isMarkedAsBad_( |
| 408 this.data_reduction_proxy_config_.fallback_origin)) |
| 409 hasBypassedProxy = true; |
| 410 |
| 411 var proxyString = this.buildProxyString_( |
| 412 this.data_reduction_proxy_config_.fallback_origin, |
| 413 this.data_reduction_proxy_config_.fallback_restricted); |
| 414 |
| 415 if (primaryProxy == '') |
| 416 primaryProxy = proxyString; |
| 417 else if (secondaryProxy == '') |
| 418 secondaryProxy = proxyString; |
| 419 } |
| 420 |
| 421 $(BandwidthView.PRIMARY_PROXY_ID).innerText = primaryProxy; |
| 422 $(BandwidthView.SECONDARY_PROXY_ID).innerText = secondaryProxy; |
| 423 if (hasBypassedProxy) |
| 424 setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), true); |
| 425 } |
| 426 }, |
| 427 |
| 428 /** |
| 429 * Takes a data reduction proxy configuration and renders to a friendly |
| 430 * string. |
| 431 */ |
| 432 buildProxyString_: function(proxy, restricted) { |
| 433 var configured = this.isConfigured_(proxy); |
| 434 var markedAsBad = this.isMarkedAsBad_(proxy); |
| 435 var proxyString = ''; |
| 436 if (restricted) { |
| 437 proxyString += proxy + ' (RESTRICTED)'; |
| 438 } else if (configured) { |
| 439 proxyString += proxy; |
| 440 if (markedAsBad) { |
| 441 proxyString += ' (BYPASSED)'; |
| 442 } else { |
| 443 proxyString += ' (ACTIVE)'; |
| 444 } |
| 445 } |
| 446 |
| 447 return proxyString; |
| 448 }, |
| 449 |
| 450 /** |
| 451 * Checks to see if a proxy server is in the current configuration. |
| 452 */ |
| 453 isConfigured_: function(proxy) { |
| 454 for (var index in this.proxy_config_) { |
| 455 var entry = this.proxy_config_[index]; |
| 456 if (entry == proxy) |
| 457 return true; |
| 458 } |
| 459 |
| 460 return false; |
| 461 }, |
| 462 |
| 463 /** |
| 464 * Checks to see if a proxy server is in marked as bad. |
| 465 */ |
| 466 isMarkedAsBad_: function(proxy) { |
| 467 for (var entry in this.bad_proxy_config_) { |
| 468 if (entry == proxy && |
| 469 this.hasTimePassedLogTime_(this.bad_proxy_config_[entry])) { |
| 470 return true; |
| 471 } |
| 472 } |
| 473 |
| 474 return false; |
| 475 }, |
| 476 |
| 477 /** |
| 478 * Checks to see if a given time in ticks has passed the time of the |
| 479 * the log. For real time viewing, this is "now", but for loaded logs, it |
| 480 * is the time at which the logs were taken. |
| 481 */ |
| 482 hasTimePassedLogTime_: function(timeTicks) { |
| 483 var logTime; |
| 484 if (MainView.isViewingLoadedLog() && ClientInfo.numericDate) { |
| 485 logTime = ClientInfo.numericDate; |
| 486 } else { |
| 487 logTime = timeutil.getCurrentTime(); |
| 488 } |
| 489 |
| 490 return timeutil.convertTimeTicksToTime(timeTicks) > logTime; |
108 } | 491 } |
109 }; | 492 }; |
110 | 493 |
111 /** | 494 /** |
112 * Converts bytes to kilobytes rounded to one decimal place. | 495 * Converts bytes to kilobytes rounded to one decimal place. |
113 */ | 496 */ |
114 function bytesToRoundedKilobytes_(val) { | 497 function bytesToRoundedKilobytes_(val) { |
115 return (val / 1024).toFixed(1); | 498 return (val / 1024).toFixed(1); |
116 } | 499 } |
117 | 500 |
118 /** | 501 /** |
119 * Returns bandwidth savings as a percent rounded to one decimal place. | 502 * Returns bandwidth savings as a percent rounded to one decimal place. |
120 */ | 503 */ |
121 function getPercentSavings_(original, received) { | 504 function getPercentSavings_(original, received) { |
122 if (original > 0) { | 505 if (original > 0) { |
123 return ((original - received) * 100 / original).toFixed(1); | 506 return ((original - received) * 100 / original).toFixed(1); |
124 } | 507 } |
125 return '0.0'; | 508 return '0.0'; |
126 } | 509 } |
127 | 510 |
128 return BandwidthView; | 511 return BandwidthView; |
129 })(); | 512 })(); |
OLD | NEW |