Add developer guide for vscode-trace-extension#355
Conversation
| setHandleTraceResourceType(handleFiles: boolean, handleFolders: boolean): void; | ||
| onSignalManagerSignal(event: K extends SignalType, listener: (...args: [...SignalArgs<Signals[K]>]) => void | Promise<void>): void; | ||
| offSignalManagerSignal(event: K extends SignalType, listener: (...args: [...SignalArgs<Signals[K]>]) => void | Promise<void>): void; | ||
| registerTimeGraphMenuSignal(menuId: string, menuLabel: string): void; |
There was a problem hiding this comment.
registerTimeGraphMenuSignal is not available.
There was a problem hiding this comment.
That was made up by AmazonQ. I'll provide the steps to achieve the registration.
| vscode.window.showInformationMessage(`Trace opened: ${experiment.name}`); | ||
| }; | ||
|
|
||
| const onExperimentClosed = (experiment: any) => { |
There was a problem hiding this comment.
This method did not work when experiment is closed
There was a problem hiding this comment.
Ok. It might be that this particular signal is not propagated to the vscode-extension and just stays inside the webviews... I need to confirm. For the doc I'll use selection changed signal instead.
| } | ||
| }; | ||
|
|
||
| traceAPI.onSignalManagerSignal('EXPERIMENT_OPENED', onExperimentOpened); |
There was a problem hiding this comment.
Among the three signals only EXPERIMENT_OPENED that works, I also tried SELECTION_RANGE_UPDATED' and it works. Here is the code I added.
// Listen for range selcetion
const onSelcetionRange = (data: any) => {
outputChannel.appendLine('Selection Range..............');
outputChannel.appendLine(`Selection data: ${JSON.stringify(data)}`);
if (data && data.experimentUUID && data.timeRange) {
const experimentUUID = data.experimentUUID;
const start = data.timeRange.start;
const end = data.timeRange.end;
outputChannel.appendLine(`Selected range - UUID: ${experimentUUID}, Start: ${start}, End: ${end}`);
// Call your custom analysis with the selected range
if (!webviewProvider) {
vscode.window.showErrorMessage('Webview provider not initialized');
return;
}
webviewProvider.updateSelectionRange(data.experimentUUID, data.timeRange);
};
}
traceAPI.onSignalManagerSignal('SELECTION_RANGE_UPDATED', onSelcetionRange);
There was a problem hiding this comment.
Only certain signals are propagated to the extensions of vscode. I'll add a comment.
| }); | ||
| } | ||
|
|
||
| function addTraceServerContributor() { |
There was a problem hiding this comment.
Yes, the custom logic is what adopters have to implement for their use cases
|
|
||
| **Creating Extended TSP Client** | ||
|
|
||
| **src/extended-tsp-client.ts** |
There was a problem hiding this comment.
Since this code can be customized for user needs. I decided to use HttpTspClient which calls trace server.
import {
HttpTspClient,
GenericResponse,
TspClientResponse,
EntryModel,
Experiment,
OutputDescriptor,
Query
} from 'tsp-typescript-client';
import { DataTreeEntry } from "tsp-typescript-client/lib/models/data-tree";
export class ExtendedTsp extends HttpTspClient {
constructor(baseUrl: string) {
super(baseUrl);
}
// Method to get tree
async getTree(expUUID: string, outputID: string, parameters: any): Promise<TspClientResponse<GenericResponse<EntryModel<DataTreeEntry>>>> {
return this.fetchTimeGraphTree(expUUID, outputID, parameters);
//Add custom code
}
async removeExperiment(expUUID: string): Promise<TspClientResponse<Experiment>> {
//Add custom code
return this.deleteExperiment(expUUID);
}
async getOutputIDS(expUUID: string): Promise<TspClientResponse<OutputDescriptor[]>> {
//Add custom code
return this.experimentOutputs(expUUID);
}
async getStates(experimentUUID: string, outputId: string, parameters: Query): Promise<any | undefined> {
//Add custom code
return this.fetchTimeGraphStates(experimentUUID, outputId, parameters);
}
}
There was a problem hiding this comment.
Thanks for the suggestion. I'll update the generated code so that the back-end calls to a custom (non-TSP) endpoint is correct. It will return 404 for the default Trace Compass server because it doesn't implement the custom endpoints. Only a custom server with those endpoints would reply 200 with the corresponding data. I think it's ok what we have to demonstrate the procedure to add a custom endpoint.
Yibhir0
left a comment
There was a problem hiding this comment.
Let me know if you want me to add something or use different approach.
|
|
||
| **src/tsp-service.ts** | ||
| ```typescript | ||
| import { ExtendedTspClient, CustomAnalysisRequest } from './extended-tsp-client'; |
There was a problem hiding this comment.
Further I used this code inside tsp-service.ts
import { ExtendedTsp } from "./extended-tsp-1-client";
import { Query } from 'tsp-typescript-client';
export class TspService {
private client: ExtendedTsp;
private count: number;
private result: any;
constructor(baseUrl: string = 'http://localhost:8080/tsp/api') {
this.client = new ExtendedTsp(baseUrl);
this.count = 0;
this.result = [];
}
async getAvailableOutputIds(experimentUUID: string): Promise<any | undefined> {
try {
const response = await this.client.getOutputIDS(experimentUUID);
if (response.isOk()) {
const outputs = response.getModel();
if (outputs) {
return outputs
}
}
else {
console.error('Failed to get OutputID:', response.getStatusMessage());
return undefined;
}
return undefined;
}
catch (error) {
console.error('Error starting custom analysis:', error);
return undefined;
}
}
private async pollAnalysisStatus(experimentUUID: string, outputId: string, start: bigint, end: bigint, entryId: string): Promise<any | undefined> {
try {
const parameters = new Query({
'requested_times': [start, end],
'requested_items': [entryId]
});
const response = await this.client.getStates(experimentUUID, outputId, parameters);
if (response.isOk()) {
const states = response.getModel()?.model.rows;
if (states) {
return states[0].states;
}
}
return undefined;
}
catch (error) {
console.error('Error polling analysis status:', error);
return undefined;
}
}
async getTreeResult(experimentUUID: string, outputId: string, start: bigint, end: bigint): Promise<any | undefined> {
try {
const parameters = new Query({
'requested_times': [start, end],
'requested_items': []
});
const treeResponse = await this.client.getTree(experimentUUID, outputId, parameters);
if (treeResponse.isOk()) {
const entries = treeResponse.getModel()?.model.entries;
return entries;
}
return undefined;
}
catch (error) {
console.error('Error polling analysis status:', error);
return undefined
}
}
// Gets 10 first states
async getSomeTimeGraphStates(experimentUUID: string, outputId: string, start: bigint, end: bigint): Promise<{ status: string, results?: any } | undefined> {
if (this.count == 3) {
this.count = 0;
return {
status: "COMPLETED",
results: this.result
};
}
try {
const entries = await this.getTreeResult(experimentUUID, outputId, start, end);
if (entries) {
const entry = entries[this.count];
const state = await this.pollAnalysisStatus(experimentUUID, outputId, entry.start, entry.end, entry.id);
if (state) {
this.result.push(state);
this.count++;
return {
status: "RUNNING",
results: this.result
}
}
}
return {
status: "FAILED"
}
}
catch (err) {
return {
status: "FAILED"
}
}
}
}
There was a problem hiding this comment.
Thanks. I'll update it accordingly as mentioned earlier.
| // ... rest of activation code ... | ||
| } | ||
|
|
||
| async function runCustomAnalysis() { |
There was a problem hiding this comment.
This is the modified version of runCustomAnalysis()
async function runCustomAnalysis() {
if (!traceAPI || !tspService) return;
const activeExperiment = traceAPI.getActiveExperiment();
if (!activeExperiment) {
vscode.window.showWarningMessage('No active trace for analysis');
return;
}
try {
const outputId = "org.eclipse.tracecompass.internal.tmf.core.statesystem.provider.StateSystemDataProvider";
const start = activeExperiment.start;
const end = activeExperiment.end;
let result;
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: 'Running Custom TSP Analysis',
cancellable: true
}, async (progress, token) => {
let completed = false;
let attempts = 0;
const maxAttempts = 30;
while (!completed && attempts < maxAttempts && !token.isCancellationRequested) {
result = await tspService.getSomeTimeGraphStates(activeExperiment.UUID, outputId, start, end);
const status = result?.status;
if (status) {
progress.report({
increment: (attempts / maxAttempts) * 100,
message: `Status: ${status}`
});
if (status == "FAILED") {
vscode.window.showErrorMessage('Analysis failed');
break;
}
else if (status == "COMPLETED") {
outputChannel.appendLine(`Analysis status: ${JSON.stringify(status)}`);
outputChannel.appendLine(`Number of statess: ${JSON.stringify(result?.results.length)}`);
completed = true;
}
}
}
if (!completed) {
await new Promise(resolve => setTimeout(resolve, 1000));
attempts++;
}
});
} catch (error) {
console.error('Error running custom analysis:', error);
vscode.window.showErrorMessage('Failed to run custom analysis');
}
}
| }, | ||
| "dependencies": { | ||
| "tsp-typescript-client": "^0.4.0", | ||
| "@vscode/messenger": "^0.4.5" |
There was a problem hiding this comment.
@vscode/messenger dependency can be removed. I used a built-in messaging functionalities.
There was a problem hiding this comment.
That was incorrectly added by Amazon Q. I asked to use www.npmjs.com/package/vscode-messenger, but I guess my prompt was not clear enough. I'll update the examples accordingly and reference this dependency.
| "dependencies": { | ||
| "tsp-typescript-client": "^0.4.0", | ||
| "@vscode/messenger": "^0.4.5" | ||
| } |
There was a problem hiding this comment.
This is the update version of webview-provider.ts.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomTraceViewProvider = void 0;
const vscode = require("vscode");
class CustomTraceViewProvider {
constructor(context, tspService, traceAPI, outputChannel) {
this.context = context;
this.tspService = tspService;
this.traceAPI = traceAPI;
}
async createWebview() {
this.panel = vscode.window.createWebviewPanel('customTraceView', 'Range Display And Analysis', vscode.ViewColumn.Two, {
enableScripts: true,
retainContextWhenHidden: true
});
this.panel.webview.html = this.getWebviewContent();
this.setupMessageHandling();
this.customizeExistingViews();
}
setupMessageHandling() {
if (!this.panel)
return;
this.panel.webview.onDidReceiveMessage(async (message) => {
if (message.command === 'runAnalysis') {
await this.runAnalysis();
}
});
}
async runAnalysis() {
const activeExperiment = this.traceAPI?.getActiveExperiment();
if (!activeExperiment)
return;
const outputId = "org.eclipse.tracecompass.internal.tmf.core.statesystem.provider.StateSystemDataProvider";
let completed = false;
this.panel?.webview.postMessage({
command: 'analysisStatus',
status: 'RUNNING'
});
while (!completed) {
const result = await this.tspService.getSomeTimeGraphStates(activeExperiment.UUID, outputId, BigInt(activeExperiment.start), BigInt(activeExperiment.end));
if (result?.status === 'COMPLETED') {
completed = true;
this.panel?.webview.postMessage({
command: 'analysisStatus',
status: 'COMPLETED',
});
}
else if (result?.status === 'FAILED') {
completed = true;
this.panel?.webview.postMessage({
command: 'analysisStatus',
status: 'FAILED'
});
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
updateSelectionRange(experimentUUID, timeRange) {
this.panel?.webview.postMessage({
command: 'selectionRangeUpdated',
data: timeRange,
id: experimentUUID
});
}
customizeExistingViews() {
const activeWebviews = this.traceAPI.getActiveWebviews();
const activeWebviewPanels = this.traceAPI.getActiveWebviewPanels();
activeWebviews.forEach(webview => {
this.injectRangeHandler(webview.webview);
});
Object.values(activeWebviewPanels).forEach(panel => {
if (panel && panel.webview) {
this.injectRangeHandler(panel.webview);
}
});
}
injectRangeHandler(webview) {
webview.onDidReceiveMessage((message) => {
if (message.method === 'selectionRangeUpdated') {
const params = JSON.parse(message.params);
this.displayRangeData(params.timeRange);
}
});
}
displayRangeData(timeRange) {
this.panel?.webview.postMessage({
command: 'rangeDataUpdated',
data: timeRange
});
}
getWebviewContent() {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Range Display</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.section { border: 1px solid #ccc; padding: 15px; margin: 10px 0; border-radius: 5px; }
button { padding: 8px 16px; cursor: pointer; }
</style>
</head>
<body>
<div class="section">
<h3>Analysis</h3>
<button onclick="runAnalysis()">Run Analysis</button>
<div id="analysis-status">Ready</div>
</div>
<div class="section">
<h3>Selection Range</h3>
<div id="selection-info">No selection</div>
</div>
<script>
const vscode = acquireVsCodeApi();
window.addEventListener('message', event => {
const message = event.data;
if (message.command === 'selectionRangeUpdated') {
document.getElementById('selection-info').innerHTML =
'Start: ' + message.data.start + '<br>End: ' + message.data.end;
} else if (message.command === 'rangeDataUpdated') {
document.getElementById('selection-info').innerHTML =
'Start: ' + message.data.start + '<br>End: ' + message.data.end;
} else if (message.command === 'analysisStatus') {
document.getElementById('analysis-status').innerHTML = message.status;
}
});
window.runAnalysis = function() {
vscode.postMessage({ command: 'runAnalysis' });
};
</script>
</body>
</html>`;
}
}
exports.CustomTraceViewProvider = CustomTraceViewProvider;
There was a problem hiding this comment.
Thanks. I'll provide an updated version as well.
| // ... existing code ... | ||
|
|
||
| // Initialize webview provider | ||
| webviewProvider = new CustomTraceViewProvider(context, tspService); |
There was a problem hiding this comment.
this needs to be initialized at last
webviewProvider = new CustomTraceViewProvider(context, tspService, traceAPI, outputChannel);
| return; | ||
| } | ||
|
|
||
| await webviewProvider.createWebview(activeExperiment.UUID, activeExperiment.name); |
There was a problem hiding this comment.
function openCustomView() {
if (!webviewProvider) {
vscode.window.showErrorMessage('Webview provider not initialized');
return;
}
await webviewProvider.webviewProvider.createWebview();
}
8b0441c to
6539cc8
Compare
Remove section in README.md with reference to developer guide. Generated using Amazon Q Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
- This custom extension interfaces the vscode-trace-extension. - It also explains how to extend the tsp-typescript-client for custom endpoints not part of TSP Updated DEVELOPER-GUIDE.md Generated using Amazon Q Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Updated DEVELOPER-GUIDE.md Generated using Amazon Q Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Generated using Amazon Q Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Generated using Amazon Q Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Generated using Amazon Q Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
- Generated code by Amazon Q was in many case incorrect. Update the code examples to working examples - Add picture of custom view created by code examples - Add notes on examples in respect of purpose and limitations Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
6539cc8 to
249399e
Compare
MatthewKhouzam
left a comment
There was a problem hiding this comment.
Improvements, thanks!
What it does
Initial version of guide was generated using Amazon Q. Then it was manually updated to clean-up and update developer guide to fix generated code examples:
How to test
Follow-ups
Review checklist