mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 846921: record details about individual addons in XPI provider; r=unfocused,vladan
This commit is contained in:
parent
fcb4b3ad1b
commit
bc0bfb0886
@ -62,6 +62,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "idleService",
|
||||
"nsIIdleService");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
function generateUUID() {
|
||||
let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
|
||||
@ -158,9 +160,7 @@ TelemetryPing.prototype = {
|
||||
appTimestamps = o.TelemetryTimestamps.get();
|
||||
} catch (ex) {}
|
||||
try {
|
||||
let o = {};
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm", o);
|
||||
ret.addonManager = o.AddonManagerPrivate.getSimpleMeasures();
|
||||
ret.addonManager = AddonManagerPrivate.getSimpleMeasures();
|
||||
} catch (ex) {}
|
||||
|
||||
if (si.process) {
|
||||
@ -545,6 +545,7 @@ TelemetryPing.prototype = {
|
||||
chromeHangs: Telemetry.chromeHangs,
|
||||
lateWrites: Telemetry.lateWrites,
|
||||
addonHistograms: this.getAddonHistograms(),
|
||||
addonDetails: AddonManagerPrivate.getTelemetryDetails(),
|
||||
info: info
|
||||
};
|
||||
|
||||
@ -689,7 +690,7 @@ TelemetryPing.prototype = {
|
||||
let observer = {
|
||||
buffer: "",
|
||||
onStreamComplete: function(loader, context, status, length, result) {
|
||||
this.buffer = String.fromCharCode.apply(this, result);
|
||||
this.buffer = String.fromCharCode.apply(this, result);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -523,19 +523,48 @@ let Histogram = {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper function to render JS objects with white space between top level elements
|
||||
* so that they look better in the browser
|
||||
* @param aObject JavaScript object or array to render
|
||||
* @return String
|
||||
*/
|
||||
function RenderObject(aObject) {
|
||||
let output = "";
|
||||
if (Array.isArray(aObject)) {
|
||||
if (aObject.length == 0) {
|
||||
return "[]";
|
||||
}
|
||||
output = "[" + JSON.stringify(aObject[0]);
|
||||
for (let i = 1; i < aObject.length; i++) {
|
||||
output += ", " + JSON.stringify(aObject[i]);
|
||||
}
|
||||
return output + "]";
|
||||
}
|
||||
let keys = Object.keys(aObject);
|
||||
if (keys.length == 0) {
|
||||
return "{}";
|
||||
}
|
||||
output = "{\"" + keys[0] + "\":\u00A0" + JSON.stringify(aObject[keys[0]]);
|
||||
for (let i = 1; i < keys.length; i++) {
|
||||
output += ", \"" + keys[i] + "\":\u00A0" + JSON.stringify(aObject[keys[i]]);
|
||||
}
|
||||
return output + "}";
|
||||
};
|
||||
|
||||
let KeyValueTable = {
|
||||
|
||||
keysHeader: bundle.GetStringFromName("keysHeader"),
|
||||
|
||||
valuesHeader: bundle.GetStringFromName("valuesHeader"),
|
||||
|
||||
/**
|
||||
* Fill out a 2-column table with keys and values
|
||||
* Returns a 2-column table with keys and values
|
||||
* @param aMeasurements Each key in this JS object is rendered as a row in
|
||||
* the table with its corresponding value
|
||||
* @param aKeysLabel Column header for the keys column
|
||||
* @param aValuesLabel Column header for the values column
|
||||
*/
|
||||
render: function KeyValueTable_render(aTableID, aMeasurements) {
|
||||
let table = document.getElementById(aTableID);
|
||||
this.renderHeader(table);
|
||||
render: function KeyValueTable_render(aMeasurements, aKeysLabel, aValuesLabel) {
|
||||
let table = document.createElement("table");
|
||||
this.renderHeader(table, aKeysLabel, aValuesLabel);
|
||||
this.renderBody(table, aMeasurements);
|
||||
return table;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -543,15 +572,17 @@ let KeyValueTable = {
|
||||
* Tabs & newlines added to cells to make it easier to copy-paste.
|
||||
*
|
||||
* @param aTable Table element
|
||||
* @param aKeysLabel Column header for the keys column
|
||||
* @param aValuesLabel Column header for the values column
|
||||
*/
|
||||
renderHeader: function KeyValueTable_renderHeader(aTable) {
|
||||
renderHeader: function KeyValueTable_renderHeader(aTable, aKeysLabel, aValuesLabel) {
|
||||
let headerRow = document.createElement("tr");
|
||||
aTable.appendChild(headerRow);
|
||||
|
||||
let keysColumn = document.createElement("th");
|
||||
keysColumn.appendChild(document.createTextNode(this.keysHeader + "\t"));
|
||||
keysColumn.appendChild(document.createTextNode(aKeysLabel + "\t"));
|
||||
let valuesColumn = document.createElement("th");
|
||||
valuesColumn.appendChild(document.createTextNode(this.valuesHeader + "\n"));
|
||||
valuesColumn.appendChild(document.createTextNode(aValuesLabel + "\n"));
|
||||
|
||||
headerRow.appendChild(keysColumn);
|
||||
headerRow.appendChild(valuesColumn);
|
||||
@ -567,7 +598,7 @@ let KeyValueTable = {
|
||||
renderBody: function KeyValueTable_renderBody(aTable, aMeasurements) {
|
||||
for (let [key, value] of Iterator(aMeasurements)) {
|
||||
if (typeof value == "object") {
|
||||
value = JSON.stringify(value);
|
||||
value = RenderObject(value);
|
||||
}
|
||||
|
||||
let newRow = document.createElement("tr");
|
||||
@ -584,6 +615,28 @@ let KeyValueTable = {
|
||||
}
|
||||
};
|
||||
|
||||
let AddonDetails = {
|
||||
tableIDTitle: bundle.GetStringFromName("addonTableID"),
|
||||
tableDetailsTitle: bundle.GetStringFromName("addonTableDetails"),
|
||||
|
||||
/**
|
||||
* Render the addon details section as a series of headers followed by key/value tables
|
||||
* @param aSections Object containing the details sections to render
|
||||
*/
|
||||
render: function AddonDetails_render(aSections) {
|
||||
let addonSection = document.getElementById("addon-details");
|
||||
for (let provider in aSections) {
|
||||
let providerSection = document.createElement("h2");
|
||||
let titleText = bundle.formatStringFromName("addonProvider", [provider], 1);
|
||||
providerSection.appendChild(document.createTextNode(titleText));
|
||||
addonSection.appendChild(providerSection);
|
||||
addonSection.appendChild(
|
||||
KeyValueTable.render(aSections[provider],
|
||||
this.tableIDTitle, this.tableDetailsTitle));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for showing "No data collected" message for a section
|
||||
*
|
||||
@ -813,10 +866,15 @@ function sortStartupMilestones(aSimpleMeasurements) {
|
||||
function displayPingData() {
|
||||
let ping = TelemetryPing.getPayload();
|
||||
|
||||
let keysHeader = bundle.GetStringFromName("keysHeader");
|
||||
let valuesHeader = bundle.GetStringFromName("valuesHeader");
|
||||
|
||||
// Show simple measurements
|
||||
let simpleMeasurements = sortStartupMilestones(ping.simpleMeasurements);
|
||||
if (Object.keys(simpleMeasurements).length) {
|
||||
KeyValueTable.render("simple-measurements-table", simpleMeasurements);
|
||||
let simpleSection = document.getElementById("simple-measurements");
|
||||
simpleSection.appendChild(KeyValueTable.render(simpleMeasurements,
|
||||
keysHeader, valuesHeader));
|
||||
} else {
|
||||
showEmptySectionMessage("simple-measurements-section");
|
||||
}
|
||||
@ -825,10 +883,19 @@ function displayPingData() {
|
||||
|
||||
// Show basic system info gathered
|
||||
if (Object.keys(ping.info).length) {
|
||||
KeyValueTable.render("system-info-table", ping.info);
|
||||
let infoSection = document.getElementById("system-info");
|
||||
infoSection.appendChild(KeyValueTable.render(ping.info,
|
||||
keysHeader, valuesHeader));
|
||||
} else {
|
||||
showEmptySectionMessage("system-info-section");
|
||||
}
|
||||
|
||||
let addonDetails = ping.addonDetails;
|
||||
if (Object.keys(addonDetails).length) {
|
||||
AddonDetails.render(addonDetails);
|
||||
} else {
|
||||
showEmptySectionMessage("addon-details-section");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", onLoad, false);
|
||||
|
@ -75,8 +75,6 @@
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="simple-measurements" class="data hidden">
|
||||
<table id="simple-measurements-table">
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -101,8 +99,15 @@
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="system-info" class="data hidden">
|
||||
<table id="system-info-table">
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="addon-details-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.addonDetailsSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="addon-details" class="data hidden">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -28,6 +28,10 @@
|
||||
Simple Measurements
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.addonDetailsSection "
|
||||
Add-on Details
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.lateWritesSection "
|
||||
Late Writes
|
||||
">
|
||||
|
@ -45,3 +45,11 @@ enableTelemetry = Enable Telemetry
|
||||
keysHeader = Property
|
||||
|
||||
valuesHeader = Value
|
||||
|
||||
addonTableID = Add-on ID
|
||||
|
||||
addonTableDetails = Details
|
||||
|
||||
# Note to translators:
|
||||
# - The %1$S will be replaced with the name of an Add-on Provider (e.g. "XPI", "Plugin")
|
||||
addonProvider = %1$S Provider
|
||||
|
@ -396,6 +396,9 @@ var AddonManagerInternal = {
|
||||
providers: [],
|
||||
types: {},
|
||||
startupChanges: {},
|
||||
// Store telemetry details per addon provider
|
||||
telemetryDetails: {},
|
||||
|
||||
|
||||
// A read-only wrapper around the types dictionary
|
||||
typesProxy: Proxy.create({
|
||||
@ -458,6 +461,10 @@ var AddonManagerInternal = {
|
||||
|
||||
this.recordTimestamp("AMI_startup_begin");
|
||||
|
||||
// clear this for xpcshell test restarts
|
||||
for (let provider in this.telemetryDetails)
|
||||
delete this.telemetryDetails[provider];
|
||||
|
||||
let appChanged = undefined;
|
||||
|
||||
let oldAppVersion = null;
|
||||
@ -2192,12 +2199,20 @@ this.AddonManagerPrivate = {
|
||||
return this._simpleMeasures;
|
||||
},
|
||||
|
||||
getTelemetryDetails: function AMP_getTelemetryDetails() {
|
||||
return AddonManagerInternal.telemetryDetails;
|
||||
},
|
||||
|
||||
setTelemetryDetails: function AMP_setTelemetryDetails(aProvider, aDetails) {
|
||||
AddonManagerInternal.telemetryDetails[aProvider] = aDetails;
|
||||
},
|
||||
|
||||
// Start a timer, record a simple measure of the time interval when
|
||||
// timer.done() is called
|
||||
simpleTimer: function(aName) {
|
||||
let startTime = Date.now();
|
||||
return {
|
||||
done: () => AddonManagerPrivate.recordSimpleMeasure(aName, Date.now() - startTime)
|
||||
done: () => this.recordSimpleMeasure(aName, Date.now() - startTime)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -1335,28 +1335,33 @@ function recursiveRemove(aFile) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp of the most recently modified file in a directory,
|
||||
* Returns the timestamp and leaf file name of the most recently modified
|
||||
* entry in a directory,
|
||||
* or simply the file's own timestamp if it is not a directory.
|
||||
*
|
||||
* @param aFile
|
||||
* A non-null nsIFile object
|
||||
* @return Epoch time, as described above. 0 for an empty directory.
|
||||
* @return [File Name, Epoch time], as described above.
|
||||
*/
|
||||
function recursiveLastModifiedTime(aFile) {
|
||||
try {
|
||||
let modTime = aFile.lastModifiedTime;
|
||||
let fileName = aFile.leafName;
|
||||
if (aFile.isFile())
|
||||
return aFile.lastModifiedTime;
|
||||
return [fileName, modTime];
|
||||
|
||||
if (aFile.isDirectory()) {
|
||||
let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
|
||||
let entry, time;
|
||||
let maxTime = aFile.lastModifiedTime;
|
||||
let entry;
|
||||
while ((entry = entries.nextFile)) {
|
||||
time = recursiveLastModifiedTime(entry);
|
||||
maxTime = Math.max(time, maxTime);
|
||||
let [subName, subTime] = recursiveLastModifiedTime(entry);
|
||||
if (subTime > modTime) {
|
||||
modTime = subTime;
|
||||
fileName = subName;
|
||||
}
|
||||
}
|
||||
entries.close();
|
||||
return maxTime;
|
||||
return [fileName, modTime];
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@ -1364,7 +1369,7 @@ function recursiveLastModifiedTime(aFile) {
|
||||
}
|
||||
|
||||
// If the file is something else, just ignore it.
|
||||
return 0;
|
||||
return ["", 0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1543,10 +1548,22 @@ var XPIProvider = {
|
||||
enabledAddons: null,
|
||||
// An array of add-on IDs of add-ons that were inactive during startup
|
||||
inactiveAddonIDs: [],
|
||||
// Count of unpacked add-ons
|
||||
unpackedAddons: 0,
|
||||
// Keep track of startup phases for telemetry
|
||||
runPhase: XPI_STARTING,
|
||||
// Keep track of the newest file in each add-on, in case we want to
|
||||
// report it to telemetry.
|
||||
_mostRecentlyModifiedFile: {},
|
||||
// Per-addon telemetry information
|
||||
_telemetryDetails: {},
|
||||
|
||||
/*
|
||||
* Set a value in the telemetry hash for a given ID
|
||||
*/
|
||||
setTelemetry: function XPI_setTelemetry(aId, aName, aValue) {
|
||||
if (!this._telemetryDetails[aId])
|
||||
this._telemetryDetails[aId] = {};
|
||||
this._telemetryDetails[aId][aName] = aValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds or updates a URI mapping for an Addon.id.
|
||||
@ -1689,6 +1706,11 @@ var XPIProvider = {
|
||||
this.installLocationsByName = {};
|
||||
// Hook for tests to detect when saving database at shutdown time fails
|
||||
this._shutdownError = null;
|
||||
// Clear this at startup for xpcshell test restarts
|
||||
this._telemetryDetails = {};
|
||||
// Register our details structure with AddonManager
|
||||
AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);
|
||||
|
||||
|
||||
AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
|
||||
|
||||
@ -2033,18 +2055,22 @@ var XPIProvider = {
|
||||
let addonStates = {};
|
||||
aLocation.addonLocations.forEach(function(file) {
|
||||
let id = aLocation.getIDForLocation(file);
|
||||
let unpacked = 0;
|
||||
let [modFile, modTime] = recursiveLastModifiedTime(file);
|
||||
addonStates[id] = {
|
||||
descriptor: file.persistentDescriptor,
|
||||
mtime: recursiveLastModifiedTime(file)
|
||||
mtime: modTime
|
||||
};
|
||||
try {
|
||||
// get the install.rdf update time, if any
|
||||
file.append(FILE_INSTALL_MANIFEST);
|
||||
let rdfTime = file.lastModifiedTime;
|
||||
addonStates[id].rdfTime = rdfTime;
|
||||
this.unpackedAddons += 1;
|
||||
unpacked = 1;
|
||||
}
|
||||
catch (e) { }
|
||||
this._mostRecentlyModifiedFile[id] = modFile;
|
||||
this.setTelemetry(id, "unpacked", unpacked);
|
||||
}, this);
|
||||
|
||||
return addonStates;
|
||||
@ -2061,7 +2087,6 @@ var XPIProvider = {
|
||||
*/
|
||||
getInstallLocationStates: function XPI_getInstallLocationStates() {
|
||||
let states = [];
|
||||
this.unpackedAddons = 0;
|
||||
this.installLocations.forEach(function(aLocation) {
|
||||
let addons = aLocation.addonLocations;
|
||||
if (addons.length == 0)
|
||||
@ -3006,11 +3031,6 @@ var XPIProvider = {
|
||||
let changed = false;
|
||||
let knownLocations = XPIDatabase.getInstallLocations();
|
||||
|
||||
// Gather stats for addon telemetry
|
||||
let modifiedUnpacked = 0;
|
||||
let modifiedExManifest = 0;
|
||||
let modifiedXPI = 0;
|
||||
|
||||
// The install locations are iterated in reverse order of priority so when
|
||||
// there are multiple add-ons installed with the same ID the one that
|
||||
// should be visible is the first one encountered.
|
||||
@ -3045,15 +3065,22 @@ var XPIProvider = {
|
||||
if (aOldAddon.visible && !aOldAddon.active)
|
||||
XPIProvider.inactiveAddonIDs.push(aOldAddon.id);
|
||||
|
||||
// Check if the add-on is unpacked, and has had other files changed
|
||||
// on disk without the install.rdf manifest being changed
|
||||
if ((addonState.rdfTime) && (aOldAddon.updateDate != addonState.mtime)) {
|
||||
modifiedUnpacked += 1;
|
||||
if (aOldAddon.updateDate >= addonState.rdfTime)
|
||||
modifiedExManifest += 1;
|
||||
}
|
||||
else if (aOldAddon.updateDate != addonState.mtime) {
|
||||
modifiedXPI += 1;
|
||||
// Check if the add-on has been changed outside the XPI provider
|
||||
if (aOldAddon.updateDate != addonState.mtime) {
|
||||
// Is the add-on unpacked?
|
||||
if (addonState.rdfTime) {
|
||||
// Was the addon manifest "install.rdf" modified, or some other file?
|
||||
if (addonState.rdfTime > aOldAddon.updateDate) {
|
||||
this.setTelemetry(aOldAddon.id, "modifiedInstallRDF", 1);
|
||||
}
|
||||
else {
|
||||
this.setTelemetry(aOldAddon.id, "modifiedFile",
|
||||
this._mostRecentlyModifiedFile[aOldAddon.id]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.setTelemetry(aOldAddon.id, "modifiedXPI", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// The add-on has changed if the modification time has changed, or
|
||||
@ -3107,12 +3134,6 @@ var XPIProvider = {
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Tell Telemetry what we found
|
||||
AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked);
|
||||
if (modifiedUnpacked > 0)
|
||||
AddonManagerPrivate.recordSimpleMeasure("modifiedExceptInstallRDF", modifiedExManifest);
|
||||
AddonManagerPrivate.recordSimpleMeasure("modifiedXPI", modifiedXPI);
|
||||
|
||||
// Cache the new install location states
|
||||
let cache = JSON.stringify(this.getInstallLocationStates());
|
||||
Services.prefs.setCharPref(PREF_INSTALL_CACHE, cache);
|
||||
@ -3263,7 +3284,6 @@ var XPIProvider = {
|
||||
ERROR("Failed to process extension changes at startup", e);
|
||||
}
|
||||
}
|
||||
AddonManagerPrivate.recordSimpleMeasure("installedUnpacked", this.unpackedAddons);
|
||||
|
||||
if (aAppChanged) {
|
||||
// When upgrading the app and using a custom skin make sure it is still
|
||||
@ -3973,6 +3993,7 @@ var XPIProvider = {
|
||||
if (Services.appinfo.inSafeMode)
|
||||
return;
|
||||
|
||||
let timeStart = new Date();
|
||||
if (aMethod == "startup") {
|
||||
LOG("Registering manifest for " + aFile.path);
|
||||
Components.manager.addBootstrappedManifestLocation(aFile);
|
||||
@ -4019,6 +4040,7 @@ var XPIProvider = {
|
||||
LOG("Removing manifest for " + aFile.path);
|
||||
Components.manager.removeBootstrappedManifestLocation(aFile);
|
||||
}
|
||||
this.setTelemetry(aId, aMethod + "_MS", new Date() - timeStart);
|
||||
}
|
||||
},
|
||||
|
||||
@ -5331,7 +5353,8 @@ AddonInstall.prototype = {
|
||||
// Update the metadata in the database
|
||||
this.addon._sourceBundle = file;
|
||||
this.addon._installLocation = this.installLocation;
|
||||
this.addon.updateDate = recursiveLastModifiedTime(file); // XXX sync recursive scan
|
||||
let [mFile, mTime] = recursiveLastModifiedTime(file);
|
||||
this.addon.updateDate = mTime;
|
||||
this.addon.visible = true;
|
||||
if (isUpgrade) {
|
||||
this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
|
||||
|
@ -16,7 +16,8 @@ const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
|
||||
"registerProvider", "unregisterProvider",
|
||||
"addStartupChange", "removeStartupChange",
|
||||
"recordTimestamp", "recordSimpleMeasure",
|
||||
"getSimpleMeasures", "simpleTimer"];
|
||||
"getSimpleMeasures", "simpleTimer",
|
||||
"setTelemetryDetails", "getTelemetryDetails"];
|
||||
|
||||
function test_functions() {
|
||||
for (let prop in AddonManager) {
|
||||
|
Loading…
Reference in New Issue
Block a user