mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-03 01:25:38 -05:00
Merge from vscode 7653d836944892f83ce9e1f95c1204bafa1aec31
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
import * as osLib from 'os';
|
||||
import { virtualMachineHint } from 'vs/base/node/id';
|
||||
import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { readdir, stat, exists, readFile } from 'fs';
|
||||
import { readdir, exists, readFile } from 'fs';
|
||||
import { join, basename } from 'vs/base/common/path';
|
||||
import { parse, ParseError, getNodeType } from 'vs/base/common/json';
|
||||
import { listProcesses } from 'vs/base/node/ps';
|
||||
@@ -17,6 +17,7 @@ import { ProcessItem } from 'vs/base/common/processes';
|
||||
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
|
||||
export const ID = 'diagnosticsService';
|
||||
export const IDiagnosticsService = createDecorator<IDiagnosticsService>(ID);
|
||||
@@ -42,24 +43,31 @@ export interface ProcessInfo {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function collectWorkspaceStats(folder: string, filter: string[]): Promise<WorkspaceStats> {
|
||||
const configFilePatterns = [
|
||||
{ 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
|
||||
{ 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
|
||||
{ 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
|
||||
{ 'tag': 'package.json', 'pattern': /^package\.json$/i },
|
||||
{ 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
|
||||
{ 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
|
||||
{ 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
|
||||
{ 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
|
||||
{ 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
|
||||
{ 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
|
||||
{ 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
|
||||
{ 'tag': 'project.json', 'pattern': /^project\.json$/i },
|
||||
{ 'tag': 'makefile', 'pattern': /^makefile$/i },
|
||||
{ 'tag': 'sln', 'pattern': /^.+\.sln$/i },
|
||||
{ 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
|
||||
{ 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
|
||||
interface ConfigFilePatterns {
|
||||
tag: string;
|
||||
filePattern: RegExp;
|
||||
relativePathPattern?: RegExp;
|
||||
}
|
||||
|
||||
export async function collectWorkspaceStats(folder: string, filter: string[]): Promise<WorkspaceStats> {
|
||||
const configFilePatterns: ConfigFilePatterns[] = [
|
||||
{ tag: 'grunt.js', filePattern: /^gruntfile\.js$/i },
|
||||
{ tag: 'gulp.js', filePattern: /^gulpfile\.js$/i },
|
||||
{ tag: 'tsconfig.json', filePattern: /^tsconfig\.json$/i },
|
||||
{ tag: 'package.json', filePattern: /^package\.json$/i },
|
||||
{ tag: 'jsconfig.json', filePattern: /^jsconfig\.json$/i },
|
||||
{ tag: 'tslint.json', filePattern: /^tslint\.json$/i },
|
||||
{ tag: 'eslint.json', filePattern: /^eslint\.json$/i },
|
||||
{ tag: 'tasks.json', filePattern: /^tasks\.json$/i },
|
||||
{ tag: 'launch.json', filePattern: /^launch\.json$/i },
|
||||
{ tag: 'settings.json', filePattern: /^settings\.json$/i },
|
||||
{ tag: 'webpack.config.js', filePattern: /^webpack\.config\.js$/i },
|
||||
{ tag: 'project.json', filePattern: /^project\.json$/i },
|
||||
{ tag: 'makefile', filePattern: /^makefile$/i },
|
||||
{ tag: 'sln', filePattern: /^.+\.sln$/i },
|
||||
{ tag: 'csproj', filePattern: /^.+\.csproj$/i },
|
||||
{ tag: 'cmake', filePattern: /^.+\.cmake$/i },
|
||||
{ tag: 'github-actions', filePattern: /^.+\.yml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }
|
||||
];
|
||||
|
||||
const fileTypes = new Map<string, number>();
|
||||
@@ -67,123 +75,91 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Promise
|
||||
|
||||
const MAX_FILES = 20000;
|
||||
|
||||
function walk(dir: string, filter: string[], token: { count: number, maxReached: boolean }, done: (allFiles: string[]) => void): void {
|
||||
let results: string[] = [];
|
||||
readdir(dir, async (err, files) => {
|
||||
// Ignore folders that can't be read
|
||||
if (err) {
|
||||
return done(results);
|
||||
}
|
||||
function collect(root: string, dir: string, filter: string[], token: { count: number, maxReached: boolean }): Promise<void> {
|
||||
const relativePath = dir.substring(root.length + 1);
|
||||
|
||||
return new Promise(resolve => {
|
||||
readdir(dir, { withFileTypes: true }, async (err, files) => {
|
||||
// Ignore folders that can't be read
|
||||
if (err) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.count > MAX_FILES) {
|
||||
token.count += files.length;
|
||||
token.maxReached = true;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let pending = files.length;
|
||||
if (pending === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let filesToRead = files;
|
||||
if (token.count + files.length > MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
pending = MAX_FILES - token.count;
|
||||
filesToRead = files.slice(0, pending);
|
||||
}
|
||||
|
||||
if (token.count > MAX_FILES) {
|
||||
token.count += files.length;
|
||||
token.maxReached = true;
|
||||
return done(results);
|
||||
}
|
||||
|
||||
let pending = files.length;
|
||||
if (pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
for (const file of filesToRead) {
|
||||
if (file.isDirectory()) {
|
||||
if (!filter.includes(file.name)) {
|
||||
await collect(root, join(dir, file.name), filter, token);
|
||||
}
|
||||
|
||||
let filesToRead = files;
|
||||
if (token.count + files.length > MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
pending = MAX_FILES - token.count;
|
||||
filesToRead = files.slice(0, pending);
|
||||
}
|
||||
|
||||
token.count += files.length;
|
||||
|
||||
for (const file of filesToRead) {
|
||||
stat(join(dir, file), (err, stats) => {
|
||||
// Ignore files that can't be read
|
||||
if (err) {
|
||||
if (--pending === 0) {
|
||||
return done(results);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (stats.isDirectory()) {
|
||||
if (filter.indexOf(file) === -1) {
|
||||
walk(join(dir, file), filter, token, (res: string[]) => {
|
||||
results = results.concat(res);
|
||||
|
||||
if (--pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (--pending === 0) {
|
||||
done(results);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
results.push(file);
|
||||
|
||||
if (--pending === 0) {
|
||||
done(results);
|
||||
const index = file.name.lastIndexOf('.');
|
||||
if (index >= 0) {
|
||||
const fileType = file.name.substring(index + 1);
|
||||
if (fileType) {
|
||||
fileTypes.set(fileType, (fileTypes.get(fileType) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const configFile of configFilePatterns) {
|
||||
if (configFile.relativePathPattern?.test(relativePath) !== false && configFile.filePattern.test(file.name)) {
|
||||
configFiles.set(configFile.tag, (configFiles.get(configFile.tag) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (--pending === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const addFileType = (fileType: string) => {
|
||||
if (fileTypes.has(fileType)) {
|
||||
fileTypes.set(fileType, fileTypes.get(fileType)! + 1);
|
||||
}
|
||||
else {
|
||||
fileTypes.set(fileType, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const addConfigFiles = (fileName: string) => {
|
||||
for (const each of configFilePatterns) {
|
||||
if (each.pattern.test(fileName)) {
|
||||
if (configFiles.has(each.tag)) {
|
||||
configFiles.set(each.tag, configFiles.get(each.tag)! + 1);
|
||||
} else {
|
||||
configFiles.set(each.tag, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const acceptFile = (name: string) => {
|
||||
if (name.lastIndexOf('.') >= 0) {
|
||||
const suffix: string | undefined = name.split('.').pop();
|
||||
if (suffix) {
|
||||
addFileType(suffix);
|
||||
}
|
||||
}
|
||||
addConfigFiles(name);
|
||||
};
|
||||
|
||||
const token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
walk(folder, filter, token, async (files) => {
|
||||
files.forEach(acceptFile);
|
||||
|
||||
const launchConfigs = await collectLaunchConfigs(folder);
|
||||
|
||||
resolve({
|
||||
configFiles: asSortedItems(configFiles),
|
||||
fileTypes: asSortedItems(fileTypes),
|
||||
fileCount: token.count,
|
||||
maxFilesReached: token.maxReached,
|
||||
launchConfigFiles: launchConfigs
|
||||
});
|
||||
});
|
||||
});
|
||||
await collect(folder, folder, filter, token);
|
||||
const launchConfigs = await collectLaunchConfigs(folder);
|
||||
return {
|
||||
configFiles: asSortedItems(configFiles),
|
||||
fileTypes: asSortedItems(fileTypes),
|
||||
fileCount: token.count,
|
||||
maxFilesReached: token.maxReached,
|
||||
launchConfigFiles: launchConfigs
|
||||
};
|
||||
}
|
||||
|
||||
function asSortedItems(map: Map<string, number>): WorkspaceStatItem[] {
|
||||
const a: WorkspaceStatItem[] = [];
|
||||
map.forEach((value, index) => a.push({ name: index, count: value }));
|
||||
return a.sort((a, b) => b.count - a.count);
|
||||
function asSortedItems(items: Map<string, number>): WorkspaceStatItem[] {
|
||||
return [
|
||||
...Iterable.map(items.entries(), ([name, count]) => ({ name: name, count: count }))
|
||||
].sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export function getMachineInfo(): IMachineInfo {
|
||||
@@ -540,58 +516,61 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
}
|
||||
|
||||
public async reportWorkspaceStats(workspace: IWorkspaceInformation): Promise<void> {
|
||||
workspace.folders.forEach(folder => {
|
||||
const folderUri = URI.revive(folder.uri);
|
||||
if (folderUri.scheme === 'file') {
|
||||
const folder = folderUri.fsPath;
|
||||
collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => {
|
||||
type WorkspaceStatsClassification = {
|
||||
'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
type WorkspaceStatsEvent = {
|
||||
'workspace.id': string | undefined;
|
||||
rendererSessionId: string;
|
||||
};
|
||||
this.telemetryService.publicLog2<WorkspaceStatsEvent, WorkspaceStatsClassification>('workspace.stats', {
|
||||
'workspace.id': workspace.telemetryId,
|
||||
rendererSessionId: workspace.rendererSessionId
|
||||
});
|
||||
type WorkspaceStatsFileClassification = {
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
type WorkspaceStatsFileEvent = {
|
||||
rendererSessionId: string;
|
||||
type: string;
|
||||
count: number;
|
||||
};
|
||||
stats.fileTypes.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.file', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.launchConfigFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.launchConfigFile', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.configFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.configFiles', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
}).catch(_ => {
|
||||
// Report nothing if collecting metadata fails.
|
||||
});
|
||||
for (const { uri } of workspace.folders) {
|
||||
const folderUri = URI.revive(uri);
|
||||
if (folderUri.scheme !== 'file') {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
const folder = folderUri.fsPath;
|
||||
try {
|
||||
const stats = await collectWorkspaceStats(folder, ['node_modules', '.git']);
|
||||
type WorkspaceStatsClassification = {
|
||||
'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
type WorkspaceStatsEvent = {
|
||||
'workspace.id': string | undefined;
|
||||
rendererSessionId: string;
|
||||
};
|
||||
this.telemetryService.publicLog2<WorkspaceStatsEvent, WorkspaceStatsClassification>('workspace.stats', {
|
||||
'workspace.id': workspace.telemetryId,
|
||||
rendererSessionId: workspace.rendererSessionId
|
||||
});
|
||||
type WorkspaceStatsFileClassification = {
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
type WorkspaceStatsFileEvent = {
|
||||
rendererSessionId: string;
|
||||
type: string;
|
||||
count: number;
|
||||
};
|
||||
stats.fileTypes.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.file', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.launchConfigFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.launchConfigFile', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.configFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.configFiles', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
// Report nothing if collecting metadata fails.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user