All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.dirigible.dev-tools.timeline_model.TimelineFrameModel.js Maven / Gradle / Ivy

There is a newer version: 10.6.27
Show newest version
/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import * as SDK from '../sdk/sdk.js';

import {RecordType, TimelineData} from './TimelineModel.js';
import {TracingLayerTree} from './TracingLayerTree.js';

/**
 * @unrestricted
 */
export class TimelineFrameModel {
  /**
   * @param {function(!SDK.TracingModel.Event):string} categoryMapper
   */
  constructor(categoryMapper) {
    this._categoryMapper = categoryMapper;
    this.reset();
  }

  /**
   * @param {number=} startTime
   * @param {number=} endTime
   * @return {!Array}
   */
  frames(startTime, endTime) {
    if (!startTime && !endTime) {
      return this._frames;
    }
    const firstFrame = this._frames.lowerBound(startTime || 0, (time, frame) => time - frame.endTime);
    const lastFrame = this._frames.lowerBound(endTime || Infinity, (time, frame) => time - frame.startTime);
    return this._frames.slice(firstFrame, lastFrame);
  }

  /**
   * @param {!SDK.TracingModel.Event} rasterTask
   * @return {boolean}
   */
  hasRasterTile(rasterTask) {
    const data = rasterTask.args['tileData'];
    if (!data) {
      return false;
    }
    const frameId = data['sourceFrameNumber'];
    const frame = frameId && this._frameById[frameId];
    if (!frame || !frame.layerTree) {
      return false;
    }
    return true;
  }

  /**
   * @param {!SDK.TracingModel.Event} rasterTask
   * @return Promise}
   */
  rasterTilePromise(rasterTask) {
    if (!this._target) {
      return Promise.resolve(null);
    }
    const data = rasterTask.args['tileData'];
    const frameId = data['sourceFrameNumber'];
    const tileId = data['tileId'] && data['tileId']['id_ref'];
    const frame = frameId && this._frameById[frameId];
    if (!frame || !frame.layerTree || !tileId) {
      return Promise.resolve(null);
    }

    return frame.layerTree.layerTreePromise().then(layerTree => layerTree && layerTree.pictureForRasterTile(tileId));
  }

  reset() {
    this._minimumRecordTime = Infinity;
    this._frames = [];
    this._frameById = {};
    this._lastFrame = null;
    this._lastLayerTree = null;
    this._mainFrameCommitted = false;
    this._mainFrameRequested = false;
    this._framePendingCommit = null;
    this._lastBeginFrame = null;
    this._lastNeedsBeginFrame = null;
    this._framePendingActivation = null;
    this._lastTaskBeginTime = null;
    this._target = null;
    this._layerTreeId = null;
    this._currentTaskTimeByCategory = {};
  }

  /**
   * @param {number} startTime
   */
  handleBeginFrame(startTime) {
    if (!this._lastFrame) {
      this._startFrame(startTime);
    }
    this._lastBeginFrame = startTime;
  }

  /**
   * @param {number} startTime
   */
  handleDrawFrame(startTime) {
    if (!this._lastFrame) {
      this._startFrame(startTime);
      return;
    }

    // - if it wasn't drawn, it didn't happen!
    // - only show frames that either did not wait for the main thread frame or had one committed.
    if (this._mainFrameCommitted || !this._mainFrameRequested) {
      if (this._lastNeedsBeginFrame) {
        const idleTimeEnd = this._framePendingActivation ? this._framePendingActivation.triggerTime :
                                                           (this._lastBeginFrame || this._lastNeedsBeginFrame);
        if (idleTimeEnd > this._lastFrame.startTime) {
          this._lastFrame.idle = true;
          this._startFrame(idleTimeEnd);
          if (this._framePendingActivation) {
            this._commitPendingFrame();
          }
          this._lastBeginFrame = null;
        }
        this._lastNeedsBeginFrame = null;
      }
      this._startFrame(startTime);
    }
    this._mainFrameCommitted = false;
  }

  handleActivateLayerTree() {
    if (!this._lastFrame) {
      return;
    }
    if (this._framePendingActivation && !this._lastNeedsBeginFrame) {
      this._commitPendingFrame();
    }
  }

  handleRequestMainThreadFrame() {
    if (!this._lastFrame) {
      return;
    }
    this._mainFrameRequested = true;
  }

  handleCompositeLayers() {
    if (!this._framePendingCommit) {
      return;
    }
    this._framePendingActivation = this._framePendingCommit;
    this._framePendingCommit = null;
    this._mainFrameRequested = false;
    this._mainFrameCommitted = true;
  }

  /**
   * @param {!TracingFrameLayerTree} layerTree
   */
  handleLayerTreeSnapshot(layerTree) {
    this._lastLayerTree = layerTree;
  }

  /**
   * @param {number} startTime
   * @param {boolean} needsBeginFrame
   */
  handleNeedFrameChanged(startTime, needsBeginFrame) {
    if (needsBeginFrame) {
      this._lastNeedsBeginFrame = startTime;
    }
  }

  /**
   * @param {number} startTime
   */
  _startFrame(startTime) {
    if (this._lastFrame) {
      this._flushFrame(this._lastFrame, startTime);
    }
    this._lastFrame = new TimelineFrame(startTime, startTime - this._minimumRecordTime);
  }

  /**
   * @param {!TimelineFrame} frame
   * @param {number} endTime
   */
  _flushFrame(frame, endTime) {
    frame._setLayerTree(this._lastLayerTree);
    frame._setEndTime(endTime);
    if (this._lastLayerTree) {
      this._lastLayerTree._setPaints(frame._paints);
    }
    if (this._frames.length &&
        (frame.startTime !== this._frames.peekLast().endTime || frame.startTime > frame.endTime)) {
      console.assert(
          false, `Inconsistent frame time for frame ${this._frames.length} (${frame.startTime} - ${frame.endTime})`);
    }
    this._frames.push(frame);
    if (typeof frame._mainFrameId === 'number') {
      this._frameById[frame._mainFrameId] = frame;
    }
  }

  _commitPendingFrame() {
    this._lastFrame._addTimeForCategories(this._framePendingActivation.timeByCategory);
    this._lastFrame._paints = this._framePendingActivation.paints;
    this._lastFrame._mainFrameId = this._framePendingActivation.mainFrameId;
    this._framePendingActivation = null;
  }

  /**
   * @param {?SDK.SDKModel.Target} target
   * @param {!Array.} events
   * @param {!Array} threadData
   */
  addTraceEvents(target, events, threadData) {
    this._target = target;
    let j = 0;
    this._currentProcessMainThread = threadData.length && threadData[0].thread || null;
    for (let i = 0; i < events.length; ++i) {
      while (j + 1 < threadData.length && threadData[j + 1].time <= events[i].startTime) {
        this._currentProcessMainThread = threadData[++j].thread;
      }
      this._addTraceEvent(events[i]);
    }
    this._currentProcessMainThread = null;
  }

  /**
   * @param {!SDK.TracingModel.Event} event
   */
  _addTraceEvent(event) {
    const eventNames = RecordType;
    if (event.startTime && event.startTime < this._minimumRecordTime) {
      this._minimumRecordTime = event.startTime;
    }

    if (event.name === eventNames.SetLayerTreeId) {
      this._layerTreeId = event.args['layerTreeId'] || event.args['data']['layerTreeId'];
    } else if (
        event.phase === SDK.TracingModel.Phase.SnapshotObject && event.name === eventNames.LayerTreeHostImplSnapshot &&
        parseInt(event.id, 0) === this._layerTreeId) {
      const snapshot = /** @type {!SDK.TracingModel.ObjectSnapshot} */ (event);
      this.handleLayerTreeSnapshot(new TracingFrameLayerTree(this._target, snapshot));
    } else {
      this._processCompositorEvents(event);
      if (event.thread === this._currentProcessMainThread) {
        this._addMainThreadTraceEvent(event);
      } else if (this._lastFrame && event.selfTime && !SDK.TracingModel.TracingModel.isTopLevelEvent(event)) {
        this._lastFrame._addTimeForCategory(this._categoryMapper(event), event.selfTime);
      }
    }
  }

  /**
   * @param {!SDK.TracingModel.Event} event
   */
  _processCompositorEvents(event) {
    const eventNames = RecordType;

    if (event.args['layerTreeId'] !== this._layerTreeId) {
      return;
    }

    const timestamp = event.startTime;
    if (event.name === eventNames.BeginFrame) {
      this.handleBeginFrame(timestamp);
    } else if (event.name === eventNames.DrawFrame) {
      this.handleDrawFrame(timestamp);
    } else if (event.name === eventNames.ActivateLayerTree) {
      this.handleActivateLayerTree();
    } else if (event.name === eventNames.RequestMainThreadFrame) {
      this.handleRequestMainThreadFrame();
    } else if (event.name === eventNames.NeedsBeginFrameChanged) {
      this.handleNeedFrameChanged(timestamp, event.args['data'] && event.args['data']['needsBeginFrame']);
    }
  }

  /**
   * @param {!SDK.TracingModel.Event} event
   */
  _addMainThreadTraceEvent(event) {
    const eventNames = RecordType;

    if (SDK.TracingModel.TracingModel.isTopLevelEvent(event)) {
      this._currentTaskTimeByCategory = {};
      this._lastTaskBeginTime = event.startTime;
    }
    if (!this._framePendingCommit && TimelineFrameModel._mainFrameMarkers.indexOf(event.name) >= 0) {
      this._framePendingCommit =
          new PendingFrame(this._lastTaskBeginTime || event.startTime, this._currentTaskTimeByCategory);
    }
    if (!this._framePendingCommit) {
      this._addTimeForCategory(this._currentTaskTimeByCategory, event);
      return;
    }
    this._addTimeForCategory(this._framePendingCommit.timeByCategory, event);

    if (event.name === eventNames.BeginMainThreadFrame && event.args['data'] && event.args['data']['frameId']) {
      this._framePendingCommit.mainFrameId = event.args['data']['frameId'];
    }
    if (event.name === eventNames.Paint && event.args['data']['layerId'] && TimelineData.forEvent(event).picture &&
        this._target) {
      this._framePendingCommit.paints.push(new LayerPaintEvent(event, this._target));
    }
    if (event.name === eventNames.CompositeLayers && event.args['layerTreeId'] === this._layerTreeId) {
      this.handleCompositeLayers();
    }
  }

  /**
   * @param {!Object.} timeByCategory
   * @param {!SDK.TracingModel.Event} event
   */
  _addTimeForCategory(timeByCategory, event) {
    if (!event.selfTime) {
      return;
    }
    const categoryName = this._categoryMapper(event);
    timeByCategory[categoryName] = (timeByCategory[categoryName] || 0) + event.selfTime;
  }
}

TimelineFrameModel._mainFrameMarkers = [
  RecordType.ScheduleStyleRecalculation, RecordType.InvalidateLayout, RecordType.BeginMainThreadFrame,
  RecordType.ScrollLayer
];

/**
 * @unrestricted
 */
export class TracingFrameLayerTree {
  /**
   * @param {!SDK.SDKModel.Target} target
   * @param {!SDK.TracingModel.ObjectSnapshot} snapshot
   */
  constructor(target, snapshot) {
    this._target = target;
    this._snapshot = snapshot;
    /** @type {!Array|undefined} */
    this._paints;
  }

  /**
   * @return {!Promise}
   */
  async layerTreePromise() {
    const result = await this._snapshot.objectPromise();
    if (!result) {
      return null;
    }
    const viewport = result['device_viewport_size'];
    const tiles = result['active_tiles'];
    const rootLayer = result['active_tree']['root_layer'];
    const layers = result['active_tree']['layers'];
    const layerTree = new TracingLayerTree(this._target);
    layerTree.setViewportSize(viewport);
    layerTree.setTiles(tiles);

    await layerTree.setLayers(rootLayer, layers, this._paints || []);
    return layerTree;
  }

  /**
   * @return {!Array}
   */
  paints() {
    return this._paints || [];
  }

  /**
   * @param {!Array} paints
   */
  _setPaints(paints) {
    this._paints = paints;
  }
}

/**
 * @unrestricted
 */
export class TimelineFrame {
  /**
   * @param {number} startTime
   * @param {number} startTimeOffset
   */
  constructor(startTime, startTimeOffset) {
    this.startTime = startTime;
    this.startTimeOffset = startTimeOffset;
    this.endTime = this.startTime;
    this.duration = 0;
    this.timeByCategory = {};
    this.cpuTime = 0;
    this.idle = false;
    /** @type {?TracingFrameLayerTree} */
    this.layerTree = null;
    /** @type {!Array.} */
    this._paints = [];
    /** @type {number|undefined} */
    this._mainFrameId = undefined;
  }

  /**
   * @return {boolean}
   */
  hasWarnings() {
    return false;
  }

  /**
   * @param {number} endTime
   */
  _setEndTime(endTime) {
    this.endTime = endTime;
    this.duration = this.endTime - this.startTime;
  }

  /**
   * @param {?TracingFrameLayerTree} layerTree
   */
  _setLayerTree(layerTree) {
    this.layerTree = layerTree;
  }

  /**
   * @param {!Object} timeByCategory
   */
  _addTimeForCategories(timeByCategory) {
    for (const category in timeByCategory) {
      this._addTimeForCategory(category, timeByCategory[category]);
    }
  }

  /**
   * @param {string} category
   * @param {number} time
   */
  _addTimeForCategory(category, time) {
    this.timeByCategory[category] = (this.timeByCategory[category] || 0) + time;
    this.cpuTime += time;
  }
}

/**
 * @unrestricted
 */
export class LayerPaintEvent {
  /**
   * @param {!SDK.TracingModel.Event} event
   * @param {?SDK.SDKModel.Target} target
   */
  constructor(event, target) {
    this._event = event;
    this._target = target;
  }

  /**
   * @return {string}
   */
  layerId() {
    return this._event.args['data']['layerId'];
  }

  /**
   * @return {!SDK.TracingModel.Event}
   */
  event() {
    return this._event;
  }

  /**
   * @return {!Promise, serializedPicture: string}>}
   */
  picturePromise() {
    const picture = TimelineData.forEvent(this._event).picture;
    return picture.objectPromise().then(result => {
      if (!result) {
        return null;
      }
      const rect = result['params'] && result['params']['layer_rect'];
      const picture = result['skp64'];
      return rect && picture ? {rect: rect, serializedPicture: picture} : null;
    });
  }

  /**
   * @return !Promise, snapshot: !SDK.PaintProfiler.PaintProfilerSnapshot}>}
   */
  snapshotPromise() {
    const paintProfilerModel = this._target && this._target.model(SDK.PaintProfiler.PaintProfilerModel);
    return this.picturePromise().then(picture => {
      if (!picture || !paintProfilerModel) {
        return null;
      }
      return paintProfilerModel.loadSnapshot(picture.serializedPicture)
          .then(snapshot => snapshot ? {rect: picture.rect, snapshot: snapshot} : null);
    });
  }
}

/**
 * @unrestricted
 */
export class PendingFrame {
  /**
   * @param {number} triggerTime
   * @param {!Object.} timeByCategory
   */
  constructor(triggerTime, timeByCategory) {
    /** @type {!Object.} */
    this.timeByCategory = timeByCategory;
    /** @type {!Array.} */
    this.paints = [];
    /** @type {number|undefined} */
    this.mainFrameId = undefined;
    this.triggerTime = triggerTime;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy