* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as vscode from 'vscode';
import * as childProcess from 'child_process';
import {
getAdbPath,
getBundleName,
getLibPath,
getWorkSpacePath,
KeelsDeviceManager,
} from './utils';
import { DevtoolsProvider } from './DevtoolsProvider';
import { resolve } from 'path';
export async function perfActivate(
context: vscode.ExtensionContext,
deviceManager: KeelsDeviceManager,
targetProvider: DevtoolsProvider,
) {
const provider = new PerfViewProvider(context.extensionUri, deviceManager, targetProvider);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(PerfViewProvider.viewType, provider),
);
provider.updateOption();
}
export class PerfViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'cpuperf';
private _view?: vscode.WebviewView;
private _keelsDeviceManager?: KeelsDeviceManager;
private _targetPrivider?: DevtoolsProvider;
constructor(
private readonly _extensionUri: vscode.Uri,
deviceManager: KeelsDeviceManager,
targetPrivider: DevtoolsProvider,
) {
this._keelsDeviceManager = deviceManager;
this._targetPrivider = targetPrivider;
}
delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
public resolveWebviewView(webviewView: vscode.WebviewView) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this._extensionUri],
};
const exeBin = process.platform === 'linux' ? '/devtools' : '\\devtools.exe';
const exePath = vscode.workspace.getConfiguration().get<string>('CJMP.sdkPath') + exeBin;
type SplitResult = {
prefix: string;
suffix?: string;
};
const splitByDash = (str: string): SplitResult => {
const index = str.indexOf('-');
if (index === -1) {
return { prefix: str };
}
return {
prefix: str.substring(0, index),
suffix: str.substring(index + 1),
};
};
let projectRoot = getWorkSpacePath();
const startTask = (message: any) => {
const result = splitByDash(message.device);
const platform = result.suffix?.toLowerCase();
const output = projectRoot?.fsPath;
const cmd =
exePath +
` cpu start --device ${result.prefix} --platform ${platform} --pid ${message.pid} --output ${output}`;
execNativeCommand(cmd);
};
const stopTask = async () => {
execNativeCommand(exePath + ` cpu stop`);
await this.delay(3000);
const htmlPath = vscode.Uri.joinPath(projectRoot!, 'perf.html');
this._targetPrivider?.showHtmlContent(htmlPath);
};
webviewView.webview.onDidReceiveMessage(async (message) => {
switch (message.command) {
case 'dropdownChanged':
break;
case 'refreshDropdown':
this.updateOption();
break;
case 'toggleTask':
if (message.state) {
startTask(message);
} else {
stopTask();
}
break;
}
});
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
}
public async updateOption() {
const devices = await this._keelsDeviceManager?.getKeelsDevices();
const deivcesNames: string[] = [];
const apps: string[] = [];
const pids: string[] = [];
if (!devices) {
return;
}
for (let device of devices) {
deivcesNames.push(device.id + '-' + device.platform);
}
const bundleName = await getBundleName();
apps.push(bundleName);
let pid = await getAndroidPid(bundleName);
pids.push(pid);
this.updateDropdownOptions(deivcesNames, apps, pids);
}
private _getHtmlForWebview(webview: vscode.Webview): string {
return `
<!DOCTYPE html>
<html>
<head>
<style>
.dropdown-container { padding: 10px; }
select { width: 100%; padding: 5px; margin-bottom: 10px; }
#refreshButton {
padding: 5px 12px;
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
cursor: pointer;
}
#refreshButton:hover {
background-color: var(--vscode-button-hoverBackground);
}
#statusButton {
padding: 5px 12px;
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
cursor: pointer;
}
#statusButton:hover {
background-color: var(--vscode-button-secondaryBackground);
}
</style>
</head>
<body>
<div class="dropdown-container">
<select id="myDropdown">
<option value="option1">device</option>
</select>
<select id="configApp">
<option value="fast">app</option>
</select>
<select id="configPid">
<option value="fast">pid</option>
</select>
<button id="refreshButton">refresh</button>
<button id="statusButton">start record</button>
</div>
<script>
const vscode = acquireVsCodeApi();
const dropdown = document.getElementById('myDropdown');
const refreshButton = document.getElementById('refreshButton');
const statusButton = document.getElementById('statusButton');
const configApp = document.getElementById('configApp');
const configPid = document.getElementById('configPid');
let isRunning = false;
dropdown.addEventListener('change', (event) => {
vscode.postMessage({
command: 'dropdownChanged',
device: dropdown.value,
app: configApp.value,
pid: configPid.value
});
});
configApp.addEventListener('change', (event) => {
vscode.postMessage({
command: 'dropdownChanged',
device: dropdown.value,
app: configApp.value,
pid: configPid.value
});
});
configPid.addEventListener('change',(event) => {
vscode.postMessage({
command: 'dropdownChanged',
device: dropdown.value,
app: configApp.value,
pid: configPid.value
});
});
refreshButton.addEventListener('click', () => {
vscode.postMessage({
command: 'refreshDropdown'
});
});
statusButton.addEventListener('click', () => {
isRunning = !isRunning;
statusButton.textContent = isRunning ? 'stop record' : 'start record';
statusButton.classList.toggle('running', isRunning);
vscode.postMessage({
command: 'toggleTask',
state: isRunning,
device: dropdown.value,
app: configApp.value,
pid: configPid.value
});
});
window.addEventListener('message', (event) => {
if (event.data.command === 'updateOptions') {
dropdown.innerHTML = event.data.options1
.map(opt => \`<option value="\${opt}">\${opt}</option>\`)
.join('');
configApp.innerHTML = event.data.options2
.map(opt => \`<option value="\${opt}">\${opt}</option>\`)
.join('');
configPid.innerHTML = event.data.options3
.map(opt => \`<option value="\${opt}">\${opt}</option>\`)
.join('');
}
});
</script>
</body>
</html>
`;
}
public updateDropdownOptions(options1: string[], options2: string[], options3: string[]) {
if (this._view) {
this._view.webview.postMessage({
command: 'updateOptions',
options1: options1,
options2: options2,
options3: options3,
});
}
}
}
export async function getAndroidPid(bundleName: string): Promise<string> {
let attemptCount = 0;
let result;
while (attemptCount < 10) {
const adbExe = getAdbPath();
if (!adbExe) {
break;
}
const shellCmd = process.platform === 'win32' ?
`${adbExe} shell ps -ef | findstr ${bundleName}` :
`${adbExe} shell ps -ef | grep ${bundleName}`;
let cmdResultInfo = await execNativeCommand(shellCmd);
const lines = cmdResultInfo.split('\n');
lines.forEach((line) => {
const parts = line.trim().split(/\s+/);
let minLength = 4;
let bundleNameId = 7;
if (parts.length >= minLength && parts[bundleNameId].toLowerCase() === bundleName) {
result = parts[1];
}
});
if (result) {
break;
}
if (attemptCount < 10) {
vscode.window.showInformationMessage(
`Can not find a target process id for ${bundleName} for ${attemptCount} times, retrying...`,
);
}
attemptCount++;
}
if (result) {
vscode.window.showInformationMessage(`process Id ${result} is found.`);
return result;
} else {
vscode.window.showInformationMessage(`failed to locate a process id`);
return '';
}
}
export function execNativeCommand(cmd: string): Promise<string> {
return new Promise((resolve, reject) => {
childProcess.exec(
cmd,
(error: any, stdout: string | PromiseLike<string>, stderr: string | any[]) => {
if (error) {
reject(error);
return;
}
if (stderr && stderr.length > 0) {
reject(stderr);
return;
}
resolve(stdout);
},
);
});
}