Bug 952543 - Record add-on manager start up exceptions in Telemetry. r=mossop

This commit is contained in:
Irving Reid 2014-01-09 15:00:27 -05:00
parent 416b653381
commit 5b4f6a95a8
3 changed files with 283 additions and 243 deletions

View File

@ -470,132 +470,140 @@ var AddonManagerInternal = {
* them.
*/
startup: function AMI_startup() {
if (gStarted)
return;
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;
try {
oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
appChanged = Services.appinfo.version != oldAppVersion;
}
catch (e) { }
if (gStarted)
return;
let oldPlatformVersion = null;
try {
oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION);
}
catch (e) { }
this.recordTimestamp("AMI_startup_begin");
if (appChanged !== false) {
LOG("Application has been upgraded");
Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
Services.appinfo.version);
Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
Services.appinfo.platformVersion);
Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
(appChanged === undefined ? 0 : -1));
}
// clear this for xpcshell test restarts
for (let provider in this.telemetryDetails)
delete this.telemetryDetails[provider];
let appChanged = undefined;
let oldAppVersion = null;
try {
oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
appChanged = Services.appinfo.version != oldAppVersion;
}
catch (e) { }
let oldPlatformVersion = null;
try {
oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION);
}
catch (e) { }
if (appChanged !== false) {
LOG("Application has been upgraded");
Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
Services.appinfo.version);
Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
Services.appinfo.platformVersion);
Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
(appChanged === undefined ? 0 : -1));
}
#ifndef MOZ_COMPATIBILITY_NIGHTLY
PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
#endif
try {
gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false);
try {
gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false);
try {
gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false);
try {
gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false);
try {
let defaultBranch = Services.prefs.getDefaultBranch("");
gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
} catch(e) {}
try {
let defaultBranch = Services.prefs.getDefaultBranch("");
gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
} catch(e) {}
try {
gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false);
try {
gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false);
try {
gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false);
try {
gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false);
try {
gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false);
try {
gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false);
try {
gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false);
try {
gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
} catch (e) {}
Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false);
let defaultProvidersEnabled = true;
try {
defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED);
} catch (e) {}
let defaultProvidersEnabled = true;
try {
defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED);
} catch (e) {}
// Ensure all default providers have had a chance to register themselves
if (defaultProvidersEnabled) {
DEFAULT_PROVIDERS.forEach(function(url) {
try {
Components.utils.import(url, {});
}
catch (e) {
AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
ERROR("Exception loading default provider \"" + url + "\"", e);
}
});
}
// Load any providers registered in the category manager
let catman = Cc["@mozilla.org/categorymanager;1"].
getService(Ci.nsICategoryManager);
let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
// Ensure all default providers have had a chance to register themselves
if (defaultProvidersEnabled) {
DEFAULT_PROVIDERS.forEach(function(url) {
try {
Components.utils.import(url, {});
}
catch (e) {
ERROR("Exception loading default provider \"" + url + "\"", e);
AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
ERROR("Exception loading provider " + entry + " from category \"" +
url + "\"", e);
}
});
}
// Load any providers registered in the category manager
let catman = Cc["@mozilla.org/categorymanager;1"].
getService(Ci.nsICategoryManager);
let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
try {
Components.utils.import(url, {});
}
catch (e) {
ERROR("Exception loading provider " + entry + " from category \"" +
url + "\"", e);
// Register our shutdown handler with the AsyncShutdown manager
AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers",
this.shutdown.bind(this));
// Once we start calling providers we must allow all normal methods to work.
gStarted = true;
this.callProviders("startup", appChanged, oldAppVersion,
oldPlatformVersion);
// If this is a new profile just pretend that there were no changes
if (appChanged === undefined) {
for (let type in this.startupChanges)
delete this.startupChanges[type];
}
gStartupComplete = true;
this.recordTimestamp("AMI_startup_end");
}
// Register our shutdown handler with the AsyncShutdown manager
AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers",
this.shutdown.bind(this));
// Once we start calling providers we must allow all normal methods to work.
gStarted = true;
this.callProviders("startup", appChanged, oldAppVersion,
oldPlatformVersion);
// If this is a new profile just pretend that there were no changes
if (appChanged === undefined) {
for (let type in this.startupChanges)
delete this.startupChanges[type];
catch (e) {
ERROR("startup failed", e);
AddonManagerPrivate.recordException("AMI", "startup failed", e);
}
gStartupComplete = true;
this.recordTimestamp("AMI_startup_end");
},
/**
@ -1699,7 +1707,7 @@ var AddonManagerInternal = {
}
}
catch (e) {
// In the event that the weblistener throws during instatiation or when
// In the event that the weblistener throws during instantiation or when
// calling onWebInstallBlocked or onWebInstallRequested all of the
// installs should get cancelled.
WARN("Failure calling web installer", e);
@ -2216,6 +2224,26 @@ this.AddonManagerPrivate = {
this._simpleMeasures[name] = value;
},
recordException: function AMP_recordException(aModule, aContext, aException) {
let report = {
module: aModule,
context: aContext
};
if (typeof aException == "number") {
report.message = Components.Exception("", aException).name;
}
else {
report.message = aException.toString();
if (aException.fileName) {
report.file = aException.fileName;
report.line = aException.lineNumber;
}
}
this._simpleMeasures.exception = report;
},
getSimpleMeasures: function AMP_getSimpleMeasures() {
return this._simpleMeasures;
},

View File

@ -1963,153 +1963,165 @@ var XPIProvider = {
XPIProvider.installLocationsByName[location.name] = location;
}
let hasRegistry = ("nsIWindowsRegKey" in Ci);
try {
let hasRegistry = ("nsIWindowsRegKey" in Ci);
let enabledScopes = Prefs.getIntPref(PREF_EM_ENABLED_SCOPES,
AddonManager.SCOPE_ALL);
let enabledScopes = Prefs.getIntPref(PREF_EM_ENABLED_SCOPES,
AddonManager.SCOPE_ALL);
// These must be in order of priority for processFileChanges etc. to work
if (enabledScopes & AddonManager.SCOPE_SYSTEM) {
if (hasRegistry) {
addRegistryInstallLocation("winreg-app-global",
Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
AddonManager.SCOPE_SYSTEM);
}
addDirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD",
[Services.appinfo.ID],
AddonManager.SCOPE_SYSTEM, true);
addDirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, "XRESysSExtPD",
[Services.appinfo.ID],
AddonManager.SCOPE_SYSTEM, true);
}
if (enabledScopes & AddonManager.SCOPE_APPLICATION) {
addDirectoryInstallLocation(KEY_APP_GLOBAL, KEY_APPDIR,
[DIR_EXTENSIONS],
AddonManager.SCOPE_APPLICATION, true);
}
if (enabledScopes & AddonManager.SCOPE_USER) {
if (hasRegistry) {
addRegistryInstallLocation("winreg-app-user",
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
AddonManager.SCOPE_USER);
}
addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
[Services.appinfo.ID],
AddonManager.SCOPE_USER, true);
}
// The profile location is always enabled
addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
[DIR_EXTENSIONS],
AddonManager.SCOPE_PROFILE, false);
this.defaultSkin = Prefs.getDefaultCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
"classic/1.0");
this.currentSkin = Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
this.defaultSkin);
this.selectedSkin = this.currentSkin;
this.applyThemeChange();
this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
null);
this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
null);
this.enabledAddons = "";
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
aOldPlatformVersion);
// Changes to installed extensions may have changed which theme is selected
this.applyThemeChange();
// If the application has been upgraded and there are add-ons outside the
// application directory then we may need to synchronize compatibility
// information but only if the mismatch UI isn't disabled
if (aAppChanged && !this.allAppGlobal &&
Prefs.getBoolPref(PREF_EM_SHOW_MISMATCH_UI, true)) {
this.showUpgradeUI();
flushCaches = true;
}
else if (aAppChanged === undefined) {
// For new profiles we will never need to show the add-on selection UI
Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
}
if (flushCaches) {
flushStartupCache();
// UI displayed early in startup (like the compatibility UI) may have
// caused us to cache parts of the skin or locale in memory. These must
// be flushed to allow extension provided skins and locales to take full
// effect
Services.obs.notifyObservers(null, "chrome-flush-skin-caches", null);
Services.obs.notifyObservers(null, "chrome-flush-caches", null);
}
this.enabledAddons = Prefs.getCharPref(PREF_EM_ENABLED_ADDONS, "");
if ("nsICrashReporter" in Ci &&
Services.appinfo instanceof Ci.nsICrashReporter) {
// Annotate the crash report with relevant add-on information.
try {
Services.appinfo.annotateCrashReport("Theme", this.currentSkin);
} catch (e) { }
try {
Services.appinfo.annotateCrashReport("EMCheckCompatibility",
AddonManager.checkCompatibility);
} catch (e) { }
this.addAddonsToCrashReporter();
}
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
for (let id in this.bootstrappedAddons) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
let reason = BOOTSTRAP_REASONS.APP_STARTUP;
// Eventually set INSTALLED reason when a bootstrap addon
// is dropped in profile folder and automatically installed
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
.indexOf(id) !== -1)
reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
this.callBootstrapMethod(id, this.bootstrappedAddons[id].version,
this.bootstrappedAddons[id].type, file,
"startup", reason);
}
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
// Let these shutdown a little earlier when they still have access to most
// of XPCOM
Services.obs.addObserver({
observe: function shutdownObserver(aSubject, aTopic, aData) {
for (let id in XPIProvider.bootstrappedAddons) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor;
XPIProvider.callBootstrapMethod(id, XPIProvider.bootstrappedAddons[id].version,
XPIProvider.bootstrappedAddons[id].type, file, "shutdown",
BOOTSTRAP_REASONS.APP_SHUTDOWN);
// These must be in order of priority for processFileChanges etc. to work
if (enabledScopes & AddonManager.SCOPE_SYSTEM) {
if (hasRegistry) {
addRegistryInstallLocation("winreg-app-global",
Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
AddonManager.SCOPE_SYSTEM);
}
Services.obs.removeObserver(this, "quit-application-granted");
addDirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD",
[Services.appinfo.ID],
AddonManager.SCOPE_SYSTEM, true);
addDirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, "XRESysSExtPD",
[Services.appinfo.ID],
AddonManager.SCOPE_SYSTEM, true);
}
}, "quit-application-granted", false);
// Detect final-ui-startup for telemetry reporting
Services.obs.addObserver({
observe: function uiStartupObserver(aSubject, aTopic, aData) {
AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
XPIProvider.runPhase = XPI_AFTER_UI_STARTUP;
Services.obs.removeObserver(this, "final-ui-startup");
if (enabledScopes & AddonManager.SCOPE_APPLICATION) {
addDirectoryInstallLocation(KEY_APP_GLOBAL, KEY_APPDIR,
[DIR_EXTENSIONS],
AddonManager.SCOPE_APPLICATION, true);
}
}, "final-ui-startup", false);
AddonManagerPrivate.recordTimestamp("XPI_startup_end");
if (enabledScopes & AddonManager.SCOPE_USER) {
if (hasRegistry) {
addRegistryInstallLocation("winreg-app-user",
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
AddonManager.SCOPE_USER);
}
addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
[Services.appinfo.ID],
AddonManager.SCOPE_USER, true);
}
this.extensionsActive = true;
this.runPhase = XPI_BEFORE_UI_STARTUP;
// The profile location is always enabled
addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
[DIR_EXTENSIONS],
AddonManager.SCOPE_PROFILE, false);
this.defaultSkin = Prefs.getDefaultCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
"classic/1.0");
this.currentSkin = Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
this.defaultSkin);
this.selectedSkin = this.currentSkin;
this.applyThemeChange();
this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
null);
this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
null);
this.enabledAddons = "";
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
aOldPlatformVersion);
// Changes to installed extensions may have changed which theme is selected
this.applyThemeChange();
// If the application has been upgraded and there are add-ons outside the
// application directory then we may need to synchronize compatibility
// information but only if the mismatch UI isn't disabled
if (aAppChanged && !this.allAppGlobal &&
Prefs.getBoolPref(PREF_EM_SHOW_MISMATCH_UI, true)) {
this.showUpgradeUI();
flushCaches = true;
}
else if (aAppChanged === undefined) {
// For new profiles we will never need to show the add-on selection UI
Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
}
if (flushCaches) {
flushStartupCache();
// UI displayed early in startup (like the compatibility UI) may have
// caused us to cache parts of the skin or locale in memory. These must
// be flushed to allow extension provided skins and locales to take full
// effect
Services.obs.notifyObservers(null, "chrome-flush-skin-caches", null);
Services.obs.notifyObservers(null, "chrome-flush-caches", null);
}
this.enabledAddons = Prefs.getCharPref(PREF_EM_ENABLED_ADDONS, "");
if ("nsICrashReporter" in Ci &&
Services.appinfo instanceof Ci.nsICrashReporter) {
// Annotate the crash report with relevant add-on information.
try {
Services.appinfo.annotateCrashReport("Theme", this.currentSkin);
} catch (e) { }
try {
Services.appinfo.annotateCrashReport("EMCheckCompatibility",
AddonManager.checkCompatibility);
} catch (e) { }
this.addAddonsToCrashReporter();
}
try {
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
for (let id in this.bootstrappedAddons) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
let reason = BOOTSTRAP_REASONS.APP_STARTUP;
// Eventually set INSTALLED reason when a bootstrap addon
// is dropped in profile folder and automatically installed
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
.indexOf(id) !== -1)
reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
this.callBootstrapMethod(id, this.bootstrappedAddons[id].version,
this.bootstrappedAddons[id].type, file,
"startup", reason);
}
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
}
catch (e) {
ERROR("bootstrap startup failed", e);
AddonManagerPrivate.recordException("XPI-BOOTSTRAP", "startup failed", e);
}
// Let these shutdown a little earlier when they still have access to most
// of XPCOM
Services.obs.addObserver({
observe: function shutdownObserver(aSubject, aTopic, aData) {
for (let id in XPIProvider.bootstrappedAddons) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor;
XPIProvider.callBootstrapMethod(id, XPIProvider.bootstrappedAddons[id].version,
XPIProvider.bootstrappedAddons[id].type, file, "shutdown",
BOOTSTRAP_REASONS.APP_SHUTDOWN);
}
Services.obs.removeObserver(this, "quit-application-granted");
}
}, "quit-application-granted", false);
// Detect final-ui-startup for telemetry reporting
Services.obs.addObserver({
observe: function uiStartupObserver(aSubject, aTopic, aData) {
AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
XPIProvider.runPhase = XPI_AFTER_UI_STARTUP;
Services.obs.removeObserver(this, "final-ui-startup");
}
}, "final-ui-startup", false);
AddonManagerPrivate.recordTimestamp("XPI_startup_end");
this.extensionsActive = true;
this.runPhase = XPI_BEFORE_UI_STARTUP;
}
catch (e) {
ERROR("startup failed", e);
AddonManagerPrivate.recordException("XPI", "startup failed", e);
}
},
/**

View File

@ -16,7 +16,7 @@ const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
"registerProvider", "unregisterProvider",
"addStartupChange", "removeStartupChange",
"recordTimestamp", "recordSimpleMeasure",
"getSimpleMeasures", "simpleTimer",
"recordException", "getSimpleMeasures", "simpleTimer",
"setTelemetryDetails", "getTelemetryDetails"];
function test_functions() {