Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
661 changes: 133 additions & 528 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@types/request": "^2.48.8",
"@types/semver": "^7.3.9",
"@types/sinon": "^10.0.6",
"@types/ws": "^8.18.1",
"@types/yargs": "^15.0.5",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
Expand Down Expand Up @@ -121,7 +122,7 @@
"smart-buffer": "^4.2.0",
"source-map": "^0.7.4",
"telnet-client": "^1.4.9",
"xml2js": "^0.5.0",
"ws": "^8.18.3",
"yargs": "^16.2.0"
}
}
18 changes: 18 additions & 0 deletions src/LaunchConfiguration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FileEntry } from 'roku-deploy';

Check failure on line 1 in src/LaunchConfiguration.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

Cannot find module 'roku-deploy' or its corresponding type declarations.
import type { DebugProtocol } from '@vscode/debugprotocol';
import type { LogLevel } from './logging';

Expand Down Expand Up @@ -215,6 +215,24 @@
*/
injectRdbOnDeviceComponent: boolean;

/**
* Configuration for profiling functionality
*/
profiling?: {
/**
* Whether profiling is enabled
*/
enabled?: boolean;
/**
* Directory where profile files should be stored
*/
dir?: string;
/**
* The name of the profile file. Can include variables like ${appTitle} and ${timestamp}
*/
filename?: string;
};

/**
* Base path to the folder containing RDB files for OnDeviceComponent
*/
Expand Down
114 changes: 114 additions & 0 deletions src/PerfettoManager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// import { expect } from "chai";
// import { PerfettoControls } from "./PerfettoManager";
// import * as sinonActual from "sinon";
// let sinon = sinonActual.createSandbox();

// describe("PerfettoControls", () => {
// let fetchStub: sinon.SinonStub;
// let perfetto: PerfettoControls;

// beforeEach(() => {
// fetchStub = sinon.stub(global as any, "fetch");
// perfetto = new PerfettoControls("192.168.1.5");
// });

// afterEach(() => {
// sinon.restore();
// });

// describe("constructor", () => {
// it("should initialize host and default standardResponse", () => {
// expect(perfetto.host).to.equal("192.168.1.5");
// expect(perfetto.standardResponse).to.deep.equal({
// message: "",
// error: false,
// });
// });
// });

// describe("startTracing", () => {
// it("should call enable and start endpoints and return success response", async () => {
// fetchStub.resolves({ ok: true } as any);

// const response = await perfetto.startTracing();

// expect(fetchStub.calledTwice).to.be.true;

// expect(fetchStub.firstCall.args[0]).to.equal(
// "http://192.168.1.5:8060/perfetto/enable/dev"
// );
// expect(fetchStub.secondCall.args[0]).to.equal(
// "http://192.168.1.5:8060/perfetto/start/dev"
// );

// expect(response).to.deep.equal({
// message: "Traceing started successfully",
// error: false,
// });
// });

// it("should set error response when enable/start fails", async () => {
// fetchStub.rejects(new Error("Network error"));

// const response = await perfetto.startTracing("");

// expect(fetchStub.calledOnce).to.be.true;
// expect(response.error).to.be.true;
// expect(response.message).to.contain("Error fetching channels");
// });
// });

// describe("stopTracing", () => {
// it("should stop tracing and return success response when request succeeds", async () => {
// fetchStub.resolves({ ok: true } as any);

// const response = await perfetto.stopTracing("");

// expect(fetchStub.calledOnce).to.be.true;
// expect(fetchStub.firstCall.args[0]).to.equal(
// "http://192.168.1.5:8060/perfetto/stop/dev"
// );

// expect(response).to.deep.equal({
// message: "Traceing stopped successfully",
// error: false,
// });
// });

// it("should return error response when stop request fails", async () => {
// fetchStub.resolves(null as any);

// const response = await perfetto.stopTracing("");

// expect(response).to.deep.equal({
// message: "Error stopping tracing",
// error: true,
// });
// });
// });

// describe("ecpGetPost (indirect)", () => {
// it("should send POST request with correct headers and body", async () => {
// fetchStub.resolves({ ok: true } as any);

// await perfetto["ecpGetPost"]("/test", "post", "<xml />");

// const [, options] = fetchStub.firstCall.args;

// expect(options.method).to.equal("POST");
// expect(options.body).to.equal("<xml />");
// expect(options.headers["Content-Type"]).to.equal("text/xml");
// });

// it("should send GET request when method is get", async () => {
// fetchStub.resolves({ ok: true } as any);

// await perfetto["ecpGetPost"]("/test", "get", "");

// const [, options] = fetchStub.firstCall.args;

// expect(options.method).to.equal("GET");
// expect(options.body).to.be.undefined;
// });
// });
// });
172 changes: 172 additions & 0 deletions src/PerfettoManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import * as fs from "fs";
import * as path from "path";
import * as fsExtra from "fs-extra";
import { WebSocket } from "ws";
import { util } from "./util";

export class PerfettoManager {
private port: number = 8060;
private selectedChannel: string = "dev";
private ws: WebSocket | null;
public perfettoConfig: {
[key: string]: any;
};
private config: Record<string, unknown>;

constructor(public host: string) {
this.host = host;
this.setConfigurations()
}

private setConfigurations(): void {
// this.config = util?._debugSession?.getPerfettoConfig()
}

public async startTracing(): Promise<{ error?: string }> {
await this.enableTracing();
try {
const tracesDir = path.join("/home/jyoti/Downloads/updated-DCl/dcl", "perfetto");
fsExtra.ensureDirSync(tracesDir);

const filename = path.join(tracesDir, "trace.perfetto-trace");

await this.wsSaveTrace("/perfetto-session", filename);

return {
error: JSON.stringify(this.perfettoConfig)
}
} catch (error) {
return {
error: `Error starting Perfetto tracing: ${error}`,
}
}
}

public async stopTracing(): Promise<{ error?: string }> {
if (!this.ws) {
return {
error: 'No active Perfetto tracing session to stop.'
}
}
this.ws = null;
}

public async enableTracing(): Promise<{ error?: string }> {
try {
await this.ecpGetPost(`/perfetto/enable/${this.selectedChannel}`, "post", "");
} catch (error) {
return {
error: `Error enabling Perfetto tracing: ${error}`,
}
}
}

private async ecpGetPost(
route: string,
method: "post" | "get" = "get",
body: string
): Promise<Response> {
const url = `http://${this.host}:${this.port}${route}`;

if (method === "post") {
return fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: body,
});
} else {
return fetch(url, {
method: "GET",
headers: {
"Content-Type": "text/xml",
},
});
}
}

private async wsSaveTrace(urlpath: string, filename: string): Promise<(() => void) | null> {
const url = `ws://${this.host}:8060${urlpath}`;
this.ws = new WebSocket(url);

// Ensure directory exists
fs.mkdirSync(path.dirname(filename), { recursive: true });

// Create write stream in append mode
const out = fs.createWriteStream(filename, { flags: "a" });

out.on("error", (err) => {
console.error("File write error:", err);
process.exit(1);
});

// Ping configuration
const PING_INTERVAL_MS = 30000;
let pingTimer: NodeJS.Timeout | null = null;

this.ws.on("open", () => {
console.log("WebSocket connected:", url);

pingTimer = setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
try {
this.ws.ping();
} catch (error) {
// Silent catch for ping errors
}
}
}, PING_INTERVAL_MS);
});

this.ws.on("message", (data: Buffer, isBinary: boolean) => {
console.log("Message receiving, binary:", isBinary);

// Only process binary data
if (!isBinary) return;

// Handle backpressure when writing to file
if (!out.write(data)) {
this.ws?.pause();
out.once("drain", () => {
this.ws?.resume();
});
}
});

this.ws.on("error", (err: Error) => {
console.error("WebSocket error:", err);
});

this.ws.on("close", (code: number, reason: string) => {
if (pingTimer) {
clearInterval(pingTimer);
pingTimer = null;
}

console.log(`WebSocket closed. Code: ${code}, Reason: ${reason}`);
out.end();
});

// Graceful shutdown handler
const shutdown = (): string => {
console.log("Shutting down...");

if (pingTimer) {
clearInterval(pingTimer);
pingTimer = null;
}

try {
this.ws?.close();
} catch {
console.log("WebSocket already closed.");
}

out.end();
return filename;
};

return shutdown;
}
}
3 changes: 0 additions & 3 deletions src/SceneGraphDebugCommandController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { logger } from './logging';

// eslint-disable-next-line
const Telnet = require('telnet-client');

Expand All @@ -16,7 +15,6 @@ export class SceneGraphDebugCommandController {
public execTimeout = 2000;
private port;
private maxBufferLength = 5242880;

private logger = logger.createLogger(`[${SceneGraphDebugCommandController.name}]`);

public async connect(options: { execTimeout?: number; timeout?: number } = {}) {
Expand Down Expand Up @@ -299,7 +297,6 @@ export class SceneGraphDebugCommandController {
}
}


/**
* Returns a simple starting object used for responses
* @private
Expand Down
Loading
Loading