Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Code block recording #115

Merged
merged 3 commits into from
Apr 3, 2024
Merged

feat: Code block recording #115

merged 3 commits into from
Apr 3, 2024

Conversation

zermelo-wisen
Copy link
Collaborator

Fixes #82

Copy link
Collaborator

@dividedmind dividedmind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block recording should work like this:

import { record } from 'appmap-node';

const appmap = record(() => {
  hello("world");
});

Note you don't need to add a flag — just have a separate façade (NOT bin.js) for importing^Wrequiring (use package.json to direct require/import to the right file) and drop the process recording when it's loaded. You'll also need to expose the AppMap type so the clients can consume the results with confidence.

You probably don't need an extra recordAsync flavor — just do async recording if the function is async and/or returns a promise.

@zermelo-wisen zermelo-wisen force-pushed the feat/code-block-recording branch 3 times, most recently from 5911f61 to 870b524 Compare March 6, 2024 11:18
Copy link
Collaborator

@dividedmind dividedmind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please also split the two refactorings (Appmap.d.ts -> AppMap.ts, emit()) into separate commits with messages explaining the rationales? It'll make it much clearer what's going on when someone reads it down the line. Thanks!

src/Recording.ts Outdated
@@ -212,6 +212,11 @@ export default class Recording {
get running(): boolean {
return !!this.stream;
}

readAppMap(): AppMap.AppMap | undefined {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does this return undefined?

src/facade.ts Outdated
block();
} finally {
stopCodeBlockRecording();
recording.finish();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably say if (!recording.finish()) return.

src/facade.ts Show resolved Hide resolved

assert(appmap1.events.filter((e) => e.event == "call").length == 1);

const appmap2 = await recordAsync(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be an async function, right?

@@ -0,0 +1 @@
{}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

src/facade.ts Outdated
disableGlobalRecording();

export function record(block: () => unknown): AppMap.AppMap | undefined {
start(new Recording("block", "block", new Date().toISOString()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The record function should probably check if the code is instrumented and throw otherwise, telling the user to run with appmap-node. Otherwise it will just mysteriously fail to produce anything. (Add a test case for that!)

Copy link
Collaborator

@dividedmind dividedmind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good! Is this ready for the final review?

src/Recording.ts Show resolved Hide resolved
src/facade.ts Outdated
startCodeBlockRecording();
try {
const result = block();
if (result instanceof Promise) return result.then(() => finishRecording(), finishRecording);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I realized it's better to use isPromise; I believe plain instanceof can return misleading results if the promise is from another realm.

src/Recording.ts Outdated
// Check here if we should record instead of requiring each
// possible hook to check it.
// Checking this inside hooks too, will save CPU cycles only
// by preventing unnecessary event creation.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should mention that it's also checked in record — event creation can be expensive (especially stringification).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a change to prevent expensive event creation, with an additional commit: perf: Prevent unnecessary event object creation.

I know it complicated the hooks a bit, but returning undefined from Recording event creation methods when we should not record, forces the hook implementations (sqllite, prisma, pg, mysql, mongo, http...) to deal with it in compile time, thanks to TypeScript. Other option would be to expect them to check shouldRecord in every Recording event creation method call, which again would lead to similar changes.

Copy link
Collaborator

@dividedmind dividedmind Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was already fine as it was — record() method already checked if shouldRecord(); I just thought it would be good to say that in this comment :)

Let's roll back that perf commit and keep it simple. There will be some wasted events but most of them will be short-circuited by the check in record().

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, all the hooks which call event creation methods directly (sqlite, prisma, pg, mysql, mongo, http) will create the event objects, but the events will not be emitted when we should not record. I believe you meant these by some wasted events.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perf commit rolled back.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, all the hooks which call event creation methods directly (sqlite, prisma, pg, mysql, mongo, http) will create the event objects, but the events will not be emitted when we should not record. I believe you meant these by some wasted events.

Yeah, that's fine, I was more worried about the normal function calls which occur much more often, but the test in record() takes care of these.

@zermelo-wisen zermelo-wisen force-pushed the feat/code-block-recording branch 2 times, most recently from 9c3cc14 to c6256cc Compare March 26, 2024 18:06
- Renamed AppMap.d.ts to AppMap.ts to get the declaration file
  created automatically and put into dist in packing.
- Removed namespace declaration inside AppMap.d.ts to expose
  the types inside to the clients.
- Changed internal importers' import declarations to
  import type * as AppMap from ./AppMap so they can use the
  types with AppMap qualifier as before.
assert(this.stream); and this.stream.emit(event); statements extracted as
Recording.emit(event) method.
@dividedmind dividedmind merged commit 9e1048a into main Apr 3, 2024
6 checks passed
@dividedmind dividedmind deleted the feat/code-block-recording branch April 3, 2024 13:05
@appland-release
Copy link

🎉 This PR is included in version 2.20.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Code block recording
3 participants