diff options
author | simonmorris@chromium.org <simonmorris@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-09 22:33:03 +0000 |
---|---|---|
committer | simonmorris@chromium.org <simonmorris@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-09 22:33:03 +0000 |
commit | cd93f5fce8153bc8f0ac1ea178869169def1e0f9 (patch) | |
tree | 9be151c8771166e11f0aefc47bd1bab64947b207 | |
parent | e98e3b9f2f4b89a74b1cc2b129108e170ae51f0f (diff) | |
download | chromium_src-cd93f5fce8153bc8f0ac1ea178869169def1e0f9.zip chromium_src-cd93f5fce8153bc8f0ac1ea178869169def1e0f9.tar.gz chromium_src-cd93f5fce8153bc8f0ac1ea178869169def1e0f9.tar.bz2 |
The chromoting client logs connection statistics to the server.
BUG=106208
Review URL: http://codereview.chromium.org/8865005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@113868 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | remoting/remoting.gyp | 1 | ||||
-rw-r--r-- | remoting/webapp/me2mom/choice.html | 1 | ||||
-rw-r--r-- | remoting/webapp/me2mom/client_screen.js | 4 | ||||
-rw-r--r-- | remoting/webapp/me2mom/client_session.js | 43 | ||||
-rw-r--r-- | remoting/webapp/me2mom/debug_log.js | 67 | ||||
-rw-r--r-- | remoting/webapp/me2mom/log_to_server.js | 53 | ||||
-rw-r--r-- | remoting/webapp/me2mom/server_log_entry.js | 174 | ||||
-rw-r--r-- | remoting/webapp/me2mom/stats_accumulator.js | 126 |
8 files changed, 365 insertions, 104 deletions
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index d236613..427aed0 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -127,6 +127,7 @@ 'webapp/me2mom/scale-to-fit.png', 'webapp/me2mom/server_log_entry.js', 'webapp/me2mom/spinner.gif', + 'webapp/me2mom/stats_accumulator.js', 'webapp/me2mom/toolbar.css', 'webapp/me2mom/toolbar.js', 'webapp/me2mom/ui_mode.js', diff --git a/remoting/webapp/me2mom/choice.html b/remoting/webapp/me2mom/choice.html index c7ba334..fa30d0d 100644 --- a/remoting/webapp/me2mom/choice.html +++ b/remoting/webapp/me2mom/choice.html @@ -28,6 +28,7 @@ found in the LICENSE file. <script src="plugin_settings.js"></script> <script src="remoting.js"></script> <script src="server_log_entry.js"></script> + <script src="stats_accumulator.js"></script> <script src="toolbar.js"></script> <script src="ui_mode.js"></script> <script src="util.js"></script> diff --git a/remoting/webapp/me2mom/client_screen.js b/remoting/webapp/me2mom/client_screen.js index 0090c53..1574e47 100644 --- a/remoting/webapp/me2mom/client_screen.js +++ b/remoting/webapp/me2mom/client_screen.js @@ -388,7 +388,9 @@ function updateStatistics_() { remoting.clientSession.state != remoting.ClientSession.State.CONNECTED) { return; } - remoting.debug.updateStatistics(remoting.clientSession.stats()); + var stats = remoting.clientSession.stats(); + remoting.debug.updateStatistics(stats); + remoting.clientSession.logStatistics(stats); // Update the stats once per second. window.setTimeout(updateStatistics_, 1000); } diff --git a/remoting/webapp/me2mom/client_session.js b/remoting/webapp/me2mom/client_session.js index e9ae4bf..464aa50 100644 --- a/remoting/webapp/me2mom/client_session.js +++ b/remoting/webapp/me2mom/client_session.js @@ -68,6 +68,15 @@ remoting.ClientSession.ConnectionError = { NETWORK_FAILURE: 4 }; +// Keys for connection statistics. +remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH = 'video_bandwidth'; +remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE = 'video_frame_rate'; +remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY = 'capture_latency'; +remoting.ClientSession.STATS_KEY_ENCODE_LATENCY = 'encode_latency'; +remoting.ClientSession.STATS_KEY_DECODE_LATENCY = 'decode_latency'; +remoting.ClientSession.STATS_KEY_RENDER_LATENCY = 'render_latency'; +remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY = 'roundtrip_latency'; + /** * The current state of the session. * @type {remoting.ClientSession.State} @@ -413,13 +422,29 @@ remoting.ClientSession.prototype.updateDimensions = function() { * @return {Object.<string, number>} The connection statistics. */ remoting.ClientSession.prototype.stats = function() { - return { - 'video_bandwidth': this.plugin.videoBandwidth, - 'video_frame_rate': this.plugin.videoFrameRate, - 'capture_latency': this.plugin.videoCaptureLatency, - 'encode_latency': this.plugin.videoEncodeLatency, - 'decode_latency': this.plugin.videoDecodeLatency, - 'render_latency': this.plugin.videoRenderLatency, - 'roundtrip_latency': this.plugin.roundTripLatency - }; + var dict = {}; + dict[remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH] = + this.plugin.videoBandwidth; + dict[remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE] = + this.plugin.videoFrameRate; + dict[remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY] = + this.plugin.videoCaptureLatency; + dict[remoting.ClientSession.STATS_KEY_ENCODE_LATENCY] = + this.plugin.videoEncodeLatency; + dict[remoting.ClientSession.STATS_KEY_DECODE_LATENCY] = + this.plugin.videoDecodeLatency; + dict[remoting.ClientSession.STATS_KEY_RENDER_LATENCY] = + this.plugin.videoRenderLatency; + dict[remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY] = + this.plugin.roundTripLatency; + return dict; +}; + +/** + * Logs statistics. + * + * @param {Object.<string, number>} stats + */ +remoting.ClientSession.prototype.logStatistics = function(stats) { + this.logToServer.logStatistics(stats); }; diff --git a/remoting/webapp/me2mom/debug_log.js b/remoting/webapp/me2mom/debug_log.js index 3e0fd47..e60426d 100644 --- a/remoting/webapp/me2mom/debug_log.js +++ b/remoting/webapp/me2mom/debug_log.js @@ -90,7 +90,7 @@ remoting.DebugLog.prototype.toggle = function() { */ remoting.DebugLog.prototype.updateStatistics = function(stats) { var units = ''; - var videoBandwidth = stats['video_bandwidth']; + var videoBandwidth = stats[remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH]; if (videoBandwidth < 1024) { units = 'Bps'; } else if (videoBandwidth < 1048576) { @@ -108,13 +108,24 @@ remoting.DebugLog.prototype.updateStatistics = function(stats) { this.statsElement.innerText = 'Bandwidth: ' + videoBandwidth.toFixed(2) + units + ', Frame Rate: ' + - (stats['video_frame_rate'] ? - stats['video_frame_rate'].toFixed(2) + ' fps' : 'n/a') + - ', Capture: ' + stats['capture_latency'].toFixed(2) + 'ms' + - ', Encode: ' + stats['encode_latency'].toFixed(2) + 'ms' + - ', Decode: ' + stats['decode_latency'].toFixed(2) + 'ms' + - ', Render: ' + stats['render_latency'].toFixed(2) + 'ms' + - ', Latency: ' + stats['roundtrip_latency'].toFixed(2) + 'ms'; + (stats[remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE] ? + stats[remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE].toFixed(2) + + ' fps' : 'n/a') + + ', Capture: ' + + stats[remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY].toFixed(2) + + 'ms' + + ', Encode: ' + + stats[remoting.ClientSession.STATS_KEY_ENCODE_LATENCY].toFixed(2) + + 'ms' + + ', Decode: ' + + stats[remoting.ClientSession.STATS_KEY_DECODE_LATENCY].toFixed(2) + + 'ms' + + ', Render: ' + + stats[remoting.ClientSession.STATS_KEY_RENDER_LATENCY].toFixed(2) + + 'ms' + + ', Latency: ' + + stats[remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY].toFixed(2) + + 'ms'; }; /** @@ -652,48 +663,10 @@ remoting.DebugLog.prototype.prettyIqSet = function(action, iq_list) { return true; } } - } else if (child.nodeName == 'gr:log') { - var log = child; - if (log.childNodes.length != 1) { - return false; - } - if (!this.verifyAttributes(log, 'xmlns:gr')) { - return false; - } - - /** @type {Node} */ - var entry = log.childNodes[0]; - if (!this.verifyAttributes(entry, 'role,event-name,session-state,cpu,' + - 'os-name,browser-version,webapp-version,id')) { - return false; - } - var role = entry.getAttribute('role'); - if (role != 'client') { - return false; - } - var event_name = entry.getAttribute('event-name'); - if (event_name != 'session-state') { - return false; - } - var session_state = entry.getAttribute('session-state'); - this.prettyIqHeading(action, '?', 'log session-state ' + session_state, - null); - - var os_name = entry.getAttribute('os-name'); - var cpu = entry.getAttribute('cpu'); - var browser_version = entry.getAttribute('browser-version'); - var webapp_version = entry.getAttribute('webapp-version'); - this.logIndent(1, os_name + ' ' + cpu + ' Chromium_v' + browser_version + - ' Chromoting_v' + webapp_version); - var remoting_id = entry.getAttribute('id'); - if (remoting_id) { - this.logIndent(1, 'id: ' + remoting_id); - } - return true; } } return false; -} +}; /** * Print out an iq 'error'-type node. diff --git a/remoting/webapp/me2mom/log_to_server.js b/remoting/webapp/me2mom/log_to_server.js index 017bc1a..48f21b2 100644 --- a/remoting/webapp/me2mom/log_to_server.js +++ b/remoting/webapp/me2mom/log_to_server.js @@ -16,7 +16,10 @@ var remoting = remoting || {}; * @constructor */ remoting.LogToServer = function() { - /** @type Array.<string> */ this.pendingEntries = []; + /** @type Array.<string> */ + this.pendingEntries = []; + /** @type {remoting.StatsAccumulator} */ + this.statsAccumulator = new remoting.StatsAccumulator(); }; // Local storage keys. @@ -49,13 +52,50 @@ remoting.LogToServer.prototype.setEnabled = function(enabled) { */ remoting.LogToServer.prototype.logClientSessionStateChange = function(state, connectionError) { - var entry = remoting.ServerLogEntry.prototype.makeClientSessionStateChange( + var entry = remoting.ServerLogEntry.makeClientSessionStateChange( state, connectionError); entry.addHostFields(); entry.addChromeVersionField(); entry.addWebappVersionField(); entry.addIdField(this.getId()); this.log(entry); + // Don't accumulate connection statistics across state changes. + this.logAccumulatedStatistics(); + this.statsAccumulator.empty(); +}; + +/** + * Logs connection statistics. + * @param {Object.<string, number>} stats the connection statistics + */ +remoting.LogToServer.prototype.logStatistics = function(stats) { + // Store the statistics. + this.statsAccumulator.add(stats); + // Send statistics to the server if they've been accumulating for at least + // 60 seconds. + if (this.statsAccumulator.getTimeSinceFirstValue() >= 60 * 1000) { + this.logAccumulatedStatistics(); + } +}; + +/** + * Moves connection statistics from the accumulator to the log server. + * + * If all the statistics are zero, then the accumulator is still emptied, + * but the statistics are not sent to the log server. + * + * @private + */ +remoting.LogToServer.prototype.logAccumulatedStatistics = function() { + var entry = remoting.ServerLogEntry.makeStats(this.statsAccumulator); + if (entry) { + entry.addHostFields(); + entry.addChromeVersionField(); + entry.addWebappVersionField(); + entry.addIdField(this.getId()); + this.log(entry); + } + this.statsAccumulator.empty(); }; /** @@ -68,20 +108,25 @@ remoting.LogToServer.prototype.log = function(entry) { if (!this.isEnabled()) { return; } - // Store a stanza for the entry + // Send the stanza to the debug log. + remoting.debug.log('Enqueueing log entry:'); + entry.toDebugLog(1); + // Store a stanza for the entry. this.pendingEntries.push(entry.toStanza()); // Stop if there's no connection to the server. if (!remoting.wcs) { return; } // Send all pending entries to the server. + remoting.debug.log('Sending ' + this.pendingEntries.length + ' log ' + + ((this.pendingEntries.length == 1) ? 'entry' : 'entries') + + ' to the server.'); var stanza = '<cli:iq to="remoting@bot.talk.google.com" type="set" ' + 'xmlns:cli="jabber:client"><gr:log xmlns:gr="google:remoting">'; while (this.pendingEntries.length > 0) { stanza += /** @type string */ this.pendingEntries.shift(); } stanza += '</gr:log></cli:iq>'; - remoting.debug.logIq(true, stanza); remoting.wcs.sendIq(stanza); }; diff --git a/remoting/webapp/me2mom/server_log_entry.js b/remoting/webapp/me2mom/server_log_entry.js index 892a91a..a8755b9 100644 --- a/remoting/webapp/me2mom/server_log_entry.js +++ b/remoting/webapp/me2mom/server_log_entry.js @@ -21,28 +21,28 @@ remoting.ServerLogEntry = function() { }; /** @private */ -remoting.ServerLogEntry.prototype.KEY_EVENT_NAME_ = 'event-name'; +remoting.ServerLogEntry.KEY_EVENT_NAME_ = 'event-name'; /** @private */ -remoting.ServerLogEntry.prototype.VALUE_EVENT_NAME_SESSION_STATE_ = +remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_ = 'session-state'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_ID_ = 'id'; +remoting.ServerLogEntry.KEY_ID_ = 'id'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_ROLE_ = 'role'; +remoting.ServerLogEntry.KEY_ROLE_ = 'role'; /** @private */ -remoting.ServerLogEntry.prototype.VALUE_ROLE_CLIENT_ = 'client'; +remoting.ServerLogEntry.VALUE_ROLE_CLIENT_ = 'client'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_SESSION_STATE_ = 'session-state'; +remoting.ServerLogEntry.KEY_SESSION_STATE_ = 'session-state'; /** * @private * @param {remoting.ClientSession.State} state * @return {string} */ -remoting.ServerLogEntry.prototype.getValueForSessionState = function(state) { +remoting.ServerLogEntry.getValueForSessionState = function(state) { switch(state) { case remoting.ClientSession.State.UNKNOWN: return 'unknown'; @@ -68,14 +68,14 @@ remoting.ServerLogEntry.prototype.getValueForSessionState = function(state) { }; /** @private */ -remoting.ServerLogEntry.prototype.KEY_CONNECTION_ERROR_ = 'connection-error'; +remoting.ServerLogEntry.KEY_CONNECTION_ERROR_ = 'connection-error'; /** * @private * @param {remoting.ClientSession.ConnectionError} connectionError * @return {string} */ -remoting.ServerLogEntry.prototype.getValueForConnectionError = +remoting.ServerLogEntry.getValueForConnectionError = function(connectionError) { switch(connectionError) { case remoting.ClientSession.ConnectionError.NONE: @@ -94,27 +94,43 @@ remoting.ServerLogEntry.prototype.getValueForConnectionError = } /** @private */ -remoting.ServerLogEntry.prototype.KEY_OS_NAME_ = 'os-name'; +remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_ = + "connection-statistics"; /** @private */ -remoting.ServerLogEntry.prototype.VALUE_OS_NAME_WINDOWS_ = 'Windows'; +remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_ = "video-bandwidth"; /** @private */ -remoting.ServerLogEntry.prototype.VALUE_OS_NAME_LINUX_ = 'Linux'; +remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_ = "capture-latency"; /** @private */ -remoting.ServerLogEntry.prototype.VALUE_OS_NAME_MAC_ = 'Mac'; +remoting.ServerLogEntry.KEY_ENCODE_LATENCY_ = "encode-latency"; /** @private */ -remoting.ServerLogEntry.prototype.VALUE_OS_NAME_CHROMEOS_ = 'ChromeOS'; +remoting.ServerLogEntry.KEY_DECODE_LATENCY_ = "decode-latency"; +/** @private */ +remoting.ServerLogEntry.KEY_RENDER_LATENCY_ = "render-latency"; +/** @private */ +remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_ = "roundtrip-latency"; + +/** @private */ +remoting.ServerLogEntry.KEY_OS_NAME_ = 'os-name'; +/** @private */ +remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_ = 'Windows'; +/** @private */ +remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_ = 'Linux'; +/** @private */ +remoting.ServerLogEntry.VALUE_OS_NAME_MAC_ = 'Mac'; +/** @private */ +remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_ = 'ChromeOS'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_OS_VERSION_ = 'os-version'; +remoting.ServerLogEntry.KEY_OS_VERSION_ = 'os-version'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_CPU_ = 'cpu'; +remoting.ServerLogEntry.KEY_CPU_ = 'cpu'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_BROWSER_VERSION_ = 'browser-version'; +remoting.ServerLogEntry.KEY_BROWSER_VERSION_ = 'browser-version'; /** @private */ -remoting.ServerLogEntry.prototype.KEY_WEBAPP_VERSION_ = 'webapp-version'; +remoting.ServerLogEntry.KEY_WEBAPP_VERSION_ = 'webapp-version'; /** * Sets one field in this log entry. @@ -142,48 +158,119 @@ remoting.ServerLogEntry.prototype.toStanza = function() { }; /** + * Prints this object on the debug log. + * + * @param {number} indentLevel the indentation level + */ +remoting.ServerLogEntry.prototype.toDebugLog = function(indentLevel) { + /** @type Array.<string> */ var fields = []; + for (var key in this.dict) { + fields.push(key + ': ' + this.dict[key]); + } + remoting.debug.logIndent(indentLevel, fields.join(', ')); +}; + +/** * Makes a log entry for a change of client session state. * * @param {remoting.ClientSession.State} state * @param {remoting.ClientSession.ConnectionError} connectionError * @return {remoting.ServerLogEntry} */ -remoting.ServerLogEntry.prototype.makeClientSessionStateChange = - function(state, connectionError) { +remoting.ServerLogEntry.makeClientSessionStateChange = function(state, + connectionError) { var entry = new remoting.ServerLogEntry(); - entry.set(this.KEY_ROLE_, this.VALUE_ROLE_CLIENT_); - entry.set(this.KEY_EVENT_NAME_, this.VALUE_EVENT_NAME_SESSION_STATE_); - entry.set(this.KEY_SESSION_STATE_, this.getValueForSessionState(state)); + entry.set(remoting.ServerLogEntry.KEY_ROLE_, + remoting.ServerLogEntry.VALUE_ROLE_CLIENT_); + entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_, + remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_); + entry.set(remoting.ServerLogEntry.KEY_SESSION_STATE_, + remoting.ServerLogEntry.getValueForSessionState(state)); if (connectionError != remoting.ClientSession.ConnectionError.NONE) { - entry.set(this.KEY_CONNECTION_ERROR_, - this.getValueForConnectionError(connectionError)); + entry.set(remoting.ServerLogEntry.KEY_CONNECTION_ERROR_, + remoting.ServerLogEntry.getValueForConnectionError( + connectionError)); } return entry; }; /** + * Makes a log entry for a set of connection statistics. + * Returns null if all the statistics were zero. + * + * @param {remoting.StatsAccumulator} statsAccumulator + * @return {?remoting.ServerLogEntry} + */ +remoting.ServerLogEntry.makeStats = function(statsAccumulator) { + var entry = new remoting.ServerLogEntry(); + entry.set(remoting.ServerLogEntry.KEY_ROLE_, + remoting.ServerLogEntry.VALUE_ROLE_CLIENT_); + entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_, + remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_); + var nonZero = false; + nonZero |= entry.addStatsField( + remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_, + remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH, statsAccumulator); + nonZero |= entry.addStatsField( + remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_, + remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY, statsAccumulator); + nonZero |= entry.addStatsField( + remoting.ServerLogEntry.KEY_ENCODE_LATENCY_, + remoting.ClientSession.STATS_KEY_ENCODE_LATENCY, statsAccumulator); + nonZero |= entry.addStatsField( + remoting.ServerLogEntry.KEY_DECODE_LATENCY_, + remoting.ClientSession.STATS_KEY_DECODE_LATENCY, statsAccumulator); + nonZero |= entry.addStatsField( + remoting.ServerLogEntry.KEY_RENDER_LATENCY_, + remoting.ClientSession.STATS_KEY_RENDER_LATENCY, statsAccumulator); + nonZero |= entry.addStatsField( + remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_, + remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY, statsAccumulator); + if (nonZero) { + return entry; + } + return null; +}; + +/** + * Adds one connection statistic to a log entry. + * + * @private + * @param {string} entryKey + * @param {string} statsKey + * @param {remoting.StatsAccumulator} statsAccumulator + * @return {boolean} whether the statistic is non-zero + */ +remoting.ServerLogEntry.prototype.addStatsField = function( + entryKey, statsKey, statsAccumulator) { + var val = statsAccumulator.calcMean(statsKey); + this.set(entryKey, val.toString()); + return (val != 0); +}; + +/** * Adds an ID field to this log entry. * * @param {string} id */ remoting.ServerLogEntry.prototype.addIdField = function(id) { - this.set(this.KEY_ID_, id); + this.set(remoting.ServerLogEntry.KEY_ID_, id); } /** * Adds fields describing the host to this log entry. */ remoting.ServerLogEntry.prototype.addHostFields = function() { - var host = this.getHostData(); + var host = remoting.ServerLogEntry.getHostData(); if (host) { if (host.os_name.length > 0) { - this.set(this.KEY_OS_NAME_, host.os_name); + this.set(remoting.ServerLogEntry.KEY_OS_NAME_, host.os_name); } if (host.os_version.length > 0) { - this.set(this.KEY_OS_VERSION_, host.os_version); + this.set(remoting.ServerLogEntry.KEY_OS_VERSION_, host.os_version); } if (host.cpu.length > 0) { - this.set(this.KEY_CPU_, host.cpu); + this.set(remoting.ServerLogEntry.KEY_CPU_, host.cpu); } } }; @@ -194,8 +281,8 @@ remoting.ServerLogEntry.prototype.addHostFields = function() { * @private * @return {{os_name:string, os_version:string, cpu:string} | null} */ -remoting.ServerLogEntry.prototype.getHostData = function() { - return this.extractHostDataFrom(navigator.userAgent); +remoting.ServerLogEntry.getHostData = function() { + return remoting.ServerLogEntry.extractHostDataFrom(navigator.userAgent); }; /** @@ -205,7 +292,7 @@ remoting.ServerLogEntry.prototype.getHostData = function() { * @param {string} s * @return {{os_name:string, os_version:string, cpu:string} | null} */ -remoting.ServerLogEntry.prototype.extractHostDataFrom = function(s) { +remoting.ServerLogEntry.extractHostDataFrom = function(s) { // Sample userAgent strings: // 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 ' + // '(KHTML, like Gecko) Chrome/15.0.874.106 Safari/535.2' @@ -218,7 +305,7 @@ remoting.ServerLogEntry.prototype.extractHostDataFrom = function(s) { var match = new RegExp('Windows NT ([0-9\\.]*)').exec(s); if (match && (match.length >= 2)) { return { - 'os_name': this.VALUE_OS_NAME_WINDOWS_, + 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_, 'os_version': match[1], 'cpu': '' }; @@ -226,7 +313,7 @@ remoting.ServerLogEntry.prototype.extractHostDataFrom = function(s) { match = new RegExp('Linux ([a-zA-Z0-9_]*)').exec(s); if (match && (match.length >= 2)) { return { - 'os_name': this.VALUE_OS_NAME_LINUX_, + 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_, 'os_version' : '', 'cpu': match[1] }; @@ -234,7 +321,7 @@ remoting.ServerLogEntry.prototype.extractHostDataFrom = function(s) { match = new RegExp('([a-zA-Z]*) Mac OS X ([0-9_]*)').exec(s); if (match && (match.length >= 3)) { return { - 'os_name': this.VALUE_OS_NAME_MAC_, + 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_MAC_, 'os_version': match[2].replace(/_/g, '.'), 'cpu': match[1] }; @@ -242,7 +329,7 @@ remoting.ServerLogEntry.prototype.extractHostDataFrom = function(s) { match = new RegExp('CrOS ([a-zA-Z0-9]*) ([0-9.]*)').exec(s); if (match && (match.length >= 3)) { return { - 'os_name': this.VALUE_OS_NAME_CHROMEOS_, + 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_, 'os_version': match[2], 'cpu': match[1] }; @@ -254,9 +341,9 @@ remoting.ServerLogEntry.prototype.extractHostDataFrom = function(s) { * Adds a field specifying the browser version to this log entry. */ remoting.ServerLogEntry.prototype.addChromeVersionField = function() { - var version = this.getChromeVersion(); + var version = remoting.ServerLogEntry.getChromeVersion(); if (version != null) { - this.set(this.KEY_BROWSER_VERSION_, version); + this.set(remoting.ServerLogEntry.KEY_BROWSER_VERSION_, version); } }; @@ -266,8 +353,8 @@ remoting.ServerLogEntry.prototype.addChromeVersionField = function() { * @private * @return {string | null} */ -remoting.ServerLogEntry.prototype.getChromeVersion = function() { - return this.extractChromeVersionFrom(navigator.userAgent); +remoting.ServerLogEntry.getChromeVersion = function() { + return remoting.ServerLogEntry.extractChromeVersionFrom(navigator.userAgent); }; /** @@ -277,7 +364,7 @@ remoting.ServerLogEntry.prototype.getChromeVersion = function() { * @param {string} s * @return {string | null} */ -remoting.ServerLogEntry.prototype.extractChromeVersionFrom = function(s) { +remoting.ServerLogEntry.extractChromeVersionFrom = function(s) { var match = new RegExp('Chrome/([0-9.]*)').exec(s); if (match && (match.length >= 2)) { return match[1]; @@ -289,5 +376,6 @@ remoting.ServerLogEntry.prototype.extractChromeVersionFrom = function(s) { * Adds a field specifying the webapp version to this log entry. */ remoting.ServerLogEntry.prototype.addWebappVersionField = function() { - this.set(this.KEY_WEBAPP_VERSION_, chrome.app.getDetails().version); + this.set(remoting.ServerLogEntry.KEY_WEBAPP_VERSION_, + chrome.app.getDetails().version); }; diff --git a/remoting/webapp/me2mom/stats_accumulator.js b/remoting/webapp/me2mom/stats_accumulator.js new file mode 100644 index 0000000..adf89c5 --- /dev/null +++ b/remoting/webapp/me2mom/stats_accumulator.js @@ -0,0 +1,126 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * The webapp reads the plugin's connection statistics frequently (once per + * second). It logs statistics to the server less frequently, to keep + * bandwidth and storage costs down. This class bridges that gap, by + * accumulating high-frequency numeric data, and providing statistics + * summarising that data. + */ + +'use strict'; + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +/** + * @constructor + */ +remoting.StatsAccumulator = function() { + /** + * A map from names to lists of values. + * @private + * @type Object.<string, Array.<number>> + */ + this.valueLists_ = {}; + + /** + * The first time, after this object was most recently initialized or emptied, + * at which a value was added to this object. + * @private + * @type {?number} + */ + this.timeOfFirstValue_ = null; +}; + +/** + * Adds values to this object. + * + * @param {Object.<string, number>} newValues + */ +remoting.StatsAccumulator.prototype.add = function(newValues) { + for (var key in newValues) { + this.getValueList(key).push(newValues[key]); + } + if (!this.timeOfFirstValue_) { + this.timeOfFirstValue_ = new Date().getTime(); + } +}; + +/** + * Empties this object. + */ +remoting.StatsAccumulator.prototype.empty = function() { + this.valueLists_ = {}; + this.timeOfFirstValue_ = null; +}; + +/** + * Gets the number of milliseconds since the first value was added to this + * object, after this object was most recently initialized or emptied. + * + * @return {number} milliseconds since the first value + */ +remoting.StatsAccumulator.prototype.getTimeSinceFirstValue = function() { + if (!this.timeOfFirstValue_) { + return 0; + } + return new Date().getTime() - this.timeOfFirstValue_; +}; + +/** + * Calculates the mean of the values for a given key. + * + * @param {string} key + * @return {number} the mean of the values for that key + */ +remoting.StatsAccumulator.prototype.calcMean = function(key) { + /** + * @param {Array.<number>} values + * @return {number} + */ + var calcMean = function(values) { + if (values.length == 0) { + return 0.0; + } + var sum = 0; + for (var i = 0; i < values.length; i++) { + sum += values[i]; + } + return sum / values.length; + }; + return this.map(key, calcMean); +}; + +/** + * Applies a given map to the list of values for a given key. + * + * @param {string} key + * @param {function(Array.<number>): number} map + * @return {number} the result of applying that map to the list of values for + * that key + */ +remoting.StatsAccumulator.prototype.map = function(key, map) { + return map(this.getValueList(key)); +}; + +/** + * Gets the list of values for a given key. + * If this object contains no values for that key, then this routine creates + * an empty list, stores it in this object, and returns it. + * + * @private + * @param {string} key + * @return {Array.<number>} the list of values for that key + */ +remoting.StatsAccumulator.prototype.getValueList = function(key) { + var valueList = this.valueLists_[key]; + if (!valueList) { + valueList = []; + this.valueLists_[key] = valueList; + } + return valueList; +}; |