Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(diagnostics): Ability to load a replay of diagnostics events from file #270

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,19 @@
},
"commands": [
{
"command": "minecraft-debugger.showMinecraftDiagnostics",
"title": "Minecraft Diagnostics: Show"
"command": "minecraft-debugger.liveDiagnostics",
"title": "Minecraft Diagnostics: Show Live Stats"
},
{
"command": "minecraft-debugger.replayDiagnostics",
"title": "Minecraft Diagnostics: Open Stats Replay"
},
{
"command": "minecraft-debugger.minecraftReload",
"title": "Minecraft Reload"
}
],
"keybindings":[
"keybindings": [
{
"command": "minecraft-debugger.minecraftReload",
"key": "ctrl+shift+r",
Expand Down Expand Up @@ -242,4 +246,4 @@
"typescript": "^5.5.4",
"vitest": "^2.1.3"
}
}
}
35 changes: 18 additions & 17 deletions src/Session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

// Copyright (C) Microsoft Corporation. All rights reserved.

import { createConnection, Server, Socket } from 'net';
Expand Down Expand Up @@ -28,10 +27,10 @@
import { DebugProtocol } from '@vscode/debugprotocol';
import { EventEmitter } from 'events';
import { LogOutputEvent, LogLevel } from '@vscode/debugadapter/lib/logger';
import { MessageStreamParser } from './MessageStreamParser';
import { SourceMaps } from './SourceMaps';
import { StatMessageModel, StatsProvider2 } from './StatsProvider2';
import { HomeViewProvider } from './panels/HomeViewProvider';
import { MessageStreamParser } from './message-stream-parser';
import { SourceMaps } from './source-maps';
import { StatMessageModel, StatsProvider } from './stats/stats-provider';
import { HomeViewProvider } from './panels/home-view-provider';
import * as path from 'path';
import * as fs from 'fs';
import { isUUID } from './Utils';
Expand All @@ -48,20 +47,20 @@

interface PluginDetails {
name: string;
module_uuid: string;

Check warning on line 50 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `module_uuid` must match one of the following formats: camelCase
}

interface ProtocolCapabilities {
type: string;
version: number;
plugins: PluginDetails[];
require_passcode?: boolean;

Check warning on line 57 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `require_passcode` must match one of the following formats: camelCase
}

interface ProfilerCapture {
type: string;
capture_base_path: string;

Check warning on line 62 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `capture_base_path` must match one of the following formats: camelCase
capture_data: string;

Check warning on line 63 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `capture_data` must match one of the following formats: camelCase
}

// Interface for specific launch arguments.
Expand Down Expand Up @@ -100,12 +99,12 @@
// 4 - mc can require a passcode to connect
// 5 - debugger can take mc script profiler captures
enum ProtocolVersion {
_Unknown = 0,

Check warning on line 102 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `_Unknown` trimmed as `Unknown` must match one of the following formats: camelCase
Initial = 1,

Check warning on line 103 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `Initial` must match one of the following formats: camelCase
SupportTargetModuleUuid = 2,

Check warning on line 104 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `SupportTargetModuleUuid` must match one of the following formats: camelCase
SupportTargetSelection = 3,

Check warning on line 105 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `SupportTargetSelection` must match one of the following formats: camelCase
SupportPasscode = 4,

Check warning on line 106 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `SupportPasscode` must match one of the following formats: camelCase
SupportProfilerCaptures = 5,

Check warning on line 107 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `SupportProfilerCaptures` must match one of the following formats: camelCase
}

// capabilites based on protocol version
Expand Down Expand Up @@ -144,10 +143,10 @@

// external communication
private _homeViewProvider: HomeViewProvider;
private _statsProvider: StatsProvider2;
private _statsProvider: StatsProvider;
private _eventEmitter: EventEmitter;

public constructor(homeViewProvider: HomeViewProvider, statsProvider: StatsProvider2, eventEmitter: EventEmitter) {
public constructor(homeViewProvider: HomeViewProvider, statsProvider: StatsProvider, eventEmitter: EventEmitter) {
super();

this._homeViewProvider = homeViewProvider;
Expand Down Expand Up @@ -192,7 +191,7 @@
command: {
command: command,
dimension_type: 'overworld',
}
},
});
}
}
Expand All @@ -202,7 +201,7 @@
type: 'startProfiler',
profiler: {
target_module_uuid: this._targetModuleUuid,
}
},
});
}

Expand All @@ -212,7 +211,7 @@
profiler: {
captures_path: capturesBasePath,
target_module_uuid: this._targetModuleUuid,
}
},
});
}

Expand All @@ -231,10 +230,9 @@
this.showNotification(`Failed to write to temp file: ${err.message}`, LogLevel.Error);
return;
}
commands.executeCommand('vscode.open', Uri.file(captureFullPath))
.then(undefined, error => {
this.showNotification(`Failed to open CPU profile: ${error.message}`, LogLevel.Error);
});
commands.executeCommand('vscode.open', Uri.file(captureFullPath)).then(undefined, error => {
this.showNotification(`Failed to open CPU profile: ${error.message}`, LogLevel.Error);
});

// notify home view of new capture
this._eventEmitter.emit('new-profiler-capture', profilerCapture.capture_base_path, newCaptureFileName);
Expand Down Expand Up @@ -1026,14 +1024,17 @@
const reloadOnSourceChangesEnabled = config.get<boolean>('reloadOnSourceChanges.enabled');
const reloadOnSourceChangesDelay = Math.max(config.get<number>('reloadOnSourceChanges.delay') ?? 0, 0);
const reloadOnSourceChangesGlobPattern = config.get<string>('reloadOnSourceChanges.globPattern');

// watch all files within the workspace matching custom glob pattern.
// only active if Minecraft /reload is enabled
let globPattern: RelativePattern | undefined = undefined;
if (reloadOnSourceChangesGlobPattern && reloadOnSourceChangesEnabled) {
const workspaceFolders = workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
globPattern = new RelativePattern(workspaceFolders[0].uri.fsPath ?? '', reloadOnSourceChangesGlobPattern);
globPattern = new RelativePattern(
workspaceFolders[0].uri.fsPath ?? '',
reloadOnSourceChangesGlobPattern
);
}
}
// watch source map files and reload cache if changed.
Expand Down Expand Up @@ -1064,7 +1065,7 @@
}
}, reloadOnSourceChangesDelay);
};

if (this._sourceFileWatcher) {
this._sourceFileWatcher.onDidChange(onSourceChanged);
this._sourceFileWatcher.onDidCreate(onSourceChanged);
Expand Down
5 changes: 2 additions & 3 deletions src/Utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

// Copyright (C) Microsoft Corporation. All rights reserved.

import * as os from 'os';
Expand Down Expand Up @@ -28,7 +27,7 @@ describe('Utils', () => {
expect(normalizePathForRemote('C:\\path\\to\\file')).toBe('C:/path/to/file');
expect(normalizePathForRemote('C:/path/to/file')).toBe('C:/path/to/file');
});
});
});

describe('isUUID', () => {
it('should return true for valid UUIDs', () => {
Expand All @@ -40,5 +39,5 @@ describe('Utils', () => {
expect(isUUID('123e4567-e89b-12d3-a456-42661417400')).toBe(false); // One character short
expect(isUUID('123e4567-e89b-12d3-a456-4266141740000')).toBe(false); // One character too long
});
});
});
});
File renamed without changes.
78 changes: 50 additions & 28 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,45 @@

// Copyright (C) Microsoft Corporation. All rights reserved.

import * as vscode from 'vscode';
import { ConfigProvider } from './ConfigProvider';
import { ServerDebugAdapterFactory } from './ServerDebugAdapterFactory';
import { HomeViewProvider } from './panels/HomeViewProvider';
import { MinecraftDiagnosticsPanel } from './panels/MinecraftDiagnostics';
import { StatsProvider2 } from './StatsProvider2';
import { ConfigProvider } from './config-provider';
import { EventEmitter } from 'stream';
import { HomeViewProvider } from './panels/home-view-provider';
import { MinecraftDiagnosticsPanel } from './panels/minecraft-diagnostics';
import { ServerDebugAdapterFactory } from './server-debug-adapter-factory';
import { StatsProvider } from './stats/stats-provider';
import { ReplayStatsProvider } from './stats/replay-stats-provider';

// called when extension is activated
//
export function activate(context: vscode.ExtensionContext) {
const statsProvider = new StatsProvider2();
const liveStatsProvider = new StatsProvider('Live', 'minecraftDiagnosticsLive');
const eventEmitter: EventEmitter = new EventEmitter();

// home view
const homeViewProvider = new HomeViewProvider(context.extensionUri, eventEmitter);
context.subscriptions.push(vscode.window.registerWebviewViewProvider(HomeViewProvider.viewType, homeViewProvider));

// register commands
context.subscriptions.push(
vscode.commands.registerCommand('extension.minecraft-js.getPort', _config => {
return vscode.window.showInputBox({
placeHolder: 'Please enter the port Minecraft is listening on.',
value: '',
});
})
);

// register a configuration provider for the 'minecraft-js' debug type
const configProvider = new ConfigProvider();
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('minecraft-js', configProvider));

// register a debug adapter descriptor factory for 'minecraft-js', this factory creates the DebugSession
let descriptorFactory = new ServerDebugAdapterFactory(homeViewProvider, statsProvider, eventEmitter);
let descriptorFactory = new ServerDebugAdapterFactory(homeViewProvider, liveStatsProvider, eventEmitter);
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('minecraft-js', descriptorFactory));

if ('dispose' in descriptorFactory) {
context.subscriptions.push(descriptorFactory);
}

// Create the show diagnostics command
const showDiagnosticsCommand = vscode.commands.registerCommand(
'minecraft-debugger.showMinecraftDiagnostics',
() => {
MinecraftDiagnosticsPanel.render(context.extensionUri, statsProvider);
}
);
//
// Command Registrations
//

const getPortCommand = vscode.commands.registerCommand('extension.minecraft-js.getPort', () => {
return vscode.window.showInputBox({
placeHolder: 'Please enter the port Minecraft is listening on.',
value: '',
});
});

const minecraftReloadCommand = vscode.commands.registerCommand('minecraft-debugger.minecraftReload', () => {
if (!vscode.debug.activeDebugSession) {
Expand Down Expand Up @@ -76,8 +68,38 @@ export function activate(context: vscode.ExtensionContext) {
}
);

// Add command to the extension context
context.subscriptions.push(showDiagnosticsCommand, minecraftReloadCommand, runMinecraftCommand);
const liveDiagnosticsCommand = vscode.commands.registerCommand('minecraft-debugger.liveDiagnostics', () => {
MinecraftDiagnosticsPanel.render(context.extensionUri, liveStatsProvider);
});

const replayDiagnosticsCommand = vscode.commands.registerCommand(
'minecraft-debugger.replayDiagnostics',
async () => {
const fileUri = await vscode.window.showOpenDialog({
canSelectMany: false,
openLabel: 'Select diagnostics capture to replay',
filters: {
'MC Stats files': ['mcstats'],
'All files': ['*'],
},
});
if (!fileUri || fileUri.length === 0) {
vscode.window.showErrorMessage('No file selected.');
return;
}
const replayStats = new ReplayStatsProvider(fileUri[0].fsPath);
MinecraftDiagnosticsPanel.render(context.extensionUri, replayStats);
}
);

// Add commands to the extension context
context.subscriptions.push(
getPortCommand,
minecraftReloadCommand,
runMinecraftCommand,
liveDiagnosticsCommand,
replayDiagnosticsCommand
);
}

// called when extension is deactivated
Expand Down
File renamed without changes.
Loading
Loading