chromium/third_party/blink/web_tests/http/tests/inspector-protocol/resources/tracing-test.js

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

(class TracingHelper {
  static Phase = {
    BEGIN: 'B',
    END: 'E',
    COMPLETE: 'X',
    INSTANT: 'I',
    ASYNC_BEGIN: 'S',
    ASYNC_STEP_INTO: 'T',
    ASYNC_STEP_PAST: 'p',
    ASYNC_END: 'F',
    NESTABLE_ASYNC_BEGIN: 'b',
    NESTABLE_ASYNC_END: 'e',
    NESTABLE_ASYNC_INSTANT: 'n',
    FLOW_BEGIN: 's',
    FLOW_STEP: 't',
    FLOW_END: 'f',
    METADATA: 'M',
    COUNTER: 'C',
    SAMPLE: 'P',
    CREATE_OBJECT: 'N',
    SNAPSHOT_OBJECT: 'O',
    DELETE_OBJECT: 'D',
    MEMORY_DUMP: 'v',
    MARK: 'R',
    CLOCK_SYNC: 'c',
  }
  constructor(testRunner, session) {
    this._testRunner = testRunner;
    this._session = session;
  }

  startTracing(categories="-*,disabled-by-default-devtools.timeline,devtools.timeline") {
    return this.startTracingWithArguments({ "categories": categories, "type": "", "options": "" });
  }

  startTracingAndSaveAsStream() {
    var args = {
      "categories": "-*,disabled-by-default-devtools.timeline,devtools.timeline",
      "type": "",
      "options": "",
      "transferMode": "ReturnAsStream"
    };
    return this.startTracingWithArguments(args);
  }

  async startTracingWithArguments(args) {
    await this._session.protocol.Tracing.start(args);
    this._testRunner.log("Recording started");
  }

  async stopTracing(filter_re=/devtools.timeline/) {
    var devtoolsEvents = [];

    function dataCollected(reply) {
      var allEvents = reply.params.value;
      var filteredEvents = allEvents.filter(e => filter_re.test(e.cat));
      devtoolsEvents = devtoolsEvents.concat(filteredEvents);
    };

    this._session.protocol.Tracing.onDataCollected(dataCollected);
    this._session.protocol.Tracing.end();
    await this._session.protocol.Tracing.onceTracingComplete();
    this._testRunner.log("Tracing complete");
    this._session.protocol.Tracing.offDataCollected(dataCollected);
    this._devtoolsEvents = devtoolsEvents;
    return devtoolsEvents;
  }

  async stopTracingAndReturnStream() {
    function dataCollected() {
      this._testRunner.log(
        "FAIL: dataCollected event should not be fired when returning trace as stream.");
    }

    this._session.protocol.Tracing.onDataCollected(dataCollected);
    this._session.protocol.Tracing.end();
    var event = await this._session.protocol.Tracing.onceTracingComplete();
    this._testRunner.log("Tracing complete");
    this._session.protocol.Tracing.offDataCollected(dataCollected);
    return event.params.stream;
  }

  retrieveStream(streamHandle, offset, chunkSize) {
    var callback;
    var promise = new Promise(f => callback = f);
    var result = "";
    var had_eof = false;

    var readArguments = { handle: streamHandle };
    if (typeof chunkSize === "number")
      readArguments.size = chunkSize;
    var firstReadArguments = JSON.parse(JSON.stringify(readArguments));
    if (typeof offset === "number")
      firstReadArguments.offset = offset;
    this._session.protocol.IO.read(firstReadArguments).then(message => onChunkRead.call(this, message.result));
    // Assure multiple in-flight reads are fine (also, save on latencies).
    this._session.protocol.IO.read(readArguments).then(message => onChunkRead.call(this, message.result));
    return promise;

    function onChunkRead(response) {
      if (had_eof)
        return;
      result += response.data;
      if (response.eof) {
        // Ignore stray callbacks from proactive read requests.
        had_eof = true;
        if (response.base64Encoded)
          result = atob(result);
        callback(result);
        return;
      }
      this._session.protocol.IO.read(readArguments).then(message => onChunkRead.call(this, message.result));
    }
  }

  findEvents(name, ph, condition) {
    return this._devtoolsEvents.filter(e => e.name === name && e.ph === ph && (!condition || condition(e)));
  }

  findEvent(name, ph, condition) {
    var events = this.findEvents(name, ph, condition);
    if (events.length)
      return events[0];
    throw new Error("Couldn't find event " + name + " / " + ph + "\n\n in " + JSON.stringify(this._devtoolsEvents, null, 2));
  }

  filterEvents(callback) {
    return this._devtoolsEvents.filter(callback);
  }

  async invokeAsyncWithTracing(performActions) {
    await this.startTracing();
    var data = await this._session.evaluateAsync(`(${performActions.toString()})()`);
    await this.stopTracing();
    return data;
  }

  formattedEvents() {
    var formattedEvents = this._devtoolsEvents.map(e => e.name + (e.args.data ? '(' + e.args.data.type + ')' : ''));
    return JSON.stringify(formattedEvents, null, 2);
  }

  logEventShape(evt, excludedProperties = [], exposeProperties = []) {
    // The tts, scope, and tdur fields in trace events are optional, and as
    // such we omit them to prevent flakiness as it may or not be included
    // on each occasion an event is dispatched.
    excludedProperties.push('tts', 'tdur', 'scope');

    const logArray = (prefix, name, array) => {
      let start = name ? `${name}: ` : '';
      start = prefix + start;
      this._testRunner.log(`${start}[`);
      for (const item of array) {
        if (item instanceof Array) {
          logArray(`${prefix}\t`, '', item);
          continue;
        }
        if (item instanceof Object) {
          logObject(`${prefix}\t`, '', item);
          continue;
        }
        this._testRunner.log(`${prefix}\t${typeof item},`);
      }
      this._testRunner.log(`${prefix}]`);
    };
    const logObject = (prefix, name, object) => {
      let start = name ? `${name}: ` : '';
      start = prefix + start;
      this._testRunner.log(`${start}{`);
      for (const key in object) {
        const value = object[key];
        if (excludedProperties.includes(key)) {
          continue;
        }
        if (value instanceof Array) {
          logArray(`${prefix}\t`, key, value);
          continue;
        } else if (value instanceof Object) {
          logObject(`${prefix}\t`, key, value)
          continue;
        }
        const valueOut = exposeProperties.includes(key) ? value : typeof value;
        this._testRunner.log(`${prefix}\t${key}: ${valueOut}`);
      }
      this._testRunner.log(`${prefix}}`);
    };
    if (evt instanceof Array) {
      logArray('', 'Array', evt);
      return;
    }

    logObject('', 'Object', evt);
  }
})