Source: profiler.js

/**
 * A basic profiling tool that can be used to measure time spent in sections of the engine.
 * @class Profiler
 * @see gdjs.RuntimeGame
 * @memberof gdjs
 */
gdjs.Profiler = function() {
  this._framesMeasures = []; // All the measures for the last frames
  this._currentFrameIndex = 0;
  this._currentFrameMeasure = null; // The measures being done
  this._currentSection = null; // The section being measured

  this._maxFramesCount = 600;
  this._framesCount = 0; // The number of frames that have been measured
  while (this._framesMeasures.length < this._maxFramesCount) {
    this._framesMeasures.push({
      parent: null,
      time: 0,
      subsections: {},
    });
  }

  this._getTimeNow =
    window.performance && typeof window.performance.now === 'function'
      ? window.performance.now.bind(window.performance)
      : Date.now;
};

gdjs.Profiler.prototype.beginFrame = function() {
  this._currentFrameMeasure = {
    parent: null,
    time: 0,
    lastStartTime: this._getTimeNow(),
    subsections: {},
  };
  this._currentSection = this._currentFrameMeasure;
};

gdjs.Profiler.prototype.begin = function(sectionName) {
  // Push the new section
  var subsections = this._currentSection.subsections;
  var subsection = (subsections[sectionName] = subsections[sectionName] || {
    parent: this._currentSection,
    time: 0,
    lastStartTime: 0,
    subsections: {},
  });
  this._currentSection = subsection;

  // Start the timer
  this._currentSection.lastStartTime = this._getTimeNow();
};

gdjs.Profiler.prototype.end = function(sectionName) {
  // Stop the timer
  var sectionTime = this._getTimeNow() - this._currentSection.lastStartTime;
  this._currentSection.time = (this._currentSection.time || 0) + sectionTime;

  // Pop the section
  this._currentSection = this._currentSection.parent;
};

gdjs.Profiler.prototype.endFrame = function() {
  if (this._currentSection.parent !== null) {
    throw new Error(
      'Mismatch in profiler, endFrame should be called on root section'
    );
  }

  this.end();

  this._framesCount++;
  if (this._framesCount > this._maxFramesCount)
    this._framesCount = this._maxFramesCount;
  this._framesMeasures[this._currentFrameIndex] = this._currentFrameMeasure;
  this._currentFrameIndex++;
  if (this._currentFrameIndex >= this._maxFramesCount)
    this._currentFrameIndex = 0;
};

gdjs.Profiler._addAverageSectionTimes = function(
  section,
  destinationSection,
  totalCount,
  i
) {
  destinationSection.time =
    (destinationSection.time || 0) + section.time / totalCount;
  for (var sectionName in section.subsections) {
    if (section.subsections.hasOwnProperty(sectionName)) {
      var destinationSubsections = destinationSection.subsections;
      var destinationSubsection = (destinationSubsections[
        sectionName
      ] = destinationSubsections[sectionName] || {
        parent: destinationSection,
        time: 0,
        subsections: {},
      });

      gdjs.Profiler._addAverageSectionTimes(
        section.subsections[sectionName],
        destinationSubsection,
        totalCount,
        i
      );
    }
  }
};

/**
 * Return the measures for all the section of the game during the frames
 * captured.
 */
gdjs.Profiler.prototype.getFramesAverageMeasures = function() {
  var framesAverageMeasures = {
    parent: null,
    time: 0,
    subsections: {},
  };

  for (var i = 0; i < this._framesCount; ++i) {
    gdjs.Profiler._addAverageSectionTimes(
      this._framesMeasures[i],
      framesAverageMeasures,
      this._framesCount,
      i
    );
  }

  return framesAverageMeasures;
};

/**
 * Get stats measured during the frames captured.
 */
gdjs.Profiler.prototype.getStats = function() {
  return {
    framesCount: this._framesCount,
  };
};

/**
 * Convert measures for a section into texts.
 * Useful for ingame profiling.
 *
 * @param {string} sectionName The name of the section
 * @param {s} profilerSection The section measures
 * @param {*} outputs The array where to push the results
 */
gdjs.Profiler.getProfilerSectionTexts = function(
  sectionName,
  profilerSection,
  outputs
) {
  var percent =
    profilerSection.parent && profilerSection.parent.time !== 0
      ? ((profilerSection.time / profilerSection.parent.time) * 100).toFixed(1)
      : '100%';
  var time = profilerSection.time.toFixed(2);
  outputs.push(sectionName + ': ' + time + 'ms (' + percent + ')');
  var subsectionsOutputs = [];

  for (var subsectionName in profilerSection.subsections) {
    if (profilerSection.subsections.hasOwnProperty(subsectionName)) {
      gdjs.Profiler.getProfilerSectionTexts(
        subsectionName,
        profilerSection.subsections[subsectionName],
        subsectionsOutputs
      );
    }
  }
  outputs.push.apply(outputs, subsectionsOutputs);
};