Home | History | Annotate | Download | only in webapp
      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} */
     13 var remoting = remoting || {};
     14 
     15 /**
     16  * @private
     17  * @constructor
     18  */
     19 remoting.ServerLogEntry = function() {
     20   /** @type Object.<string, string> */ this.dict = {};
     21 };
     22 
     23 /** @private */
     24 remoting.ServerLogEntry.KEY_EVENT_NAME_ = 'event-name';
     25 /** @private */
     26 remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_ =
     27     'session-state';
     28 
     29 /** @private */
     30 remoting.ServerLogEntry.KEY_SESSION_ID_ = 'session-id';
     31 
     32 /** @private */
     33 remoting.ServerLogEntry.KEY_ROLE_ = 'role';
     34 /** @private */
     35 remoting.ServerLogEntry.VALUE_ROLE_CLIENT_ = 'client';
     36 
     37 /** @private */
     38 remoting.ServerLogEntry.KEY_SESSION_STATE_ = 'session-state';
     39 
     40 /**
     41  * @private
     42  * @param {remoting.ClientSession.State} state
     43  * @return {string}
     44  */
     45 remoting.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 */
     71 remoting.ServerLogEntry.KEY_CONNECTION_ERROR_ = 'connection-error';
     72 
     73 /**
     74  * @private
     75  * @param {remoting.Error} connectionError
     76  * @return {string}
     77  */
     78 remoting.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 */
    109 remoting.ServerLogEntry.KEY_SESSION_DURATION_ = 'session-duration';
    110 
    111 /** @private */
    112 remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_ =
    113     "connection-statistics";
    114 /** @private */
    115 remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_ = "video-bandwidth";
    116 /** @private */
    117 remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_ = "capture-latency";
    118 /** @private */
    119 remoting.ServerLogEntry.KEY_ENCODE_LATENCY_ = "encode-latency";
    120 /** @private */
    121 remoting.ServerLogEntry.KEY_DECODE_LATENCY_ = "decode-latency";
    122 /** @private */
    123 remoting.ServerLogEntry.KEY_RENDER_LATENCY_ = "render-latency";
    124 /** @private */
    125 remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_ = "roundtrip-latency";
    126 
    127 /** @private */
    128 remoting.ServerLogEntry.KEY_OS_NAME_ = 'os-name';
    129 /** @private */
    130 remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_ = 'Windows';
    131 /** @private */
    132 remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_ = 'Linux';
    133 /** @private */
    134 remoting.ServerLogEntry.VALUE_OS_NAME_MAC_ = 'Mac';
    135 /** @private */
    136 remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_ = 'ChromeOS';
    137 
    138 /** @private */
    139 remoting.ServerLogEntry.KEY_OS_VERSION_ = 'os-version';
    140 
    141 /** @private */
    142 remoting.ServerLogEntry.KEY_CPU_ = 'cpu';
    143 
    144 /** @private */
    145 remoting.ServerLogEntry.KEY_BROWSER_VERSION_ = 'browser-version';
    146 
    147 /** @private */
    148 remoting.ServerLogEntry.KEY_WEBAPP_VERSION_ = 'webapp-version';
    149 
    150 /** @private */
    151 remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_ = 'session-id-old';
    152 
    153 /** @private */
    154 remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_ = 'session-id-new';
    155 
    156 /** @private */
    157 remoting.ServerLogEntry.KEY_MODE_ = 'mode';
    158 /** @private */
    159 remoting.ServerLogEntry.VALUE_MODE_IT2ME_ = 'it2me';
    160 /** @private */
    161 remoting.ServerLogEntry.VALUE_MODE_ME2ME_ = 'me2me';
    162 /** @private */
    163 remoting.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  */
    172 remoting.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  */
    181 remoting.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  */
    195 remoting.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  */
    211 remoting.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  */
    233 remoting.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  */
    247 remoting.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  */
    288 remoting.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  */
    302 remoting.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  */
    320 remoting.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  */
    336 remoting.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  */
    343 remoting.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  */
    364 remoting.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  */
    375 remoting.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  */
    423 remoting.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  */
    433 remoting.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  */
    445 remoting.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  */
    457 remoting.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