add sqm id for windows (#195377)

* add sqm id for windows

* Update src/vs/platform/windows/electron-main/windowsMainService.ts

* Update src/vs/platform/sharedProcess/node/sharedProcess.ts

* react on review comments

* The reg entry is called MachineId not MachineGuid

* fix compile error

* no need for \\ prefix in reg path

* Wait for 1s max (as to not block the startup) to read the SQM value

---------

Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
This commit is contained in:
Isidor Nikolic
2023-10-12 13:47:40 -07:00
committed by GitHub
co-authored by Benjamin Pasero
parent 8de0cf79d2
commit 9d32835bd7
19 changed files with 91 additions and 28 deletions
+19
View File
@@ -7,6 +7,7 @@ import { networkInterfaces } from 'os';
import { TernarySearchTree } from 'vs/base/common/ternarySearchTree';
import * as uuid from 'vs/base/common/uuid';
import { getMac } from 'vs/base/node/macAddress';
import { isWindows } from 'vs/base/common/platform';
// http://www.techrepublic.com/blog/data-center/mac-address-scorecard-for-common-virtual-machine-platforms/
// VMware ESX 3, Server, Workstation, Player 00-50-56, 00-0C-29, 00-05-69
@@ -99,3 +100,21 @@ async function getMacMachineId(errorLogger: (error: any) => void): Promise<strin
return undefined;
}
}
const SQM_KEY: string = 'Software\\Microsoft\\SQMClient';
export async function getSqmMachineId(errorLogger: (error: any) => void): Promise<string> {
if (isWindows) {
const Registry = await import('@vscode/windows-registry');
try {
// Wait for 1s max (as to not block the startup) to read the SQM value
return await Promise.race([
Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', SQM_KEY, 'MachineId') || '',
new Promise<string>(resolve => setTimeout(() => resolve(''), 1000))
]);
} catch (err) {
errorLogger(err);
return '';
}
}
return '';
}
+8 -1
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { getMachineId } from 'vs/base/node/id';
import { getMachineId, getSqmMachineId } from 'vs/base/node/id';
import { getMac } from 'vs/base/node/macAddress';
import { flakySuite } from 'vs/base/test/node/testUtils';
@@ -17,6 +17,13 @@ flakySuite('ID', () => {
assert.strictEqual(errors.length, 0);
});
test('getSqmId', async function () {
const errors = [];
const id = await getSqmMachineId(err => errors.push(err));
assert.ok(typeof id === 'string');
assert.strictEqual(errors.length, 0);
});
test('getMac', async () => {
const macAddress = getMac();
assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`);
+12 -9
View File
@@ -105,7 +105,7 @@ import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/e
import { UserDataProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataProfilesHandler';
import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc';
import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
import { resolveMachineId } from 'vs/platform/telemetry/electron-main/telemetryUtils';
import { resolveMachineId, resolveSqmId } from 'vs/platform/telemetry/electron-main/telemetryUtils';
import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService';
import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc';
import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService';
@@ -596,14 +596,17 @@ export class CodeApplication extends Disposable {
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
const machineId = await resolveMachineId(this.stateService, this.logService);
const [machineId, sqmId] = await Promise.all([
resolveMachineId(this.stateService, this.logService),
resolveSqmId(this.stateService, this.logService)
]);
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Shared process
const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId);
const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId, sqmId);
// Services
const appInstantiationService = await this.initServices(machineId, sharedProcessReady);
const appInstantiationService = await this.initServices(machineId, sqmId, sharedProcessReady);
// Auth Handler
this._register(appInstantiationService.createInstance(ProxyAuthHandler));
@@ -956,8 +959,8 @@ export class CodeApplication extends Disposable {
return false;
}
private setupSharedProcess(machineId: string): { sharedProcessReady: Promise<MessagePortClient>; sharedProcessClient: Promise<MessagePortClient> } {
const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId));
private setupSharedProcess(machineId: string, sqmId: string): { sharedProcessReady: Promise<MessagePortClient>; sharedProcessClient: Promise<MessagePortClient> } {
const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, sqmId));
const sharedProcessClient = (async () => {
this.logService.trace('Main->SharedProcess#connect');
@@ -978,7 +981,7 @@ export class CodeApplication extends Disposable {
return { sharedProcessReady, sharedProcessClient };
}
private async initServices(machineId: string, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
private async initServices(machineId: string, sqmId: string, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
const services = new ServiceCollection();
// Update
@@ -1001,7 +1004,7 @@ export class CodeApplication extends Disposable {
}
// Windows
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv], false));
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, sqmId, this.userEnv], false));
services.set(IAuxiliaryWindowsMainService, new SyncDescriptor(AuxiliaryWindowsMainService, undefined, false));
// Dialogs
@@ -1081,7 +1084,7 @@ export class CodeApplication extends Disposable {
const isInternal = isInternalTelemetry(this.productService, this.configurationService);
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
const appender = new TelemetryAppenderClient(channel);
const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal);
const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, sqmId, isInternal);
const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService);
const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true };
+3 -2
View File
@@ -57,7 +57,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfilesReadonlyService } from 'vs/platform/userDataProfile/node/userDataProfile';
import { resolveMachineId } from 'vs/platform/telemetry/node/telemetryUtils';
import { resolveMachineId, resolveSqmId } from 'vs/platform/telemetry/node/telemetryUtils';
import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService';
import { LogService } from 'vs/platform/log/common/logService';
import { LoggerService } from 'vs/platform/log/node/loggerService';
@@ -184,6 +184,7 @@ class CliMain extends Disposable {
logService.error(error);
}
}
const sqmId = await resolveSqmId(stateService, logService);
// Initialize user data profiles after initializing the state
userDataProfilesService.init();
@@ -219,7 +220,7 @@ class CliMain extends Disposable {
const config: ITelemetryServiceConfig = {
appenders,
sendErrorTelemetry: false,
commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, machineId, isInternal),
commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, machineId, sqmId, isInternal),
piiPaths: getPiiPathsFromEnvironment(environmentService)
};
@@ -303,7 +303,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter {
telemetryService = new TelemetryService({
appenders,
commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, internalTelemetry),
commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, this.configuration.sqmId, internalTelemetry),
sendErrorTelemetry: true,
piiPaths: getPiiPathsFromEnvironment(environmentService),
}, configurationService, productService);
@@ -29,6 +29,7 @@ export class SharedProcess extends Disposable {
constructor(
private readonly machineId: string,
private readonly sqmId: string,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@@ -172,6 +173,7 @@ export class SharedProcess extends Disposable {
private createSharedProcessConfiguration(): ISharedProcessConfiguration {
return {
machineId: this.machineId,
sqmId: this.sqmId,
codeCachePath: this.environmentMainService.codeCachePath,
profiles: {
home: this.userDataProfilesService.profilesHome,
@@ -13,6 +13,8 @@ import { UriComponents, UriDto } from 'vs/base/common/uri';
export interface ISharedProcessConfiguration {
readonly machineId: string;
readonly sqmId: string;
readonly codeCachePath: string | undefined;
readonly args: NativeParsedArgs;
@@ -23,6 +23,7 @@ export function resolveCommonProperties(
commit: string | undefined,
version: string | undefined,
machineId: string | undefined,
sqmId: string | undefined,
isInternalTelemetry: boolean,
product?: string
): ICommonProperties {
@@ -30,6 +31,8 @@ export function resolveCommonProperties(
// __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
result['common.machineId'] = machineId;
// __GDPR__COMMON__ "common.sqmId" : { "endPoint": "SQMMachineId", "classification": "EndUserPseudonymizedInformation", "purpose": "BusinessInsight" }
result['common.sqmId'] = sqmId;
// __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['sessionID'] = generateUuid() + Date.now();
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
@@ -71,6 +71,7 @@ export const currentSessionDateStorageKey = 'telemetry.currentSessionDate';
export const firstSessionDateStorageKey = 'telemetry.firstSessionDate';
export const lastSessionDateStorageKey = 'telemetry.lastSessionDate';
export const machineIdKey = 'telemetry.machineId';
export const sqmIdKey = 'telemetry.sqmId';
// Configuration Keys
export const TELEMETRY_SECTION_ID = 'telemetry';
@@ -5,12 +5,18 @@
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { machineIdKey } from 'vs/platform/telemetry/common/telemetry';
import { resolveMachineId as resolveNodeMachineId } from 'vs/platform/telemetry/node/telemetryUtils';
import { machineIdKey, sqmIdKey } from 'vs/platform/telemetry/common/telemetry';
import { resolveMachineId as resolveNodeMachineId, resolveSqmId as resolveNodeSqmId } from 'vs/platform/telemetry/node/telemetryUtils';
export async function resolveMachineId(stateService: IStateService, logService: ILogService) {
export async function resolveMachineId(stateService: IStateService, logService: ILogService): Promise<string> {
// Call the node layers implementation to avoid code duplication
const machineId = await resolveNodeMachineId(stateService, logService);
stateService.setItem(machineIdKey, machineId);
return machineId;
}
export async function resolveSqmId(stateService: IStateService, logService: ILogService): Promise<string> {
const sqmId = await resolveNodeSqmId(stateService, logService);
stateService.setItem(sqmIdKey, sqmId);
return sqmId;
}
@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { isMacintosh } from 'vs/base/common/platform';
import { getMachineId } from 'vs/base/node/id';
import { getMachineId, getSqmMachineId } from 'vs/base/node/id';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateReadService } from 'vs/platform/state/node/state';
import { machineIdKey } from 'vs/platform/telemetry/common/telemetry';
import { machineIdKey, sqmIdKey } from 'vs/platform/telemetry/common/telemetry';
export async function resolveMachineId(stateService: IStateReadService, logService: ILogService) {
export async function resolveMachineId(stateService: IStateReadService, logService: ILogService): Promise<string> {
// We cache the machineId for faster lookups
// and resolve it only once initially if not cached or we need to replace the macOS iBridge device
let machineId = stateService.getItem<string>(machineIdKey);
@@ -20,3 +20,12 @@ export async function resolveMachineId(stateService: IStateReadService, logServi
return machineId;
}
export async function resolveSqmId(stateService: IStateReadService, logService: ILogService): Promise<string> {
let sqmId = stateService.getItem<string>(sqmIdKey);
if (typeof sqmId !== 'string') {
sqmId = await getSqmMachineId(logService.error.bind(logService));
}
return sqmId;
}
+1
View File
@@ -280,6 +280,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
mainPid: number;
machineId: string;
sqmId: string;
execPath: string;
backupPath?: string;
@@ -199,6 +199,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
constructor(
private readonly machineId: string,
private readonly sqmId: string,
private readonly initialUserEnv: IProcessEnvironment,
@ILogService private readonly logService: ILogService,
@ILoggerMainService private readonly loggerService: ILoggerMainService,
@@ -1381,6 +1382,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
...options.cli,
machineId: this.machineId,
sqmId: this.sqmId,
windowId: -1, // Will be filled in by the window once loaded later
+5 -4
View File
@@ -9,7 +9,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { getMachineId } from 'vs/base/node/id';
import { getMachineId, getSqmMachineId } from 'vs/base/node/id';
import { Promises } from 'vs/base/node/pfs';
import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
@@ -132,10 +132,11 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)));
// Initialize
const [, , machineId] = await Promise.all([
const [, , machineId, sqmId] = await Promise.all([
configurationService.initialize(),
userDataProfilesService.init(),
getMachineId(logService.error.bind(logService))
getMachineId(logService.error.bind(logService)),
getSqmMachineId(logService.error.bind(logService))
]);
const extensionHostStatusService = new ExtensionHostStatusService();
@@ -155,7 +156,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
const config: ITelemetryServiceConfig = {
appenders: [oneDsAppender],
commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, isInternal, 'remoteAgent'),
commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, sqmId, isInternal, 'remoteAgent'),
piiPaths: getPiiPathsFromEnvironment(environmentService)
};
const initialTelemetryLevelArg = environmentService.args['telemetry-level'];
@@ -38,6 +38,7 @@ export interface INativeWorkbenchEnvironmentService extends IBrowserWorkbenchEnv
readonly mainPid: number;
readonly os: IOSConfiguration;
readonly machineId: string;
readonly sqmId: string;
// --- Paths
readonly execPath: string;
@@ -59,6 +60,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment
@memoize
get machineId() { return this.configuration.machineId; }
@memoize
get sqmId() { return this.configuration.sqmId; }
@memoize
get remoteAuthority() { return this.configuration.remoteAuthority; }
@@ -16,11 +16,12 @@ export function resolveWorkbenchCommonProperties(
commit: string | undefined,
version: string | undefined,
machineId: string,
sqmId: string,
isInternalTelemetry: boolean,
process: INodeProcess,
remoteAuthority?: string
): ICommonProperties {
const result = resolveCommonProperties(release, hostname, process.arch, commit, version, machineId, isInternalTelemetry);
const result = resolveCommonProperties(release, hostname, process.arch, commit, version, machineId, sqmId, isInternalTelemetry);
const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION)!;
const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.APPLICATION)!;
@@ -44,7 +44,7 @@ export class TelemetryService extends Disposable implements ITelemetryService {
const channel = sharedProcessService.getChannel('telemetryAppender');
const config: ITelemetryServiceConfig = {
appenders: [new TelemetryAppenderClient(channel)],
commonProperties: resolveWorkbenchCommonProperties(storageService, environmentService.os.release, environmentService.os.hostname, productService.commit, productService.version, environmentService.machineId, isInternal, process, environmentService.remoteAuthority),
commonProperties: resolveWorkbenchCommonProperties(storageService, environmentService.os.release, environmentService.os.hostname, productService.commit, productService.version, environmentService.machineId, environmentService.sqmId, isInternal, process, environmentService.remoteAuthority),
piiPaths: getPiiPathsFromEnvironment(environmentService),
sendErrorTelemetry: true
};
@@ -22,7 +22,7 @@ suite('Telemetry - common properties', function () {
ensureNoDisposablesAreLeakedInTestSuite();
test('default', function () {
const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', false, process);
const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', 'someSqmId', false, process);
assert.ok('commitHash' in props);
assert.ok('sessionID' in props);
assert.ok('timestamp' in props);
@@ -46,14 +46,14 @@ suite('Telemetry - common properties', function () {
testStorageService.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.APPLICATION, StorageTarget.MACHINE);
const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', false, process);
const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', 'someSqmId', false, process);
assert.ok('common.lastSessionDate' in props); // conditional, see below
assert.ok('common.isNewSession' in props);
assert.strictEqual(props['common.isNewSession'], '0');
});
test('values chance on ask', async function () {
const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', false, process);
const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', 'someSqmId', false, process);
let value1 = props['common.sequence'];
let value2 = props['common.sequence'];
assert.ok(value1 !== value2, 'seq');
@@ -55,6 +55,7 @@ const NULL_PROFILE = {
const TestNativeWindowConfiguration: INativeWindowConfiguration = {
windowId: 0,
machineId: 'testMachineId',
sqmId: 'testSqmId',
logLevel: LogLevel.Error,
loggers: { global: [], window: [] },
mainPid: 0,