Javascript  |  319行  |  9.63 KB

// Copyright (c) 2012 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.

'use strict';

/**
 * @fileoverview State and UI for trace data collection.
 */
base.requireStylesheet('about_tracing.tracing_controller');

base.require('base.properties');
base.require('base.events');
base.require('ui.overlay');

base.exportTo('about_tracing', function() {

  /**
   * The tracing controller is responsible for talking to tracing_ui.cc in
   * chrome
   * @constructor
   * @param {function(String, opt_Array.<String>} Function to be used to send
   * data to chrome.
   */
  function TracingController(sendFn) {
    this.sendFn_ = sendFn;
    this.overlay_ = new ui.Overlay();
    this.overlay_.className = 'tracing-overlay';

    this.statusDiv_ = document.createElement('div');
    this.overlay_.appendChild(this.statusDiv_);

    this.bufferPercentDiv_ = document.createElement('div');
    this.overlay_.appendChild(this.bufferPercentDiv_);

    this.stopButton_ = document.createElement('button');
    this.stopButton_.onclick = this.endTracing.bind(this);
    this.stopButton_.textContent = 'Stop tracing';
    this.overlay_.appendChild(this.stopButton_);

    this.traceEventData_ = undefined;
    this.systemTraceEvents_ = undefined;

    this.onKeydown_ = this.onKeydown_.bind(this);
    this.onKeypress_ = this.onKeypress_.bind(this);

    this.supportsSystemTracing_ = base.isChromeOS;

    if (this.sendFn_)
      this.sendFn_('tracingControllerInitialized');
  }

  TracingController.prototype = {
    __proto__: base.EventTarget.prototype,

    gpuInfo_: undefined,
    clientInfo_: undefined,
    tracingEnabled_: false,
    tracingEnding_: false,
    systemTraceDataFilename_: undefined,

    get supportsSystemTracing() {
      return this.supportsSystemTracing_;
    },

    onRequestBufferPercentFullComplete: function(percent_full) {
      if (!this.overlay_.visible)
        return;

      window.setTimeout(this.beginRequestBufferPercentFull_.bind(this), 500);

      var newText = 'Buffer usage: ' +
          Math.round(100 * percent_full) + '%';
      if (this.bufferPercentDiv_.textContent != newText)
        this.bufferPercentDiv_.textContent = newText;
    },

    /**
     * Begin requesting the buffer fullness
     */
    beginRequestBufferPercentFull_: function() {
      this.sendFn_('beginRequestBufferPercentFull');
    },

    /**
     * Called by info_view to empty the trace buffer
     *
     * |opt_trace_categories| is a comma-delimited list of category wildcards.
     * A category can have an optional '-' prefix to make it an excluded
     * category.  All the same rules apply above, so for example, having both
     * included and excluded categories in the same list would not be
     * supported.
     *
     * Example: beginTracing("test_MyTest*");
     * Example: beginTracing("test_MyTest*,test_OtherStuff");
     * Example: beginTracing("-excluded_category1,-excluded_category2");
     */
    beginTracing: function(opt_systemTracingEnabled, opt_trace_continuous,
                           opt_enableSampling, opt_trace_categories) {
      if (this.tracingEnabled_)
        throw new Error('Tracing already begun.');

      this.stopButton_.hidden = false;
      this.statusDiv_.textContent = 'Tracing active.';
      this.overlay_.obeyCloseEvents = false;
      this.overlay_.visible = true;

      this.tracingEnabled_ = true;

      console.log('Beginning to trace...');
      this.statusDiv_.textContent = 'Tracing active.';

      var trace_options = [];
      trace_options.push(opt_trace_continuous ? 'record-continuously' :
                                                'record-until-full');
      if (opt_enableSampling)
        trace_options.push('enable-sampling');

      this.traceEventData_ = undefined;
      this.systemTraceEvents_ = undefined;
      this.sendFn_(
          'beginTracing',
          [
           opt_systemTracingEnabled || false,
           opt_trace_categories || '-test_*',
           trace_options.join(',')
          ]
      );
      this.beginRequestBufferPercentFull_();

      window.addEventListener('keypress', this.onKeypress_);
      window.addEventListener('keydown', this.onKeydown_);
    },

    onKeydown_: function(e) {
      if (e.keyCode == 27) {
        this.endTracing();
      }
    },

    onKeypress_: function(e) {
      if (e.keyIdentifier == 'Enter') {
        this.endTracing();
      }
    },

    /**
     * Called from gpu c++ code when ClientInfo is updated.
     */
    onClientInfoUpdate: function(clientInfo) {
      this.clientInfo_ = clientInfo;
    },

    /**
     * Called from gpu c++ code when GPU Info is updated.
     */
    onGpuInfoUpdate: function(gpuInfo) {
      this.gpuInfo_ = gpuInfo;
    },

    /**
     * Checks whether tracing is enabled
     */
    get isTracingEnabled() {
      return this.tracingEnabled_;
    },

    /**
     * Gets the currently traced events. If tracing is active, then
     * this can change on the fly.
     */
    get traceEventData() {
      return this.traceEventData_;
    },

    /**
     * Called to finish tracing and update all views.
     */
    endTracing: function() {
      if (!this.tracingEnabled_) throw new Error('Tracing not begun.');
      if (this.tracingEnding_) return;
      this.tracingEnding_ = true;

      this.statusDiv_.textContent = 'Ending trace...';
      console.log('Finishing trace');
      this.statusDiv_.textContent = 'Downloading trace data...';
      this.stopButton_.hidden = true;
      // delay sending endTracingAsync until we get a chance to
      // update the screen...
      var that = this;
      window.setTimeout(function() {
        that.sendFn_('endTracingAsync');
      }, 100);
    },

    /**
     * Called by the browser when all processes complete tracing.
     */
    onEndTracingComplete: function(traceDataString) {
      window.removeEventListener('keydown', this.onKeydown_);
      window.removeEventListener('keypress', this.onKeypress_);
      this.overlay_.visible = false;
      this.tracingEnabled_ = false;
      this.tracingEnding_ = false;

      if (traceDataString[traceDataString.length - 1] == ',')
        traceDataString = traceDataString.substr(0, traceDataString.length - 1);
      if (traceDataString[0] != '[')
        traceDataString = '[' + traceDataString;
      if (traceDataString[traceDataString.length - 1] != ']')
        traceDataString = traceDataString + ']';

      this.traceEventData_ = traceDataString;

      console.log('onEndTracingComplete p1 with ' +
                  this.traceEventData_.length + ' bytes of data.');
      var e = new base.Event('traceEnded');
      this.dispatchEvent(e);
    },

    collectCategories: function() {
      this.sendFn_('getKnownCategories');
    },

    onKnownCategoriesCollected: function(categories) {
      var e = new base.Event('categoriesCollected');
      e.categories = categories;
      this.dispatchEvent(e);
    },


    /**
     * Called by tracing c++ code when new system trace data arrives.
     */
    onSystemTraceDataCollected: function(events) {
      console.log('onSystemTraceDataCollected with ' +
                  events.length + ' chars of data.');
      this.systemTraceEvents_ = events;
    },

    /**
     * Gets the currentl system trace events. If tracing is active, then
     * this can change on the fly.
     */
    get systemTraceEvents() {
      return this.systemTraceEvents_;
    },

    /**
     * Tells browser to put up a load dialog and load the trace file
     */
    beginLoadTraceFile: function() {
      this.sendFn_('loadTraceFile');
    },

    /**
     * Called by the browser when a trace file is loaded.
     */
    onLoadTraceFileComplete: function(traceDataString, opt_filename) {
      this.traceEventData_ = traceDataString;
      this.systemTraceEvents_ = undefined;

      var e = new base.Event('loadTraceFileComplete');
      e.filename = opt_filename || '';
      this.dispatchEvent(e);
    },

    /**
     * Called by the browser when loading a trace file was canceled.
     */
    onLoadTraceFileCanceled: function() {
      base.dispatchSimpleEvent(this, 'loadTraceFileCanceled');
    },

    /**
     * Tells browser to put up a save dialog and save the trace file
     */
    beginSaveTraceFile: function() {
      // this.traceEventData_ is already in JSON form, but now need to insert it
      // into a data structure containing metadata about the recording. To do
      // this "right," we should parse the traceEventData_, make the new data
      // structure and then JSONize the lot. But, the traceEventData_ is huge so
      // parsing it and stringifying it again is going to consume time and
      // memory.
      //
      // Instead, we make the new data strcture with a placeholder string,
      // JSONify it, then replace the placeholder string with the
      // traceEventData_.
      var data = {
        traceEvents: '__TRACE_EVENT_PLACEHOLDER__',
        systemTraceEvents: this.systemTraceEvents_,
        clientInfo: this.clientInfo_,
        gpuInfo: this.gpuInfo_
      };
      var dataAsString = JSON.stringify(data);
      dataAsString = dataAsString.replace('"__TRACE_EVENT_PLACEHOLDER__"',
                                          this.traceEventData_);
      this.sendFn_('saveTraceFile', [dataAsString]);
    },

    /**
     * Called by the browser when a trace file is saveed.
     */
    onSaveTraceFileComplete: function() {
      base.dispatchSimpleEvent(this, 'saveTraceFileComplete');
    },

    /**
     * Called by the browser when saving a trace file was canceled.
     */
    onSaveTraceFileCanceled: function() {
      base.dispatchSimpleEvent(this, 'saveTraceFileCanceled');
    }
  };
  return {
    TracingController: TracingController
  };
});