-
Notifications
You must be signed in to change notification settings - Fork 4
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
Conversation
There was a problem hiding this 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.
5911f61
to
870b524
Compare
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
.
test/codeBlock/index.js
Outdated
|
||
assert(appmap1.events.filter((e) => e.event == "call").length == 1); | ||
|
||
const appmap2 = await recordAsync(() => { |
There was a problem hiding this comment.
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?
test/codeBlock/package.json
Outdated
@@ -0,0 +1 @@ | |||
{} |
There was a problem hiding this comment.
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())); |
There was a problem hiding this comment.
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!)
870b524
to
024a72f
Compare
There was a problem hiding this 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/facade.ts
Outdated
startCodeBlockRecording(); | ||
try { | ||
const result = block(); | ||
if (result instanceof Promise) return result.then(() => finishRecording(), finishRecording); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perf commit rolled back.
There was a problem hiding this comment.
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.
9c3cc14
to
c6256cc
Compare
- 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.
c6256cc
to
df39f93
Compare
🎉 This PR is included in version 2.20.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Fixes #82