Skip to content

Commit

Permalink
Drive usage based on launch config setting instead of device.
Browse files Browse the repository at this point in the history
Move rendezvous events out of adapters.
  • Loading branch information
TwitchBronBron committed Jun 30, 2023
1 parent bfaef31 commit 150434d
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 62 deletions.
13 changes: 7 additions & 6 deletions src/LaunchConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ export interface LaunchConfiguration extends DebugProtocol.LaunchRequestArgument
* Show variables that are prefixed with a special prefix designated to be hidden
*/
showHiddenVariables: boolean;

/**
* If true: turn on ECP rendezvous tracking, or turn on 8080 rendezvous tracking if ECP unsupported
* If false, turn off both.
* @default true
*/
rendezvousTracking: boolean;
}

export interface ComponentLibraryConfiguration {
Expand Down Expand Up @@ -287,10 +294,4 @@ export interface ComponentLibraryConfiguration {
* This is an absolute path to the TrackerTask.xml file to be injected into the component library during a debug session.
*/
raleTrackerTaskFileLocation: string;
/**
* If true, turn on ECP tracking, unless unsupported, then turn on 8080 tracking.
* If false, turn off both.
* TODO - this isn't actually implemented yet
*/
rendezvousTracking: boolean;
}
29 changes: 21 additions & 8 deletions src/RendezvousTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,22 +266,27 @@ describe('BrightScriptFileUtils ', () => {

});

afterEach(() => {
afterEach(async () => {
sinon.restore();
rendezvousTrackerMock.restore();
rendezvousTracker?.destroy();

//prevent hitting the network during teardown
rendezvousTracker.toggleEcpRendezvousTracking = () => Promise.resolve() as any;
rendezvousTracker['runSGLogrendezvousCommand'] = () => Promise.resolve() as any;

await rendezvousTracker?.destroy();
});

describe('isEcpRendezvousTrackingSupported ', () => {
it('works', () => {
rendezvousTracker['deviceInfo']['software-version'] = '11.0.0';
expect(rendezvousTracker.isEcpRendezvousTrackingSupported).to.be.false;
expect(rendezvousTracker.doesHostSupportEcpRendezvousTracking).to.be.false;

rendezvousTracker['deviceInfo']['software-version'] = '11.5.0';
expect(rendezvousTracker.isEcpRendezvousTrackingSupported).to.be.true;
expect(rendezvousTracker.doesHostSupportEcpRendezvousTracking).to.be.true;

rendezvousTracker['deviceInfo']['software-version'] = '12.0.1';
expect(rendezvousTracker.isEcpRendezvousTrackingSupported).to.be.true;
expect(rendezvousTracker.doesHostSupportEcpRendezvousTracking).to.be.true;
});
});

Expand Down Expand Up @@ -369,33 +374,38 @@ describe('BrightScriptFileUtils ', () => {
});

it('does not activate if telnet and ecp are both off', async () => {
sinon.stub(rendezvousTracker as any, 'runSGLogrendezvousCommand').returns(Promise.resolve(''));
sinon.stub(rendezvousTracker, 'getIsEcpRendezvousTrackingEnabled').returns(Promise.resolve(false));
sinon.stub(rendezvousTracker, 'getIsTelnetRendezvousTrackingEnabled').returns(Promise.resolve(false));
expect(
await rendezvousTracker.activateEcpTracking()
await rendezvousTracker.activate()
).to.be.false;
});

it('activates if telnet is enabled but ecp is disabled', async () => {
sinon.stub(rendezvousTracker as any, 'runSGLogrendezvousCommand').returns(Promise.resolve(''));
sinon.stub(rendezvousTracker, 'getIsEcpRendezvousTrackingEnabled').returns(Promise.resolve(false));
sinon.stub(rendezvousTracker, 'getIsTelnetRendezvousTrackingEnabled').returns(Promise.resolve(true));
expect(
await rendezvousTracker.activateEcpTracking()
await rendezvousTracker.activate()
).to.be.true;
});

it('activates if telnet is disabled but ecp is enabled', async () => {
sinon.stub(rendezvousTracker as any, 'runSGLogrendezvousCommand').returns(Promise.resolve(''));
sinon.stub(rendezvousTracker, 'getIsEcpRendezvousTrackingEnabled').returns(Promise.resolve(true));
sinon.stub(rendezvousTracker, 'getIsTelnetRendezvousTrackingEnabled').returns(Promise.resolve(false));
expect(
await rendezvousTracker.activateEcpTracking()
await rendezvousTracker.activate()
).to.be.true;
});
});

describe('processLog ', () => {
it('filters out all rendezvous log lines', async () => {
rendezvousTrackerMock.expects('emit').withArgs('rendezvous').once();
rendezvousTracker['trackingSource'] = 'telnet';

let expected = `channel: Start\nStarting data processing\nData processing completed\n`;
assert.equal(await rendezvousTracker.processLog(logString), expected);
assert.deepEqual(rendezvousTracker.getRendezvousHistory, expectedHistory);
Expand All @@ -405,6 +415,8 @@ describe('BrightScriptFileUtils ', () => {
it('does not filter out rendezvous log lines', async () => {
rendezvousTrackerMock.expects('emit').withArgs('rendezvous').once();
rendezvousTracker.setConsoleOutput('full');
rendezvousTracker['trackingSource'] = 'telnet';

assert.equal(await rendezvousTracker.processLog(logString), logString);
assert.deepEqual(rendezvousTracker.getRendezvousHistory, expectedHistory);
rendezvousTrackerMock.verify();
Expand Down Expand Up @@ -434,6 +446,7 @@ describe('BrightScriptFileUtils ', () => {
it('to reset the history data', async () => {
rendezvousTrackerMock.expects('emit').withArgs('rendezvous').twice();
let expected = `channel: Start\nStarting data processing\nData processing completed\n`;
rendezvousTracker['trackingSource'] = 'telnet';
assert.equal(await rendezvousTracker.processLog(logString), expected);
assert.deepEqual(rendezvousTracker.getRendezvousHistory, expectedHistory);

Expand Down
77 changes: 52 additions & 25 deletions src/RendezvousTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ export class RendezvousTracker {
private filterOutLogs: boolean;
private rendezvousBlocks: RendezvousBlocks;
private rendezvousHistory: RendezvousHistory;
private ecpTrackingEnabled = false;

/**
* Where should the rendezvous data be tracked from? If ecp, then the ecp ping data will be reported. If telnet, then any
* rendezvous data from telnet will reported. If 'off', then no data will be reported
*/
private trackingSource: 'telnet' | 'ecp' | 'off' = 'off';

/**
* Determine if the current Roku device supports the ECP rendezvous tracking feature
*/
public get isEcpRendezvousTrackingSupported() {
public get doesHostSupportEcpRendezvousTracking() {
return semver.gte(this.deviceInfo['software-version'] as string, '11.5.0');
}

Expand Down Expand Up @@ -118,13 +123,18 @@ export class RendezvousTracker {
* Determine if rendezvous tracking is enabled via the 8080 telnet command
*/
public async getIsTelnetRendezvousTrackingEnabled() {
let host = this.deviceInfo.host as string;
let sgDebugCommandController = new SceneGraphDebugCommandController(host);
return (await this.runSGLogrendezvousCommand('status'))?.trim()?.toLowerCase() === 'on';
}

/**
* Run a SceneGraph logendezvous 8080 command and get the text output
*/
private async runSGLogrendezvousCommand(command: 'status' | 'on' | 'off'): Promise<string> {
let sgDebugCommandController = new SceneGraphDebugCommandController(this.deviceInfo.host as string);
try {
let logRendezvousResponse = await sgDebugCommandController.logrendezvous('status');
return logRendezvousResponse.result.rawResponse?.trim()?.toLowerCase() === 'on';
return (await sgDebugCommandController.logrendezvous(command)).result.rawResponse;
} catch (error) {
this.logger.warn('An error occurred getting logRendezvous');
this.logger.warn(`An error occurred running SG command "${command}"`, error);
} finally {
await sgDebugCommandController.end();
}
Expand All @@ -138,27 +148,32 @@ export class RendezvousTracker {
return ecpData.trackingEnabled;
}

public async activateEcpTracking(): Promise<boolean> {
//ECP tracking not supported, return early
if (!this.isEcpRendezvousTrackingSupported) {
return;
}

let isTelnetRendezvousTrackingEnabled = false;
this.ecpTrackingEnabled = await this.getIsEcpRendezvousTrackingEnabled();
isTelnetRendezvousTrackingEnabled = await this.getIsTelnetRendezvousTrackingEnabled();

if (this.ecpTrackingEnabled || isTelnetRendezvousTrackingEnabled) {
public async activate(): Promise<boolean> {
//if ECP tracking is supported, turn that on
if (this.doesHostSupportEcpRendezvousTracking) {
// Toggle ECP tracking off and on to clear the log and then continue tracking
let untrack = await this.toggleEcpRendezvousTracking('untrack');
let track = await this.toggleEcpRendezvousTracking('track');
this.ecpTrackingEnabled = untrack && track;
const isEcpTrackingEnabled = untrack && track && await this.getIsEcpRendezvousTrackingEnabled();
if (isEcpTrackingEnabled) {
this.trackingSource = 'ecp';
this.startEcpPingTimer();

//disable telnet rendezvous tracking since ECP is working
try {
await this.runSGLogrendezvousCommand('off');
} catch { }
return true;
}
}
if (this.ecpTrackingEnabled) {
this.logger.log('ecp rendezvous logging is enabled');
this.startEcpPingTimer();

//ECP tracking is not supported (or had an issue). Try enabling telnet rendezvous tracking (that only works with run_as_process=0, but worth a try...)
await this.runSGLogrendezvousCommand('on');
if (await this.getIsTelnetRendezvousTrackingEnabled()) {
this.trackingSource = 'telnet';
return true;
}
return this.ecpTrackingEnabled;
return false;
}

/**
Expand Down Expand Up @@ -229,7 +244,7 @@ export class RendezvousTracker {
let match = /\[sg\.node\.(BLOCK|UNBLOCK)\s{0,}\] Rendezvous\[(\d+)\](?:\s\w+\n|\s\w{2}\s(.*)\((\d+)\)|[\s\w]+(\d+\.\d+)+|\s\w+)/g.exec(line);
// see the following for an explanation for this regex: https://regex101.com/r/In0t7d/6
if (match) {
if (!this.ecpTrackingEnabled) {
if (this.trackingSource === 'telnet') {
let [, type, id, fileName, lineNumber, duration] = match;
if (type === 'BLOCK') {
// detected the start of a rendezvous event
Expand Down Expand Up @@ -400,8 +415,20 @@ export class RendezvousTracker {
/**
* Destroy/tear down this class
*/
public destroy() {
public async destroy() {
this.emitter?.removeAllListeners();
this.stopEcpPingTimer();
//turn off ECP rendezvous tracking
if (this.doesHostSupportEcpRendezvousTracking) {
await this.toggleEcpRendezvousTracking('untrack');
}

//turn off telnet rendezvous tracking
try {
await this.runSGLogrendezvousCommand('off');
} catch (e) {
this.logger.error('Failed to disable logrendezvous over 8080', e);
}
}
}

Expand Down
6 changes: 0 additions & 6 deletions src/adapters/DebugProtocolAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ export class DebugProtocolAdapter {
this.chanperfTracker.on('chanperf', (output) => {
this.emit('chanperf', output);
});

// watch for rendezvous events
this.rendezvousTracker.on('rendezvous', (output) => {
this.emit('rendezvous', output);
});
}

private logger = logger.createLogger(`[${DebugProtocolAdapter.name}]`);
Expand Down Expand Up @@ -91,7 +86,6 @@ export class DebugProtocolAdapter {
public on(eventName: 'connected', handler: (params: boolean) => void);
public on(eventname: 'console-output', handler: (output: string) => void); // TODO: might be able to remove this at some point.
public on(eventname: 'protocol-version', handler: (output: ProtocolVersionDetails) => void);
public on(eventname: 'rendezvous', handler: (output: RendezvousHistory) => void);
public on(eventName: 'runtime-error', handler: (error: BrightScriptRuntimeError) => void);
public on(eventName: 'suspend', handler: () => void);
public on(eventName: 'start', handler: () => void);
Expand Down
7 changes: 0 additions & 7 deletions src/adapters/TelnetAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,10 @@ export class TelnetAdapter {
this.chanperfTracker = new ChanperfTracker();
this.compileErrorProcessor = new CompileErrorProcessor();


// watch for chanperf events
this.chanperfTracker.on('chanperf', (output) => {
this.emit('chanperf', output);
});

// watch for rendezvous events
this.rendezvousTracker.on('rendezvous', (output) => {
this.emit('rendezvous', output);
});
}

public logger = logger.createLogger(`[${TelnetAdapter.name}]`);
Expand Down Expand Up @@ -83,7 +77,6 @@ export class TelnetAdapter {
public on(eventName: 'diagnostics', handler: (params: BSDebugDiagnostic[]) => void);
public on(eventName: 'connected', handler: (params: boolean) => void);
public on(eventname: 'console-output', handler: (output: string) => void);
public on(eventname: 'rendezvous', handler: (output: RendezvousHistory) => void);
public on(eventName: 'runtime-error', handler: (error: BrightScriptRuntimeError) => void);
public on(eventName: 'suspend', handler: () => void);
public on(eventName: 'start', handler: () => void);
Expand Down
29 changes: 28 additions & 1 deletion src/debugSession/BrightScriptDebugSession.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { util as bscUtil, standardizePath as s } from 'brighterscript';
import { DefaultFiles } from 'roku-deploy';
import type { AddProjectParams, ComponentLibraryConstructorParams } from '../managers/ProjectManager';
import { ComponentLibraryProject, Project } from '../managers/ProjectManager';
import * as dateFormat from 'dateformat';
import { RendezvousTracker } from '../RendezvousTracker';

const sinon = sinonActual.createSandbox();
const tempDir = s`${__dirname}/../../.tmp`;
Expand Down Expand Up @@ -548,6 +548,33 @@ describe('BrightScriptDebugSession', () => {
});
});

describe('initRendezvousTracking', () => {
it('clears history when disabled', async () => {
const stub = sinon.stub(session, 'sendEvent');
const activateStub = sinon.stub(RendezvousTracker.prototype, 'activate');
const clearHistoryStub = sinon.stub(RendezvousTracker.prototype, 'clearHistory');

session['launchConfiguration'].rendezvousTracking = false;

await session['initRendezvousTracking']();
expect(clearHistoryStub.called).to.be.true;
expect(activateStub.called).to.be.false;
});

it('activates when not disabled', async () => {
const stub = sinon.stub(session, 'sendEvent');
const activateStub = sinon.stub(RendezvousTracker.prototype, 'activate');
const clearHistoryStub = sinon.stub(RendezvousTracker.prototype, 'clearHistory');

session['launchConfiguration'].rendezvousTracking = undefined;

await session['initRendezvousTracking']();
expect(clearHistoryStub.called).to.be.true;
expect(activateStub.called).to.be.true;

});
});

describe('setBreakPointsRequest', () => {
let response;
let args: DebugProtocol.SetBreakpointsArguments;
Expand Down
46 changes: 37 additions & 9 deletions src/debugSession/BrightScriptDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,7 @@ export class BrightScriptDebugSession extends BaseDebugSession {

util.log(`Connecting to Roku via ${this.enableDebugProtocol ? 'the BrightScript debug protocol' : 'telnet'} at ${this.launchConfiguration.host}`);

this.rendezvousTracker = new RendezvousTracker(this.deviceInfo);

// start ECP rendezvous tracking (if possible)
await this.rendezvousTracker.activateEcpTracking();
await this.initRendezvousTracking();

this.createRokuAdapter(this.launchConfiguration.host, this.rendezvousTracker);
if (!this.enableDebugProtocol) {
Expand Down Expand Up @@ -333,11 +330,6 @@ export class BrightScriptDebugSession extends BaseDebugSession {
this.sendEvent(new ChanperfEvent(output));
});

// Send rendezvous events to the extension
this.rokuAdapter.on('rendezvous', (output) => {
this.sendEvent(new RendezvousEvent(output));
});

//listen for a closed connection (shut down when received)
this.rokuAdapter.on('close', (reason = '') => {
if (reason === 'compileErrors') {
Expand Down Expand Up @@ -429,6 +421,42 @@ export class BrightScriptDebugSession extends BaseDebugSession {
}
}

/**
* Activate rendezvous tracking (IF enabled in the LaunchConfig)
*/
public async initRendezvousTracking() {
const timeout = 5000;
let initCompleted = false;
await Promise.race([
util.sleep(timeout),
this._initRendezvousTracking().finally(() => {
initCompleted = true;
})
]);

if (initCompleted === false) {
this.showPopupMessage(`Rendezvous tracking timed out after ${timeout}ms. Consider setting "rendezvousTracking": false in launch.json`, 'warn');
}
}

private async _initRendezvousTracking() {
this.rendezvousTracker = new RendezvousTracker(this.deviceInfo);

// Send rendezvous events to the debug protocol client
this.rendezvousTracker.on('rendezvous', (output) => {
this.sendEvent(new RendezvousEvent(output));
});

//clear the history so the user doesn't have leftover rendezvous data from a previous session
this.rendezvousTracker.clearHistory();

//if rendezvous tracking is enabled, then enable it on the device
if (this.launchConfiguration.rendezvousTracking !== false) {
// start ECP rendezvous tracking (if possible)
await this.rendezvousTracker.activate();
}
}

/**
* Anytime a roku adapter emits diagnostics, this method is called to handle it.
*/
Expand Down

0 comments on commit 150434d

Please sign in to comment.