Bug 1553546, moving telemetry environment isWoW data and installYear data off-main-thread and implementing them on an idle task instead of on the main thread during startup r=mconley,chutten

Differential Revision: https://phabricator.services.mozilla.com/D43549

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Emma Malysz 2019-09-20 20:52:32 +00:00
Родитель 970d3a1d9b
Коммит a6deb8b6f9
5 изменённых файлов: 316 добавлений и 85 удалений

Просмотреть файл

@ -1101,6 +1101,8 @@ EnvironmentCache.prototype = {
async delayedInit() {
if (AppConstants.platform == "win") {
this._hddData = await Services.sysinfo.diskInfo;
this._processData = await Services.sysinfo.processInfo;
let osData = await Services.sysinfo.osInfo;
let oldEnv = null;
if (!this._initTask) {
// We've finished creating the initial env, so notify for the update
@ -1111,9 +1113,19 @@ EnvironmentCache.prototype = {
// instead of all the consumers.
oldEnv = this.currentEnvironment;
}
this._osData = this._getOSData();
// Augment the return values from the promises with cached values
this._osData = Object.assign(osData, this._osData);
this._currentEnvironment.system.os = this._getOSData();
this._currentEnvironment.system.hdd = this._getHDDData();
this._currentEnvironment.system.isWow64 = this._getProcessData().isWow64;
this._currentEnvironment.system.isWowARM64 = this._getProcessData().isWowARM64;
if (!this._initTask) {
this._onEnvironmentChange("hdd-info", oldEnv);
this._onEnvironmentChange("system-info", oldEnv);
}
}
},
@ -1785,12 +1797,17 @@ EnvironmentCache.prototype = {
return partnerData;
},
_cpuData: null,
/**
* Get the CPU information.
* @return Object containing the CPU information data.
*/
_getCpuData() {
let cpuData = {
_getCPUData() {
if (this._cpuData) {
return this._cpuData;
}
this._cpuData = {
count: getSysinfoProperty("cpucount", null),
cores: getSysinfoProperty("cpucores", null),
vendor: getSysinfoProperty("cpuvendor", null),
@ -1828,9 +1845,21 @@ EnvironmentCache.prototype = {
}
}
cpuData.extensions = availableExts;
this._cpuData.extensions = availableExts;
return cpuData;
return this._cpuData;
},
_processData: null,
/**
* Get the process information.
* @return Object containing the process information data.
*/
_getProcessData() {
if (this._processData) {
return this._processData;
}
return {};
},
/**
@ -1851,19 +1880,23 @@ EnvironmentCache.prototype = {
};
},
_osData: null,
/**
* Get the OS information.
* @return Object containing the OS data.
*/
_getOSData() {
let data = {
if (this._osData) {
return this._osData;
}
this._osData = {
name: forceToStringOrNull(getSysinfoProperty("name", null)),
version: forceToStringOrNull(getSysinfoProperty("version", null)),
locale: forceToStringOrNull(getSystemLocale()),
};
if (AppConstants.platform == "android") {
data.kernelVersion = forceToStringOrNull(
this._osData.kernelVersion = forceToStringOrNull(
getSysinfoProperty("kernel_version", null)
);
} else if (AppConstants.platform === "win") {
@ -1872,13 +1905,13 @@ EnvironmentCache.prototype = {
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
let versionInfo = getWindowsVersionInfo();
data.servicePackMajor = versionInfo.servicePackMajor;
data.servicePackMinor = versionInfo.servicePackMinor;
data.windowsBuildNumber = versionInfo.buildNumber;
this._osData.servicePackMajor = versionInfo.servicePackMajor;
this._osData.servicePackMinor = versionInfo.servicePackMinor;
this._osData.windowsBuildNumber = versionInfo.buildNumber;
// We only need the UBR if we're at or above Windows 10.
if (
typeof data.version === "string" &&
Services.vc.compare(data.version, "10") >= 0
typeof this._osData.version === "string" &&
Services.vc.compare(this._osData.version, "10") >= 0
) {
// Query the UBR key and only add it to the environment if it's available.
// |readRegKey| doesn't throw, but rather returns 'undefined' on error.
@ -1888,12 +1921,11 @@ EnvironmentCache.prototype = {
"UBR",
Ci.nsIWindowsRegKey.WOW64_64
);
data.windowsUBR = ubr !== undefined ? ubr : null;
this._osData.windowsUBR = ubr !== undefined ? ubr : null;
}
data.installYear = getSysinfoProperty("installYear", null);
}
return data;
return this._osData;
},
_hddData: null,
@ -2011,7 +2043,7 @@ EnvironmentCache.prototype = {
let data = {
memoryMB,
virtualMaxMB: virtualMB,
cpu: this._getCpuData(),
cpu: this._getCPUData(),
os: this._getOSData(),
hdd: this._getHDDData(),
gfx: this._getGFXData(),
@ -2019,8 +2051,7 @@ EnvironmentCache.prototype = {
};
if (AppConstants.platform === "win") {
data.isWow64 = getSysinfoProperty("isWow64", null);
data.isWowARM64 = getSysinfoProperty("isWowARM64", null);
data = { ...this._getProcessData(), ...data };
} else if (AppConstants.platform == "android") {
data.device = this._getDeviceData();
}

Просмотреть файл

@ -186,6 +186,14 @@ var SysInfo = {
return this._genuine.QueryInterface(Ci.nsISystemInfo).diskInfo;
},
get osInfo() {
return this._genuine.QueryInterface(Ci.nsISystemInfo).osInfo;
},
get processInfo() {
return this._genuine.QueryInterface(Ci.nsISystemInfo).processInfo;
},
QueryInterface: ChromeUtils.generateQI(["nsIPropertyBag2", "nsISystemInfo"]),
};
@ -595,7 +603,7 @@ function checkGfxAdapter(data) {
}
}
function checkSystemSection(data) {
function checkSystemSection(data, assertProcessData) {
const EXPECTED_FIELDS = [
"memoryMB",
"cpu",
@ -636,6 +644,7 @@ function checkSystemSection(data) {
}
if (gIsWindows) {
if (assertProcessData) {
Assert.equal(
typeof data.system.isWow64,
"boolean",
@ -646,6 +655,7 @@ function checkSystemSection(data) {
"boolean",
"isWowARM64 must be available on Windows and have the correct type."
);
}
Assert.ok(
"virtualMaxMB" in data.system,
"virtualMaxMB must be available."
@ -1011,13 +1021,17 @@ function checkExperimentsSection(data) {
}
function checkEnvironmentData(data, options = {}) {
const { isInitial = false, expectBrokenAddons = false } = options;
const {
isInitial = false,
expectBrokenAddons = false,
assertProcessData = false,
} = options;
checkBuildSection(data);
checkSettingsSection(data);
checkProfileSection(data);
checkPartnerSection(data, isInitial);
checkSystemSection(data);
checkSystemSection(data, assertProcessData);
checkAddonsSection(data, expectBrokenAddons);
}
@ -1109,7 +1123,7 @@ add_task(async function test_checkEnvironment() {
Services.obs.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
environmentData = TelemetryEnvironment.currentEnvironment;
checkEnvironmentData(environmentData);
checkEnvironmentData(environmentData, { assertProcessData: true });
});
add_task(async function test_prefWatchPolicies() {
@ -2485,6 +2499,40 @@ if (gIsWindows) {
checkString(data.system.hdd[k].type);
}
});
add_task(async function test_environmentProcessInfo() {
await TelemetryEnvironment.testCleanRestart().onInitialized();
let data = TelemetryEnvironment.currentEnvironment;
Assert.deepEqual(data.system.isWow64, null, "Should have no data yet.");
await TelemetryEnvironment.delayedInit();
data = TelemetryEnvironment.currentEnvironment;
Assert.equal(
typeof data.system.isWow64,
"boolean",
"isWow64 must be a boolean."
);
Assert.equal(
typeof data.system.isWowARM64,
"boolean",
"isWowARM64 must be a boolean."
);
});
add_task(async function test_environmentOSInfo() {
await TelemetryEnvironment.testCleanRestart().onInitialized();
let data = TelemetryEnvironment.currentEnvironment;
Assert.deepEqual(
data.system.os.installYear,
null,
"Should have no data yet."
);
await TelemetryEnvironment.delayedInit();
data = TelemetryEnvironment.currentEnvironment;
Assert.ok(
Number.isFinite(data.system.os.installYear),
"Install year must be a number."
);
});
}
add_task(async function test_environmentShutdown() {

Просмотреть файл

@ -102,6 +102,40 @@ static void SimpleParseKeyValuePairs(
#if defined(XP_WIN)
namespace {
nsresult CollectProcessInfo(ProcessInfo& info) {
// IsWow64Process2 is only available on Windows 10+, so we have to dynamically
// check for its existence.
typedef BOOL(WINAPI * LPFN_IWP2)(HANDLE, USHORT*, USHORT*);
LPFN_IWP2 iwp2 = reinterpret_cast<LPFN_IWP2>(
GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process2"));
BOOL isWow64 = false;
USHORT processMachine = IMAGE_FILE_MACHINE_UNKNOWN;
USHORT nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN;
BOOL gotWow64Value;
if (iwp2) {
gotWow64Value = iwp2(GetCurrentProcess(), &processMachine, &nativeMachine);
if (gotWow64Value) {
info.isWow64 = (processMachine != IMAGE_FILE_MACHINE_UNKNOWN);
}
} else {
gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64);
// The function only indicates a WOW64 environment if it's 32-bit x86
// running on x86-64, so emulate what IsWow64Process2 would have given.
if (gotWow64Value && info.isWow64) {
processMachine = IMAGE_FILE_MACHINE_I386;
nativeMachine = IMAGE_FILE_MACHINE_AMD64;
}
}
NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed");
if (gotWow64Value) {
// Set this always, even for the x86-on-arm64 case.
// Additional information if we're running x86-on-arm64
info.isWowARM64 = (processMachine == IMAGE_FILE_MACHINE_I386 &&
nativeMachine == IMAGE_FILE_MACHINE_ARM64);
}
return NS_OK;
}
static nsresult GetFolderDiskInfo(nsIFile* file, FolderDiskInfo& info) {
info.model.Truncate();
info.revision.Truncate();
@ -213,7 +247,7 @@ static nsresult CollectDiskInfo(nsIFile* greDir, nsIFile* winDir,
return GetFolderDiskInfo(profDir, info.profile);
}
nsresult GetInstallYear(uint32_t& aYear) {
static nsresult CollectOSInfo(OSInfo& info) {
HKEY hKey;
LONG status = RegOpenKeyExW(
HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0,
@ -245,7 +279,7 @@ nsresult GetInstallYear(uint32_t& aYear) {
return NS_ERROR_UNEXPECTED;
}
aYear = 1900UL + time.tm_year;
info.installYear = 1900UL + time.tm_year;
return NS_OK;
}
@ -784,53 +818,6 @@ nsresult nsSystemInfo::Init() {
return rv;
}
// IsWow64Process2 is only available on Windows 10+, so we have to dynamically
// check for its existence.
typedef BOOL(WINAPI * LPFN_IWP2)(HANDLE, USHORT*, USHORT*);
LPFN_IWP2 iwp2 = reinterpret_cast<LPFN_IWP2>(
GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process2"));
BOOL isWow64 = false;
USHORT processMachine = IMAGE_FILE_MACHINE_UNKNOWN;
USHORT nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN;
BOOL gotWow64Value;
if (iwp2) {
gotWow64Value = iwp2(GetCurrentProcess(), &processMachine, &nativeMachine);
if (gotWow64Value) {
isWow64 = (processMachine != IMAGE_FILE_MACHINE_UNKNOWN);
}
} else {
gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64);
// The function only indicates a WOW64 environment if it's 32-bit x86
// running on x86-64, so emulate what IsWow64Process2 would have given.
if (gotWow64Value && isWow64) {
processMachine = IMAGE_FILE_MACHINE_I386;
nativeMachine = IMAGE_FILE_MACHINE_AMD64;
}
}
NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed");
if (gotWow64Value) {
// Set this always, even for the x86-on-arm64 case.
rv = SetPropertyAsBool(NS_LITERAL_STRING("isWow64"), !!isWow64);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Additional information if we're running x86-on-arm64
bool isWowARM64 = (processMachine == IMAGE_FILE_MACHINE_I386 &&
nativeMachine == IMAGE_FILE_MACHINE_ARM64);
rv = SetPropertyAsBool(NS_LITERAL_STRING("isWowARM64"), !!isWowARM64);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
uint32_t installYear = 0;
if (NS_SUCCEEDED(GetInstallYear(installYear))) {
rv = SetPropertyAsUint32(NS_LITERAL_STRING("installYear"), installYear);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
# ifndef __MINGW32__
nsAutoString avInfo, antiSpyInfo, firewallInfo;
if (NS_SUCCEEDED(
@ -1096,8 +1083,28 @@ static bool GetJSObjForDiskInfo(JSContext* aCx, JS::Handle<JSObject*> aParent,
JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*jsInfo));
return JS_SetProperty(aCx, aParent, propName, val);
}
JSObject* GetJSObjForOSInfo(JSContext* aCx, const OSInfo& info) {
JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx));
JS::Rooted<JS::Value> valInstallYear(aCx, JS::Int32Value(info.installYear));
JS_SetProperty(aCx, jsInfo, "installYear", valInstallYear);
return jsInfo;
}
#endif
JSObject* GetJSObjForProcessInfo(JSContext* aCx, const ProcessInfo& info) {
JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx));
JS::Rooted<JS::Value> valisWow64(aCx, JS::BooleanValue(info.isWow64));
JS_SetProperty(aCx, jsInfo, "isWow64", valisWow64);
JS::Rooted<JS::Value> valisWowARM64(aCx, JS::BooleanValue(info.isWowARM64));
JS_SetProperty(aCx, jsInfo, "isWowARM64", valisWowARM64);
return jsInfo;
}
RefPtr<mozilla::LazyIdleThread> nsSystemInfo::GetHelperThread() {
if (!mLazyHelperThread) {
mLazyHelperThread =
@ -1106,6 +1113,63 @@ RefPtr<mozilla::LazyIdleThread> nsSystemInfo::GetHelperThread() {
return mLazyHelperThread;
}
NS_IMETHODIMP
nsSystemInfo::GetOsInfo(JSContext* aCx, Promise** aResult) {
NS_ENSURE_ARG_POINTER(aResult);
*aResult = nullptr;
if (!XRE_IsParentProcess()) {
return NS_ERROR_FAILURE;
}
#if defined(XP_WIN)
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
ErrorResult erv;
RefPtr<Promise> promise = Promise::Create(global, erv);
if (NS_WARN_IF(erv.Failed())) {
return erv.StealNSResult();
}
if (!mOSInfoPromise) {
RefPtr<mozilla::LazyIdleThread> lazyIOThread = GetHelperThread();
mOSInfoPromise = InvokeAsync(lazyIOThread, __func__, []() {
OSInfo info;
nsresult rv = CollectOSInfo(info);
if (NS_SUCCEEDED(rv)) {
return OSInfoPromise::CreateAndResolve(info, __func__);
}
return OSInfoPromise::CreateAndReject(rv, __func__);
});
};
// Chain the new promise to the extant mozpromise
RefPtr<Promise> capturedPromise = promise;
mOSInfoPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[capturedPromise](const OSInfo& info) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> val(
cx, JS::ObjectValue(*GetJSObjForOSInfo(cx, info)));
capturedPromise->MaybeResolve(val);
},
[capturedPromise](const nsresult rv) {
// Resolve with null when installYear is not available from the system
capturedPromise->MaybeResolve(JS::NullHandleValue);
});
promise.forget(aResult);
#endif
return NS_OK;
}
NS_IMETHODIMP
nsSystemInfo::GetDiskInfo(JSContext* aCx, Promise** aResult) {
NS_ENSURE_ARG_POINTER(aResult);
@ -1159,9 +1223,8 @@ nsSystemInfo::GetDiskInfo(JSContext* aCx, Promise** aResult) {
mDiskInfoPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[capturedPromise](const DiskInfo& info) {
RefPtr<nsIGlobalObject> global = capturedPromise->GetGlobalObject();
AutoJSAPI jsapi;
if (!global || !jsapi.Init(global)) {
if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
@ -1236,9 +1299,8 @@ nsSystemInfo::GetCountryCode(JSContext* aCx, Promise** aResult) {
mCountryCodePromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[capturedPromise](const nsString& countryCode) {
RefPtr<nsIGlobalObject> global = capturedPromise->GetGlobalObject();
AutoJSAPI jsapi;
if (!global || !jsapi.Init(global)) {
if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
@ -1258,3 +1320,61 @@ nsSystemInfo::GetCountryCode(JSContext* aCx, Promise** aResult) {
#endif
return NS_OK;
}
NS_IMETHODIMP
nsSystemInfo::GetProcessInfo(JSContext* aCx, Promise** aResult) {
NS_ENSURE_ARG_POINTER(aResult);
*aResult = nullptr;
if (!XRE_IsParentProcess()) {
return NS_ERROR_FAILURE;
}
#if defined(XP_WIN)
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
ErrorResult erv;
RefPtr<Promise> promise = Promise::Create(global, erv);
if (NS_WARN_IF(erv.Failed())) {
return erv.StealNSResult();
}
if (!mProcessInfoPromise) {
RefPtr<mozilla::LazyIdleThread> lazyIOThread = GetHelperThread();
mProcessInfoPromise = InvokeAsync(lazyIOThread, __func__, []() {
ProcessInfo info;
nsresult rv = CollectProcessInfo(info);
if (NS_SUCCEEDED(rv)) {
return ProcessInfoPromise::CreateAndResolve(info, __func__);
}
return ProcessInfoPromise::CreateAndReject(rv, __func__);
});
};
// Chain the new promise to the extant mozpromise
RefPtr<Promise> capturedPromise = promise;
mProcessInfoPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[capturedPromise](const ProcessInfo& info) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> val(
cx, JS::ObjectValue(*GetJSObjForProcessInfo(cx, info)));
capturedPromise->MaybeResolve(val);
},
[capturedPromise](const nsresult rv) {
// Resolve with null when installYear is not available from the system
capturedPromise->MaybeResolve(JS::NullHandleValue);
});
promise.forget(aResult);
#endif
return NS_OK;
}

Просмотреть файл

@ -28,12 +28,27 @@ struct DiskInfo {
FolderDiskInfo system;
};
struct OSInfo {
uint32_t installYear;
};
struct ProcessInfo {
bool isWow64;
bool isWowARM64;
};
typedef mozilla::MozPromise<DiskInfo, nsresult, /* IsExclusive */ false>
DiskInfoPromise;
typedef mozilla::MozPromise<nsAutoString, nsresult, /* IsExclusive */ false>
CountryCodePromise;
typedef mozilla::MozPromise<OSInfo, nsresult, /* IsExclusive */ false>
OSInfoPromise;
typedef mozilla::MozPromise<ProcessInfo, nsresult, /* IsExclusive */ false>
ProcessInfoPromise;
class nsSystemInfo final : public nsISystemInfo, public nsHashPropertyBag {
public:
NS_DECL_ISUPPORTS_INHERITED
@ -64,6 +79,8 @@ class nsSystemInfo final : public nsISystemInfo, public nsHashPropertyBag {
RefPtr<DiskInfoPromise> mDiskInfoPromise;
RefPtr<CountryCodePromise> mCountryCodePromise;
RefPtr<OSInfoPromise> mOSInfoPromise;
RefPtr<ProcessInfoPromise> mProcessInfoPromise;
RefPtr<mozilla::LazyIdleThread> mLazyHelperThread;
RefPtr<mozilla::LazyIdleThread> GetHelperThread();
};

Просмотреть файл

@ -22,4 +22,19 @@ interface nsISystemInfo : nsISupports
*/
[implicit_jscontext]
readonly attribute Promise countryCode;
/**
* Asynchronously gets OS info on the system's install year.
* Note: only implemented on Windows, will return null elsewhere.
*/
[implicit_jscontext]
readonly attribute Promise osInfo;
/**
* Asynchronously gets process info that indicates if the process is running
* under Wow64 and WowARM64.
* Note: only implemented on Windows, will return null elsewhere.
*/
[implicit_jscontext]
readonly attribute Promise processInfo;
};