Skip to content
This repository has been archived by the owner on Apr 3, 2024. It is now read-only.

Commit

Permalink
feat: Add ability to store breakpoint data in firebase realtime datab…
Browse files Browse the repository at this point in the history
…ase (#1067)

* Checkpoint on Firebase controller.

Error handling and tests still need work.

* Move api key and db url to the client's control

* Some changes to work with the cli implementation

- We now use a top level 'cdbg' node
- Add timestamp with breakpoint is finalized
- When a snapshot triggers, a separate node is used for the complete
  snapshot data under final.

* Fix tests that relied on debuglet.stop().

Also disable the tests that aren't working yet, and remove a bunch of console.log messages.

* Use underscores in label keys.

* Replace console.log with util.debuglog.

Co-authored-by: Jason Borg <jcborg@google.com>
  • Loading branch information
mctavish and jasonborg authored May 5, 2022
1 parent a4add51 commit 22e255a
Show file tree
Hide file tree
Showing 10 changed files with 730 additions and 341 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"console-log-level": "^1.4.0",
"extend": "^3.0.2",
"findit2": "^2.2.3",
"firebase-admin": "^9.11.1",
"gcp-metadata": "^4.0.0",
"p-limit": "^3.0.1",
"semver": "^7.0.0",
Expand Down
17 changes: 17 additions & 0 deletions src/agent/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,21 @@ export interface ResolvedDebugAgentConfig extends GoogleAuthOptions {
* in defaultConfig.
*/
resetV8DebuggerThreshold: number;

/**
* If set, use Firebase Realtime Database as the backend instead of the
* Cloud Debugger API. Requires many things, which will be documented later.
*/
useFirebase: boolean;

/**
* If set, use this key for Firebase activities instead of default google credentials.
*/
firebaseKeyPath?: string;
/**
* If set, use this as the firebase database url. If not set, a FIXME default will be used.
*/
firebaseDbUrl?: string;
}

export interface StackdriverConfig extends GoogleAuthOptions {
Expand Down Expand Up @@ -412,4 +427,6 @@ export const defaultConfig: ResolvedDebugAgentConfig = {
forceNewAgent_: false,
testMode_: false,
resetV8DebuggerThreshold: 30,

useFirebase: false,
};
163 changes: 17 additions & 146 deletions src/agent/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,14 @@
* @module debug/controller
*/

import {ServiceObject} from '@google-cloud/common';
import * as assert from 'assert';
import * as qs from 'querystring';
import * as t from 'teeny-request';

import {URL} from 'url';

import {Debug} from '../client/stackdriver/debug';
import {Debuggee} from '../debuggee';
import * as stackdriver from '../types/stackdriver';

export class Controller extends ServiceObject {
private nextWaitToken: string | null;
private agentId: string | null;

apiUrl: string;

/**
* @constructor
*/

constructor(debug: Debug, config?: {apiUrl?: string}) {
super({parent: debug, baseUrl: '/controller'});

/** @private {string} */
this.nextWaitToken = null;
this.agentId = null;

this.apiUrl = `https://${debug.apiEndpoint}/v2/controller`;

if (config && config.apiUrl) {
this.apiUrl = config.apiUrl + new URL(this.apiUrl).pathname;
}
}

export interface Controller {
/**
* Register to the API (implementation)
*
* @param {!function(?Error,Object=)} callback
* @private
*/
register(
debuggee: Debuggee,
Expand All @@ -66,91 +34,7 @@ export class Controller extends ServiceObject {
agentId: string;
}
) => void
): void {
const options = {
uri: this.apiUrl + '/debuggees/register',
method: 'POST',
json: true,
body: {debuggee},
};
this.request(
options,
(err, body: {debuggee: Debuggee; agentId: string}, response) => {
if (err) {
callback(err);
} else if (response!.statusCode !== 200) {
callback(
new Error('unable to register, statusCode ' + response!.statusCode)
);
} else if (!body.debuggee) {
callback(new Error('invalid response body from server'));
} else {
debuggee.id = body.debuggee.id;
this.agentId = body.agentId;
callback(null, body);
}
}
);
}

/**
* Fetch the list of breakpoints from the server. Assumes we have registered.
* @param {!function(?Error,Object=,Object=)} callback accepting (err, response,
* body)
*/
listBreakpoints(
debuggee: Debuggee,
callback: (
err: Error | null,
response?: t.Response,
body?: stackdriver.ListBreakpointsResponse
) => void
): void {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
assert(debuggee.id, 'should have a registered debuggee');
const query: stackdriver.ListBreakpointsQuery = {successOnTimeout: true};
if (that.nextWaitToken) {
query.waitToken = that.nextWaitToken;
}
if (that.agentId) {
query.agentId = that.agentId;
}

const uri =
this.apiUrl +
'/debuggees/' +
encodeURIComponent(debuggee.id) +
'/breakpoints?' +
qs.stringify(query as qs.ParsedUrlQueryInput);
that.request(
{uri, json: true},
(err, body: stackdriver.ListBreakpointsResponse, response) => {
if (!response) {
callback(
err || new Error('unknown error - request response missing')
);
return;
} else if (response.statusCode === 404) {
// The v2 API returns 404 (google.rpc.Code.NOT_FOUND) when the agent
// registration expires. We should re-register.
callback(null, response as {} as t.Response);
return;
} else if (response.statusCode !== 200) {
callback(
new Error(
'unable to list breakpoints, status code ' + response.statusCode
)
);
return;
} else {
body = body || {};
that.nextWaitToken = body.nextWaitToken;
callback(null, response as {} as t.Response, body);
}
}
);
}
): void;

/**
* Update the server about breakpoint state
Expand All @@ -162,34 +46,21 @@ export class Controller extends ServiceObject {
debuggee: Debuggee,
breakpoint: stackdriver.Breakpoint,
callback: (err?: Error, body?: {}) => void
): void {
assert(debuggee.id, 'should have a registered debuggee');
): void;

breakpoint.action = 'CAPTURE';
breakpoint.isFinalState = true;
const options = {
uri:
this.apiUrl +
'/debuggees/' +
encodeURIComponent(debuggee.id) +
// TODO: Address the case where `breakpoint.id` is `undefined`.
'/breakpoints/' +
encodeURIComponent(breakpoint.id as string),
json: true,
method: 'PUT',
body: {debuggeeId: debuggee.id, breakpoint},
};
/**
* Start listening to breakpoints updates. The callback will be called when
* there is an unrecoverable error or when the set of active breakpoints has changed.
* @param {!Debuggee} debuggee
* @param {!function(?Error,Object=)} callback accepting (err, breakpoints)
*/
subscribeToBreakpoints(
debuggee: Debuggee,
callback: (err: Error | null, breakpoints: stackdriver.Breakpoint[]) => void
): void;

// We need to have a try/catch here because a JSON.stringify will be done
// by request. Some V8 debug mirror objects get a throw when we attempt to
// stringify them. The try-catch keeps it resilient and avoids crashing the
// user's app.
try {
this.request(options, (err, body /*, response */) => {
callback(err!, body);
});
} catch (error) {
callback(error);
}
}
/**
* Stops the Controller. This is for testing purposes only.
*/
stop(): void;
}
Loading

0 comments on commit 22e255a

Please sign in to comment.