mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 01:25:38 -05:00
* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 * Bump distro * Upgrade GCC to 4.9 due to yarn install errors * Update build image * Fix bootstrap base url * Bump distro * Fix build errors * Update source map file * Disable checkbox for blocking migration issues (#15131) * disable checkbox for blocking issues * wip * disable checkbox fixes * fix strings * Remove duplicate tsec command * Default to off for tab color if settings not present * re-skip failing tests * Fix mocha error * Bump sqlite version & fix notebooks search view * Turn off esbuild warnings * Update esbuild log level * Fix overflowactionbar tests * Fix ts-ignore in dropdown tests * cleanup/fixes * Fix hygiene * Bundle in entire zone.js module * Remove extra constructor param * bump distro for web compile break * bump distro for web compile break v2 * Undo log level change * New distro * Fix integration test scripts * remove the "no yarn.lock changes" workflow * fix scripts v2 * Update unit test scripts * Ensure ads-kerberos2 updates in .vscodeignore * Try fix unit tests * Upload crash reports * remove nogpu * always upload crashes * Use bash script * Consolidate data/ext dir names * Create in tmp directory Co-authored-by: chlafreniere <hichise@gmail.com> Co-authored-by: Christopher Suh <chsuh@microsoft.com> Co-authored-by: chgagnon <chgagnon@microsoft.com>
488 lines
15 KiB
TypeScript
488 lines
15 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import 'vs/css!./media/processExplorer';
|
|
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
|
|
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
|
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
|
|
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
|
import { localize } from 'vs/nls';
|
|
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
|
|
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window';
|
|
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
|
|
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
|
|
import { ProcessItem } from 'vs/base/common/processes';
|
|
import * as dom from 'vs/base/browser/dom';
|
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
|
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
|
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
|
import { ByteSize } from 'vs/platform/files/common/files';
|
|
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
|
import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
|
import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
|
|
|
|
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
|
|
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
|
|
|
|
class ProcessListDelegate implements IListVirtualDelegate<MachineProcessInformation | ProcessItem | IRemoteDiagnosticError> {
|
|
getHeight(element: MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) {
|
|
return 22;
|
|
}
|
|
|
|
getTemplateId(element: ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) {
|
|
if (isProcessItem(element)) {
|
|
return 'process';
|
|
}
|
|
|
|
if (isMachineProcessInformation(element)) {
|
|
return 'machine';
|
|
}
|
|
|
|
if (isRemoteDiagnosticError(element)) {
|
|
return 'error';
|
|
}
|
|
|
|
if (isProcessInformation(element)) {
|
|
return 'header';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
}
|
|
|
|
interface IProcessItemTemplateData extends IProcessRowTemplateData {
|
|
CPU: HTMLElement;
|
|
memory: HTMLElement;
|
|
PID: HTMLElement;
|
|
}
|
|
|
|
interface IProcessRowTemplateData {
|
|
name: HTMLElement;
|
|
}
|
|
|
|
class ProcessTreeDataSource implements IDataSource<ProcessTree, ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError> {
|
|
hasChildren(element: ProcessTree | ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError): boolean {
|
|
if (isRemoteDiagnosticError(element)) {
|
|
return false;
|
|
}
|
|
|
|
if (isProcessItem(element)) {
|
|
return !!element.children?.length;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
getChildren(element: ProcessTree | ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) {
|
|
if (isProcessItem(element)) {
|
|
return element.children ? element.children : [];
|
|
}
|
|
|
|
if (isRemoteDiagnosticError(element)) {
|
|
return [];
|
|
}
|
|
|
|
if (isProcessInformation(element)) {
|
|
// If there are multiple process roots, return these, otherwise go directly to the root process
|
|
if (element.processRoots.length > 1) {
|
|
return element.processRoots;
|
|
} else {
|
|
return [element.processRoots[0].rootProcess];
|
|
}
|
|
}
|
|
|
|
if (isMachineProcessInformation(element)) {
|
|
return [element.rootProcess];
|
|
}
|
|
|
|
return [element.processes];
|
|
}
|
|
}
|
|
|
|
class ProcessHeaderTreeRenderer implements ITreeRenderer<ProcessInformation, void, IProcessItemTemplateData> {
|
|
templateId: string = 'header';
|
|
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
|
|
const data = Object.create(null);
|
|
const row = dom.append(container, dom.$('.row'));
|
|
data.name = dom.append(row, dom.$('.nameLabel'));
|
|
data.CPU = dom.append(row, dom.$('.cpu'));
|
|
data.memory = dom.append(row, dom.$('.memory'));
|
|
data.PID = dom.append(row, dom.$('.pid'));
|
|
return data;
|
|
}
|
|
renderElement(node: ITreeNode<ProcessInformation, void>, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void {
|
|
templateData.name.textContent = localize('name', "Process Name");
|
|
templateData.CPU.textContent = localize('cpu', "CPU %");
|
|
templateData.PID.textContent = localize('pid', "PID");
|
|
templateData.memory.textContent = localize('memory', "Memory (MB)");
|
|
|
|
}
|
|
disposeTemplate(templateData: any): void {
|
|
// Nothing to do
|
|
}
|
|
}
|
|
|
|
class MachineRenderer implements ITreeRenderer<MachineProcessInformation, void, IProcessRowTemplateData> {
|
|
templateId: string = 'machine';
|
|
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
|
|
const data = Object.create(null);
|
|
const row = dom.append(container, dom.$('.row'));
|
|
data.name = dom.append(row, dom.$('.nameLabel'));
|
|
return data;
|
|
}
|
|
renderElement(node: ITreeNode<MachineProcessInformation, void>, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void {
|
|
templateData.name.textContent = node.element.name;
|
|
}
|
|
disposeTemplate(templateData: IProcessRowTemplateData): void {
|
|
// Nothing to do
|
|
}
|
|
}
|
|
|
|
class ErrorRenderer implements ITreeRenderer<IRemoteDiagnosticError, void, IProcessRowTemplateData> {
|
|
templateId: string = 'error';
|
|
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
|
|
const data = Object.create(null);
|
|
const row = dom.append(container, dom.$('.row'));
|
|
data.name = dom.append(row, dom.$('.nameLabel'));
|
|
return data;
|
|
}
|
|
renderElement(node: ITreeNode<IRemoteDiagnosticError, void>, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void {
|
|
templateData.name.textContent = node.element.errorMessage;
|
|
}
|
|
disposeTemplate(templateData: IProcessRowTemplateData): void {
|
|
// Nothing to do
|
|
}
|
|
}
|
|
|
|
|
|
class ProcessRenderer implements ITreeRenderer<ProcessItem, void, IProcessItemTemplateData> {
|
|
constructor(private platform: string, private totalMem: number, private mapPidToWindowTitle: Map<number, string>) { }
|
|
|
|
templateId: string = 'process';
|
|
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
|
|
const data = <IProcessItemTemplateData>Object.create(null);
|
|
const row = dom.append(container, dom.$('.row'));
|
|
|
|
data.name = dom.append(row, dom.$('.nameLabel'));
|
|
data.CPU = dom.append(row, dom.$('.cpu'));
|
|
data.memory = dom.append(row, dom.$('.memory'));
|
|
data.PID = dom.append(row, dom.$('.pid'));
|
|
|
|
return data;
|
|
}
|
|
renderElement(node: ITreeNode<ProcessItem, void>, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void {
|
|
const { element } = node;
|
|
|
|
let name = element.name;
|
|
if (name === 'window') {
|
|
const windowTitle = this.mapPidToWindowTitle.get(element.pid);
|
|
name = windowTitle !== undefined ? `${name} (${this.mapPidToWindowTitle.get(element.pid)})` : name;
|
|
}
|
|
|
|
templateData.name.textContent = name;
|
|
templateData.name.title = element.cmd;
|
|
|
|
templateData.CPU.textContent = element.load.toFixed(0);
|
|
templateData.PID.textContent = element.pid.toFixed(0);
|
|
|
|
const memory = this.platform === 'win32' ? element.mem : (this.totalMem * (element.mem / 100));
|
|
templateData.memory.textContent = (memory / ByteSize.MB).toFixed(0);
|
|
}
|
|
|
|
disposeTemplate(templateData: IProcessItemTemplateData): void {
|
|
// Nothing to do
|
|
}
|
|
}
|
|
|
|
interface MachineProcessInformation {
|
|
name: string;
|
|
rootProcess: ProcessItem | IRemoteDiagnosticError
|
|
}
|
|
|
|
interface ProcessInformation {
|
|
processRoots: MachineProcessInformation[];
|
|
}
|
|
|
|
interface ProcessTree {
|
|
processes: ProcessInformation;
|
|
}
|
|
|
|
function isMachineProcessInformation(item: any): item is MachineProcessInformation {
|
|
return !!item.name && !!item.rootProcess;
|
|
}
|
|
|
|
function isProcessInformation(item: any): item is ProcessInformation {
|
|
return !!item.processRoots;
|
|
}
|
|
|
|
function isProcessItem(item: any): item is ProcessItem {
|
|
return !!item.pid;
|
|
}
|
|
|
|
class ProcessExplorer {
|
|
private lastRequestTime: number;
|
|
|
|
private mapPidToWindowTitle = new Map<number, string>();
|
|
|
|
private listeners = new DisposableStore();
|
|
|
|
private nativeHostService: INativeHostService;
|
|
|
|
private tree: DataTree<any, ProcessTree | MachineProcessInformation | ProcessItem | ProcessInformation | IRemoteDiagnosticError, any> | undefined;
|
|
|
|
constructor(windowId: number, private data: ProcessExplorerData) {
|
|
const mainProcessService = new ElectronIPCMainProcessService(windowId);
|
|
this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService;
|
|
|
|
this.applyStyles(data.styles);
|
|
|
|
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
|
|
ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => {
|
|
this.mapPidToWindowTitle = new Map<number, string>();
|
|
windows.forEach(window => this.mapPidToWindowTitle.set(window.pid, window.title));
|
|
});
|
|
|
|
ipcRenderer.on('vscode:listProcessesResponse', async (event: unknown, processRoots: MachineProcessInformation[]) => {
|
|
processRoots.forEach((info, index) => {
|
|
if (isProcessItem(info.rootProcess)) {
|
|
info.rootProcess.name = index === 0 ? `${this.data.applicationName} main` : 'remote agent';
|
|
}
|
|
});
|
|
|
|
if (!this.tree) {
|
|
await this.createProcessTree(processRoots);
|
|
} else {
|
|
this.tree.setInput({ processes: { processRoots } });
|
|
}
|
|
|
|
this.requestProcessList(0);
|
|
});
|
|
|
|
this.lastRequestTime = Date.now();
|
|
ipcRenderer.send('vscode:windowsInfoRequest');
|
|
ipcRenderer.send('vscode:listProcesses');
|
|
}
|
|
|
|
private async createProcessTree(processRoots: MachineProcessInformation[]): Promise<void> {
|
|
const container = document.getElementById('process-list');
|
|
if (!container) {
|
|
return;
|
|
}
|
|
|
|
const { totalmem } = await this.nativeHostService.getOSStatistics();
|
|
|
|
const renderers = [
|
|
new ProcessRenderer(this.data.platform, totalmem, this.mapPidToWindowTitle),
|
|
new ProcessHeaderTreeRenderer(),
|
|
new MachineRenderer(),
|
|
new ErrorRenderer()
|
|
];
|
|
|
|
this.tree = new DataTree('processExplorer',
|
|
container,
|
|
new ProcessListDelegate(),
|
|
renderers,
|
|
new ProcessTreeDataSource(),
|
|
{
|
|
identityProvider:
|
|
{
|
|
getId: (element: ProcessTree | ProcessItem | MachineProcessInformation | ProcessInformation | IRemoteDiagnosticError) => {
|
|
if (isProcessItem(element)) {
|
|
return element.pid.toString();
|
|
}
|
|
|
|
if (isRemoteDiagnosticError(element)) {
|
|
return element.hostName;
|
|
}
|
|
|
|
if (isProcessInformation(element)) {
|
|
return 'processes';
|
|
}
|
|
|
|
if (isMachineProcessInformation(element)) {
|
|
return element.name;
|
|
}
|
|
|
|
return 'header';
|
|
}
|
|
}
|
|
});
|
|
|
|
this.tree.setInput({ processes: { processRoots } });
|
|
this.tree.layout(window.innerHeight, window.innerWidth);
|
|
this.tree.onContextMenu(e => {
|
|
if (isProcessItem(e.element)) {
|
|
this.showContextMenu(e.element, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
private isDebuggable(cmd: string): boolean {
|
|
const matches = DEBUG_FLAGS_PATTERN.exec(cmd);
|
|
return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0;
|
|
}
|
|
|
|
private attachTo(item: ProcessItem) {
|
|
const config: any = {
|
|
type: 'node',
|
|
request: 'attach',
|
|
name: `process ${item.pid}`
|
|
};
|
|
|
|
let matches = DEBUG_FLAGS_PATTERN.exec(item.cmd);
|
|
if (matches && matches.length >= 2) {
|
|
// attach via port
|
|
if (matches.length === 4 && matches[3]) {
|
|
config.port = parseInt(matches[3]);
|
|
}
|
|
config.protocol = matches[1] === 'debug' ? 'legacy' : 'inspector';
|
|
} else {
|
|
// no port -> try to attach via pid (send SIGUSR1)
|
|
config.processId = String(item.pid);
|
|
}
|
|
|
|
// a debug-port=n or inspect-port=n overrides the port
|
|
matches = DEBUG_PORT_PATTERN.exec(item.cmd);
|
|
if (matches && matches.length === 3) {
|
|
// override port
|
|
config.port = parseInt(matches[2]);
|
|
}
|
|
|
|
ipcRenderer.send('vscode:workbenchCommand', { id: 'debug.startFromConfig', from: 'processExplorer', args: [config] });
|
|
}
|
|
|
|
private applyStyles(styles: ProcessExplorerStyles): void {
|
|
const styleTag = document.createElement('style');
|
|
const content: string[] = [];
|
|
|
|
if (styles.hoverBackground) {
|
|
content.push(`.monaco-list-row:hover { background-color: ${styles.hoverBackground}; }`);
|
|
}
|
|
|
|
if (styles.hoverForeground) {
|
|
content.push(`.monaco-list-row:hover { color: ${styles.hoverForeground}; }`);
|
|
}
|
|
|
|
styleTag.textContent = content.join('\n');
|
|
if (document.head) {
|
|
document.head.appendChild(styleTag);
|
|
}
|
|
if (styles.color) {
|
|
document.body.style.color = styles.color;
|
|
}
|
|
}
|
|
|
|
private showContextMenu(item: ProcessItem, isLocal: boolean) {
|
|
const items: IContextMenuItem[] = [];
|
|
const pid = Number(item.pid);
|
|
|
|
if (isLocal) {
|
|
items.push({
|
|
label: localize('killProcess', "Kill Process"),
|
|
click: () => {
|
|
this.nativeHostService.killProcess(pid, 'SIGTERM');
|
|
}
|
|
});
|
|
|
|
items.push({
|
|
label: localize('forceKillProcess', "Force Kill Process"),
|
|
click: () => {
|
|
this.nativeHostService.killProcess(pid, 'SIGKILL');
|
|
}
|
|
});
|
|
|
|
items.push({
|
|
type: 'separator'
|
|
});
|
|
}
|
|
|
|
items.push({
|
|
label: localize('copy', "Copy"),
|
|
click: () => {
|
|
const row = document.getElementById(pid.toString());
|
|
if (row) {
|
|
this.nativeHostService.writeClipboardText(row.innerText);
|
|
}
|
|
}
|
|
});
|
|
|
|
items.push({
|
|
label: localize('copyAll', "Copy All"),
|
|
click: () => {
|
|
const processList = document.getElementById('process-list');
|
|
if (processList) {
|
|
this.nativeHostService.writeClipboardText(processList.innerText);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (item && isLocal && this.isDebuggable(item.cmd)) {
|
|
items.push({
|
|
type: 'separator'
|
|
});
|
|
|
|
items.push({
|
|
label: localize('debug', "Debug"),
|
|
click: () => {
|
|
this.attachTo(item);
|
|
}
|
|
});
|
|
}
|
|
|
|
popup(items);
|
|
}
|
|
|
|
private requestProcessList(totalWaitTime: number): void {
|
|
setTimeout(() => {
|
|
const nextRequestTime = Date.now();
|
|
const waited = totalWaitTime + nextRequestTime - this.lastRequestTime;
|
|
this.lastRequestTime = nextRequestTime;
|
|
|
|
// Wait at least a second between requests.
|
|
if (waited > 1000) {
|
|
ipcRenderer.send('vscode:windowsInfoRequest');
|
|
ipcRenderer.send('vscode:listProcesses');
|
|
} else {
|
|
this.requestProcessList(waited);
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
public dispose() {
|
|
this.listeners.dispose();
|
|
}
|
|
}
|
|
|
|
export function startup(windowId: number, data: ProcessExplorerData): void {
|
|
const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac';
|
|
document.body.classList.add(platformClass); // used by our fonts
|
|
applyZoom(data.zoomLevel);
|
|
|
|
const processExplorer = new ProcessExplorer(windowId, data);
|
|
|
|
document.onkeydown = (e: KeyboardEvent) => {
|
|
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
|
|
|
|
// Cmd/Ctrl + w closes issue window
|
|
if (cmdOrCtrlKey && e.keyCode === 87) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
processExplorer.dispose();
|
|
ipcRenderer.send('vscode:closeProcessExplorer');
|
|
}
|
|
|
|
// Cmd/Ctrl + zooms in
|
|
if (cmdOrCtrlKey && e.keyCode === 187) {
|
|
zoomIn();
|
|
}
|
|
|
|
// Cmd/Ctrl - zooms out
|
|
if (cmdOrCtrlKey && e.keyCode === 189) {
|
|
zoomOut();
|
|
}
|
|
};
|
|
}
|