// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of the GcDiagnostics class.
 *
 */

goog.provide('goog.debug.GcDiagnostics');

goog.require('goog.debug.Trace');
goog.require('goog.log');
goog.require('goog.userAgent');



/**
 * Class used for singleton goog.debug.GcDiagnostics.  Used to hook into
 * the L2 ActiveX controller to profile garbage collection information in IE.
 * Can be used in combination with tracers (goog.debug.Trace), to provide object
 * allocation counts from within the tracers or used alone by invoking start and
 * stop.
 *
 * See http://go/l2binary for the install.
 * TODO(user): Move the L2 installer somewhere more general.
 * @constructor
 * @private
 */
goog.debug.GcDiagnostics_ = function() {};


/**
 * Install the GcDiagnostics tool.
 */
goog.debug.GcDiagnostics_.prototype.install = function() {
  if (goog.userAgent.IE) {
    /** @preserveTry */
    try {
      var l2Helper = new ActiveXObject('L2.NativeHelper');

      // If using tracers, use the higher precision timer provided by L2.
      if (goog.debug.Trace_) {
        goog.debug.Trace_.now = function() {
          return l2Helper['getMilliSeconds']();
        };
      }

      if (l2Helper['gcTracer']) {
        l2Helper['gcTracer']['installGcTracing']();
        this.gcTracer_ = l2Helper['gcTracer'];
        if (goog.debug.Trace) {
          // If tracers are in use, register the gcTracer so that per tracer
          // allocations are recorded.
          goog.debug.Trace.setGcTracer(this.gcTracer_);
        }
      }
      goog.log.info(this.logger_, 'Installed L2 native helper');
    } catch (e) {
      goog.log.info(this.logger_, 'Failed to install L2 native helper: ' + e);
    }
  }
};


/**
 * Logger for the gcDiagnotics
 * @type {goog.log.Logger}
 * @private
 */
goog.debug.GcDiagnostics_.prototype.logger_ =
    goog.log.getLogger('goog.debug.GcDiagnostics');


/**
 * Starts recording garbage collection information.  If a trace is already in
 * progress, it is ended.
 */
goog.debug.GcDiagnostics_.prototype.start = function() {
  if (this.gcTracer_) {
    if (this.gcTracer_['isTracing']()) {
      this.gcTracer_['endGcTracing']();
    }
    this.gcTracer_['startGcTracing']();
  }
};


/**
 * Stops recording garbage collection information.  Logs details on the garbage
 * collections that occurred between start and stop.  If tracers are in use,
 * adds comments where each GC occurs.
 */
goog.debug.GcDiagnostics_.prototype.stop = function() {
  if (this.gcTracer_ && this.gcTracer_['isTracing']()) {
    var gcTracer = this.gcTracer_;
    this.gcTracer_['endGcTracing']();

    var numGCs = gcTracer['getNumTraces']();
    goog.log.info(this.logger_, '*********GC TRACE*********');
    goog.log.info(this.logger_, 'GC ran ' + numGCs + ' times.');
    var totalTime = 0;
    for (var i = 0; i < numGCs; i++) {
      var trace = gcTracer['getTrace'](i);

      var msStart = trace['gcStartTime'];
      var msElapsed = trace['gcElapsedTime'];

      var msRounded = Math.round(msElapsed * 10) / 10;
      var s = 'GC ' + i + ': ' + msRounded + ' ms, ' +
          'numVValAlloc=' + trace['numVValAlloc'] + ', ' +
          'numVarAlloc=' + trace['numVarAlloc'] + ', ' +
          'numBytesSysAlloc=' + trace['numBytesSysAlloc'];
      if (goog.debug.Trace) {
        goog.debug.Trace.addComment(s, null, msStart);
      }
      goog.log.info(this.logger_, s);
      totalTime += msElapsed;
    }
    if (goog.debug.Trace) {
      goog.debug.Trace.addComment('Total GC time was ' + totalTime + ' ms.');
    }
    goog.log.info(this.logger_, 'Total GC time was ' + totalTime + ' ms.');
    goog.log.info(this.logger_, '*********GC TRACE*********');
  }
};


/**
 * Singleton GcDiagnostics object
 * @type {goog.debug.GcDiagnostics_}
 */
goog.debug.GcDiagnostics = new goog.debug.GcDiagnostics_();
