216 lines
6.2 KiB
JavaScript
216 lines
6.2 KiB
JavaScript
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = ["PerformanceStats"];
|
|
|
|
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
|
|
|
/**
|
|
* API for querying and examining performance data.
|
|
*
|
|
* The data exposed by this API is computed internally by the JavaScript VM.
|
|
* See `PerformanceData` for the detail of the information provided by this
|
|
* API.
|
|
*/
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
|
|
|
let performanceStatsService =
|
|
Cc["@mozilla.org/toolkit/performance-stats-service;1"].
|
|
getService(Ci.nsIPerformanceStatsService);
|
|
|
|
|
|
const PROPERTIES_NUMBERED = ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"];
|
|
const PROPERTIES_META_IMMUTABLE = ["name", "addonId", "isSystem"];
|
|
const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title"];
|
|
const PROPERTIES_FLAT = [...PROPERTIES_NUMBERED, ...PROPERTIES_META];
|
|
|
|
/**
|
|
* Information on a single component.
|
|
*
|
|
* This offers the following fields:
|
|
*
|
|
* @field {string} name The name of the component:
|
|
* - for the process itself, "<process>";
|
|
* - for platform code, "<platform>";
|
|
* - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar");
|
|
* - for a webpage, the url of the page.
|
|
*
|
|
* @field {string} addonId The identifier of the addon (e.g. "myaddon@foo.bar").
|
|
*
|
|
* @field {string|null} title The title of the webpage to which this code
|
|
* belongs. Note that this is the title of the entire webpage (i.e. the tab),
|
|
* even if the code is executed in an iframe. Also note that this title may
|
|
* change over time.
|
|
*
|
|
* @field {number} windowId The outer window ID of the top-level nsIDOMWindow
|
|
* to which this code belongs. May be 0 if the code doesn't belong to any
|
|
* nsIDOMWindow.
|
|
*
|
|
* @field {boolean} isSystem `true` if the component is a system component (i.e.
|
|
* an add-on or platform-code), `false` otherwise (i.e. a webpage).
|
|
*
|
|
* @field {number} totalUserTime The total amount of time spent executing code.
|
|
*
|
|
* @field {number} totalSystemTime The total amount of time spent executing
|
|
* system calls.
|
|
*
|
|
* @field {number} totalCPOWTime The total amount of time spent waiting for
|
|
* blocking cross-process communications
|
|
*
|
|
* @field {number} ticks The number of times the JavaScript VM entered the code
|
|
* of this component to execute it.
|
|
*
|
|
* @field {Array<number>} durations An array containing at each position `i`
|
|
* the number of times execution of this component has lasted at least `2^i`
|
|
* milliseconds.
|
|
*
|
|
* All numeric values are non-negative and can only increase.
|
|
*/
|
|
function PerformanceData(xpcom) {
|
|
for (let k of PROPERTIES_FLAT) {
|
|
this[k] = xpcom[k];
|
|
}
|
|
this.durations = xpcom.getDurations();
|
|
}
|
|
PerformanceData.prototype = {
|
|
/**
|
|
* Compare two instances of `PerformanceData`
|
|
*
|
|
* @return `true` if `this` and `to` have equal values in all fields.
|
|
*/
|
|
equals: function(to) {
|
|
if (!(to instanceof PerformanceData)) {
|
|
throw new TypeError();
|
|
}
|
|
for (let k of PROPERTIES_FLAT) {
|
|
if (this[k] != to[k]) {
|
|
return false;
|
|
}
|
|
}
|
|
for (let i = 0; i < this.durations.length; ++i) {
|
|
if (to.durations[i] != this.durations[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Compute the delta between two instances of `PerformanceData`.
|
|
*
|
|
* @param {PerformanceData|null} to. If `null`, assumed an instance of
|
|
* `PerformanceData` in which all numeric values are 0.
|
|
*
|
|
* @return {PerformanceDiff} The performance usage between `to` and `this`.
|
|
*/
|
|
substract: function(to = null) {
|
|
return new PerformanceDiff(this, to);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The delta between two instances of `PerformanceData`.
|
|
*
|
|
* Used to monitor resource usage between two timestamps.
|
|
*
|
|
* @field {number} longestDuration An indication of the longest
|
|
* execution duration between two timestamps:
|
|
* - -1 == less than 1ms
|
|
* - 0 == [1, 2[ ms
|
|
* - 1 == [2, 4[ ms
|
|
* - 3 == [4, 8[ ms
|
|
* - 4 == [8, 16[ ms
|
|
* - ...
|
|
* - 7 == [128, ...] ms
|
|
*/
|
|
function PerformanceDiff(current, old = null) {
|
|
for (let k of PROPERTIES_META) {
|
|
this[k] = current[k];
|
|
}
|
|
|
|
if (old) {
|
|
if (!(old instanceof PerformanceData)) {
|
|
throw new TypeError();
|
|
}
|
|
if (current.durations.length != old.durations.length) {
|
|
throw new TypeError("Internal error: mismatched length for `durations`.");
|
|
}
|
|
|
|
this.durations = [];
|
|
|
|
this.longestDuration = -1;
|
|
|
|
for (let i = 0; i < current.durations.length; ++i) {
|
|
let delta = current.durations[i] - old.durations[i];
|
|
this.durations[i] = delta;
|
|
if (delta > 0) {
|
|
this.longestDuration = i;
|
|
}
|
|
}
|
|
for (let k of PROPERTIES_NUMBERED) {
|
|
this[k] = current[k] - old[k];
|
|
}
|
|
} else {
|
|
this.durations = current.durations.slice(0);
|
|
|
|
for (let k of PROPERTIES_NUMBERED) {
|
|
this[k] = current[k];
|
|
}
|
|
|
|
this.longestDuration = -1;
|
|
for (let i = this.durations.length - 1; i >= 0; --i) {
|
|
if (this.durations[i] > 0) {
|
|
this.longestDuration = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A snapshot of the performance usage of the process.
|
|
*/
|
|
function Snapshot(xpcom) {
|
|
this.componentsData = [];
|
|
let enumeration = xpcom.getComponentsData().enumerate();
|
|
while (enumeration.hasMoreElements()) {
|
|
let stat = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
|
|
this.componentsData.push(new PerformanceData(stat));
|
|
}
|
|
this.processData = new PerformanceData(xpcom.getProcessData());
|
|
}
|
|
|
|
|
|
this.PerformanceStats = {
|
|
/**
|
|
* Activate monitoring.
|
|
*/
|
|
init() {
|
|
//
|
|
// The implementation actually does nothing, as monitoring is
|
|
// initiated when loading the module.
|
|
//
|
|
// This function is actually provided as a gentle way to ensure
|
|
// that client code that imports `PerformanceStats` lazily
|
|
// does not forget to force the import, hence triggering
|
|
// actual load of the module.
|
|
//
|
|
},
|
|
|
|
/**
|
|
* Get a snapshot of the performance usage of the current process.
|
|
*
|
|
* @type {Snapshot}
|
|
*/
|
|
getSnapshot() {
|
|
return new Snapshot(performanceStatsService.getSnapshot());
|
|
},
|
|
};
|
|
|
|
performanceStatsService.isStopwatchActive = true;
|