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 3// found in the LICENSE file. 4 5/** 6 * @fileoverview 7 * A class of server log entries. 8 */ 9 10'use strict'; 11 12/** @suppress {duplicate} */ 13var remoting = remoting || {}; 14 15/** 16 * @private 17 * @constructor 18 */ 19remoting.ServerLogEntry = function() { 20 /** @type Object.<string, string> */ this.dict = {}; 21}; 22 23/** @private */ 24remoting.ServerLogEntry.KEY_EVENT_NAME_ = 'event-name'; 25/** @private */ 26remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_ = 27 'session-state'; 28 29/** @private */ 30remoting.ServerLogEntry.KEY_SESSION_ID_ = 'session-id'; 31 32/** @private */ 33remoting.ServerLogEntry.KEY_ROLE_ = 'role'; 34/** @private */ 35remoting.ServerLogEntry.VALUE_ROLE_CLIENT_ = 'client'; 36 37/** @private */ 38remoting.ServerLogEntry.KEY_SESSION_STATE_ = 'session-state'; 39 40/** 41 * @private 42 * @param {remoting.ClientSession.State} state 43 * @return {string} 44 */ 45remoting.ServerLogEntry.getValueForSessionState = function(state) { 46 switch(state) { 47 case remoting.ClientSession.State.UNKNOWN: 48 return 'unknown'; 49 case remoting.ClientSession.State.CREATED: 50 return 'created'; 51 case remoting.ClientSession.State.CONNECTING: 52 return 'connecting'; 53 case remoting.ClientSession.State.INITIALIZING: 54 return 'initializing'; 55 case remoting.ClientSession.State.CONNECTED: 56 return 'connected'; 57 case remoting.ClientSession.State.CLOSED: 58 return 'closed'; 59 case remoting.ClientSession.State.FAILED: 60 return 'connection-failed'; 61 case remoting.ClientSession.State.CONNECTION_DROPPED: 62 return 'connection-dropped'; 63 case remoting.ClientSession.State.CONNECTION_CANCELED: 64 return 'connection-canceled'; 65 default: 66 return 'undefined-' + state; 67 } 68}; 69 70/** @private */ 71remoting.ServerLogEntry.KEY_CONNECTION_ERROR_ = 'connection-error'; 72 73/** 74 * @private 75 * @param {remoting.Error} connectionError 76 * @return {string} 77 */ 78remoting.ServerLogEntry.getValueForError = 79 function(connectionError) { 80 switch(connectionError) { 81 case remoting.Error.NONE: 82 return 'none'; 83 case remoting.Error.INVALID_ACCESS_CODE: 84 return 'invalid-access-code'; 85 case remoting.Error.MISSING_PLUGIN: 86 return 'missing_plugin'; 87 case remoting.Error.AUTHENTICATION_FAILED: 88 return 'authentication-failed'; 89 case remoting.Error.HOST_IS_OFFLINE: 90 return 'host-is-offline'; 91 case remoting.Error.INCOMPATIBLE_PROTOCOL: 92 return 'incompatible-protocol'; 93 case remoting.Error.BAD_PLUGIN_VERSION: 94 return 'bad-plugin-version'; 95 case remoting.Error.NETWORK_FAILURE: 96 return 'network-failure'; 97 case remoting.Error.HOST_OVERLOAD: 98 return 'host-overload'; 99 case remoting.Error.P2P_FAILURE: 100 return 'p2p-failure'; 101 case remoting.Error.UNEXPECTED: 102 return 'unexpected'; 103 default: 104 return 'unknown-' + connectionError; 105 } 106}; 107 108/** @private */ 109remoting.ServerLogEntry.KEY_SESSION_DURATION_ = 'session-duration'; 110 111/** @private */ 112remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_ = 113 "connection-statistics"; 114/** @private */ 115remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_ = "video-bandwidth"; 116/** @private */ 117remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_ = "capture-latency"; 118/** @private */ 119remoting.ServerLogEntry.KEY_ENCODE_LATENCY_ = "encode-latency"; 120/** @private */ 121remoting.ServerLogEntry.KEY_DECODE_LATENCY_ = "decode-latency"; 122/** @private */ 123remoting.ServerLogEntry.KEY_RENDER_LATENCY_ = "render-latency"; 124/** @private */ 125remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_ = "roundtrip-latency"; 126 127/** @private */ 128remoting.ServerLogEntry.KEY_OS_NAME_ = 'os-name'; 129/** @private */ 130remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_ = 'Windows'; 131/** @private */ 132remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_ = 'Linux'; 133/** @private */ 134remoting.ServerLogEntry.VALUE_OS_NAME_MAC_ = 'Mac'; 135/** @private */ 136remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_ = 'ChromeOS'; 137 138/** @private */ 139remoting.ServerLogEntry.KEY_OS_VERSION_ = 'os-version'; 140 141/** @private */ 142remoting.ServerLogEntry.KEY_CPU_ = 'cpu'; 143 144/** @private */ 145remoting.ServerLogEntry.KEY_BROWSER_VERSION_ = 'browser-version'; 146 147/** @private */ 148remoting.ServerLogEntry.KEY_WEBAPP_VERSION_ = 'webapp-version'; 149 150/** @private */ 151remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_ = 'session-id-old'; 152 153/** @private */ 154remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_ = 'session-id-new'; 155 156/** @private */ 157remoting.ServerLogEntry.KEY_MODE_ = 'mode'; 158/** @private */ 159remoting.ServerLogEntry.VALUE_MODE_IT2ME_ = 'it2me'; 160/** @private */ 161remoting.ServerLogEntry.VALUE_MODE_ME2ME_ = 'me2me'; 162/** @private */ 163remoting.ServerLogEntry.VALUE_MODE_UNKNOWN_ = 'unknown'; 164 165/** 166 * Sets one field in this log entry. 167 * 168 * @private 169 * @param {string} key 170 * @param {string} value 171 */ 172remoting.ServerLogEntry.prototype.set = function(key, value) { 173 this.dict[key] = value; 174}; 175 176/** 177 * Converts this object into an XML stanza. 178 * 179 * @return {string} 180 */ 181remoting.ServerLogEntry.prototype.toStanza = function() { 182 var stanza = '<gr:entry '; 183 for (var key in this.dict) { 184 stanza += escape(key) + '="' + escape(this.dict[key]) + '" '; 185 } 186 stanza += '/>'; 187 return stanza; 188}; 189 190/** 191 * Prints this object on the debug log. 192 * 193 * @param {number} indentLevel the indentation level 194 */ 195remoting.ServerLogEntry.prototype.toDebugLog = function(indentLevel) { 196 /** @type Array.<string> */ var fields = []; 197 for (var key in this.dict) { 198 fields.push(key + ': ' + this.dict[key]); 199 } 200 console.log(Array(indentLevel+1).join(" ") + fields.join(', ')); 201}; 202 203/** 204 * Makes a log entry for a change of client session state. 205 * 206 * @param {remoting.ClientSession.State} state 207 * @param {remoting.Error} connectionError 208 * @param {remoting.ClientSession.Mode} mode 209 * @return {remoting.ServerLogEntry} 210 */ 211remoting.ServerLogEntry.makeClientSessionStateChange = function(state, 212 connectionError, mode) { 213 var entry = new remoting.ServerLogEntry(); 214 entry.set(remoting.ServerLogEntry.KEY_ROLE_, 215 remoting.ServerLogEntry.VALUE_ROLE_CLIENT_); 216 entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_, 217 remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_); 218 entry.set(remoting.ServerLogEntry.KEY_SESSION_STATE_, 219 remoting.ServerLogEntry.getValueForSessionState(state)); 220 if (connectionError != remoting.Error.NONE) { 221 entry.set(remoting.ServerLogEntry.KEY_CONNECTION_ERROR_, 222 remoting.ServerLogEntry.getValueForError(connectionError)); 223 } 224 entry.addModeField(mode); 225 return entry; 226}; 227 228/** 229 * Adds a session duration to a log entry. 230 * 231 * @param {number} sessionDuration 232 */ 233remoting.ServerLogEntry.prototype.addSessionDurationField = function( 234 sessionDuration) { 235 this.set(remoting.ServerLogEntry.KEY_SESSION_DURATION_, 236 sessionDuration.toString()); 237}; 238 239/** 240 * Makes a log entry for a set of connection statistics. 241 * Returns null if all the statistics were zero. 242 * 243 * @param {remoting.StatsAccumulator} statsAccumulator 244 * @param {remoting.ClientSession.Mode} mode 245 * @return {?remoting.ServerLogEntry} 246 */ 247remoting.ServerLogEntry.makeStats = function(statsAccumulator, mode) { 248 var entry = new remoting.ServerLogEntry(); 249 entry.set(remoting.ServerLogEntry.KEY_ROLE_, 250 remoting.ServerLogEntry.VALUE_ROLE_CLIENT_); 251 entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_, 252 remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_); 253 entry.addModeField(mode); 254 var nonZero = false; 255 nonZero |= entry.addStatsField( 256 remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_, 257 remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH, statsAccumulator); 258 nonZero |= entry.addStatsField( 259 remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_, 260 remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY, statsAccumulator); 261 nonZero |= entry.addStatsField( 262 remoting.ServerLogEntry.KEY_ENCODE_LATENCY_, 263 remoting.ClientSession.STATS_KEY_ENCODE_LATENCY, statsAccumulator); 264 nonZero |= entry.addStatsField( 265 remoting.ServerLogEntry.KEY_DECODE_LATENCY_, 266 remoting.ClientSession.STATS_KEY_DECODE_LATENCY, statsAccumulator); 267 nonZero |= entry.addStatsField( 268 remoting.ServerLogEntry.KEY_RENDER_LATENCY_, 269 remoting.ClientSession.STATS_KEY_RENDER_LATENCY, statsAccumulator); 270 nonZero |= entry.addStatsField( 271 remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_, 272 remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY, statsAccumulator); 273 if (nonZero) { 274 return entry; 275 } 276 return null; 277}; 278 279/** 280 * Adds one connection statistic to a log entry. 281 * 282 * @private 283 * @param {string} entryKey 284 * @param {string} statsKey 285 * @param {remoting.StatsAccumulator} statsAccumulator 286 * @return {boolean} whether the statistic is non-zero 287 */ 288remoting.ServerLogEntry.prototype.addStatsField = function( 289 entryKey, statsKey, statsAccumulator) { 290 var val = statsAccumulator.calcMean(statsKey); 291 this.set(entryKey, val.toFixed(2)); 292 return (val != 0); 293}; 294 295/** 296 * Makes a log entry for a "this session ID is old" event. 297 * 298 * @param {string} sessionId 299 * @param {remoting.ClientSession.Mode} mode 300 * @return {remoting.ServerLogEntry} 301 */ 302remoting.ServerLogEntry.makeSessionIdOld = function(sessionId, mode) { 303 var entry = new remoting.ServerLogEntry(); 304 entry.set(remoting.ServerLogEntry.KEY_ROLE_, 305 remoting.ServerLogEntry.VALUE_ROLE_CLIENT_); 306 entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_, 307 remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_); 308 entry.addSessionIdField(sessionId); 309 entry.addModeField(mode); 310 return entry; 311}; 312 313/** 314 * Makes a log entry for a "this session ID is new" event. 315 * 316 * @param {string} sessionId 317 * @param {remoting.ClientSession.Mode} mode 318 * @return {remoting.ServerLogEntry} 319 */ 320remoting.ServerLogEntry.makeSessionIdNew = function(sessionId, mode) { 321 var entry = new remoting.ServerLogEntry(); 322 entry.set(remoting.ServerLogEntry.KEY_ROLE_, 323 remoting.ServerLogEntry.VALUE_ROLE_CLIENT_); 324 entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_, 325 remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_); 326 entry.addSessionIdField(sessionId); 327 entry.addModeField(mode); 328 return entry; 329}; 330 331/** 332 * Adds a session ID field to this log entry. 333 * 334 * @param {string} sessionId 335 */ 336remoting.ServerLogEntry.prototype.addSessionIdField = function(sessionId) { 337 this.set(remoting.ServerLogEntry.KEY_SESSION_ID_, sessionId); 338} 339 340/** 341 * Adds fields describing the host to this log entry. 342 */ 343remoting.ServerLogEntry.prototype.addHostFields = function() { 344 var host = remoting.ServerLogEntry.getHostData(); 345 if (host) { 346 if (host.os_name.length > 0) { 347 this.set(remoting.ServerLogEntry.KEY_OS_NAME_, host.os_name); 348 } 349 if (host.os_version.length > 0) { 350 this.set(remoting.ServerLogEntry.KEY_OS_VERSION_, host.os_version); 351 } 352 if (host.cpu.length > 0) { 353 this.set(remoting.ServerLogEntry.KEY_CPU_, host.cpu); 354 } 355 } 356}; 357 358/** 359 * Extracts host data from the userAgent string. 360 * 361 * @private 362 * @return {{os_name:string, os_version:string, cpu:string} | null} 363 */ 364remoting.ServerLogEntry.getHostData = function() { 365 return remoting.ServerLogEntry.extractHostDataFrom(navigator.userAgent); 366}; 367 368/** 369 * Extracts host data from the given userAgent string. 370 * 371 * @private 372 * @param {string} s 373 * @return {{os_name:string, os_version:string, cpu:string} | null} 374 */ 375remoting.ServerLogEntry.extractHostDataFrom = function(s) { 376 // Sample userAgent strings: 377 // 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 ' + 378 // '(KHTML, like Gecko) Chrome/15.0.874.106 Safari/535.2' 379 // 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.8 ' + 380 // '(KHTML, like Gecko) Chrome/17.0.933.0 Safari/535.8' 381 // 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 ' + 382 // '(KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1' 383 // 'Mozilla/5.0 (X11; CrOS i686 14.811.154) AppleWebKit/535.1 ' + 384 // '(KHTML, like Gecko) Chrome/14.0.835.204 Safari/535.1' 385 var match = new RegExp('Windows NT ([0-9\\.]*)').exec(s); 386 if (match && (match.length >= 2)) { 387 return { 388 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_, 389 'os_version': match[1], 390 'cpu': '' 391 }; 392 } 393 match = new RegExp('Linux ([a-zA-Z0-9_]*)').exec(s); 394 if (match && (match.length >= 2)) { 395 return { 396 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_, 397 'os_version' : '', 398 'cpu': match[1] 399 }; 400 } 401 match = new RegExp('([a-zA-Z]*) Mac OS X ([0-9_]*)').exec(s); 402 if (match && (match.length >= 3)) { 403 return { 404 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_MAC_, 405 'os_version': match[2].replace(/_/g, '.'), 406 'cpu': match[1] 407 }; 408 } 409 match = new RegExp('CrOS ([a-zA-Z0-9]*) ([0-9.]*)').exec(s); 410 if (match && (match.length >= 3)) { 411 return { 412 'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_, 413 'os_version': match[2], 414 'cpu': match[1] 415 }; 416 } 417 return null; 418}; 419 420/** 421 * Adds a field specifying the browser version to this log entry. 422 */ 423remoting.ServerLogEntry.prototype.addChromeVersionField = function() { 424 var version = remoting.getChromeVersion(); 425 if (version != null) { 426 this.set(remoting.ServerLogEntry.KEY_BROWSER_VERSION_, version); 427 } 428}; 429 430/** 431 * Adds a field specifying the webapp version to this log entry. 432 */ 433remoting.ServerLogEntry.prototype.addWebappVersionField = function() { 434 var manifest = chrome.runtime.getManifest(); 435 if (manifest && manifest.version) { 436 this.set(remoting.ServerLogEntry.KEY_WEBAPP_VERSION_, manifest.version); 437 } 438}; 439 440/** 441 * Adds a field specifying the mode to this log entry. 442 * 443 * @param {remoting.ClientSession.Mode} mode 444 */ 445remoting.ServerLogEntry.prototype.addModeField = function(mode) { 446 this.set(remoting.ServerLogEntry.KEY_MODE_, 447 remoting.ServerLogEntry.getModeField(mode)); 448}; 449 450/** 451 * Gets the value of the mode field to be put in a log entry. 452 * 453 * @private 454 * @param {remoting.ClientSession.Mode} mode 455 * @return {string} 456 */ 457remoting.ServerLogEntry.getModeField = function(mode) { 458 switch(mode) { 459 case remoting.ClientSession.Mode.IT2ME: 460 return remoting.ServerLogEntry.VALUE_MODE_IT2ME_; 461 case remoting.ClientSession.Mode.ME2ME: 462 return remoting.ServerLogEntry.VALUE_MODE_ME2ME_; 463 default: 464 return remoting.ServerLogEntry.VALUE_MODE_UNKNOWN_; 465 } 466}; 467