Skip to content

Runtime tracing, flight recording#3627

Open
jakebailey wants to merge 8 commits intomainfrom
jabaile/go-runtime-trace
Open

Runtime tracing, flight recording#3627
jakebailey wants to merge 8 commits intomainfrom
jabaile/go-runtime-trace

Conversation

@jakebailey
Copy link
Copy Markdown
Member

This adds Go runtime/trace support, including flight recording. Traces are integrated into the VS Code extension, similarly to pprof profiling.

I've added trace points in various places, and plumbed context around a bit.

Copilot AI review requested due to automatic review settings April 27, 2026 21:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Go runtime/trace integration (including a flight recorder) to tsgo, and wires capture/control into the LSP + the preview VS Code extension to make runtime tracing accessible similarly to existing pprof tooling.

Changes:

  • Introduces internal/runtimetrace to manage process-lifetime runtime tracing and flight-recorder snapshots (env-driven + SIGUSR1 on Unix).
  • Plumbs context.Context through checker/program APIs and adds trace regions/log events across compiler, parser, resolver, project system, and LSP request handling.
  • Adds new LSP custom requests + VS Code extension commands to start/snapshot/stop the flight recorder.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/transformers/tstransforms/importelision_test.go Updates test program stub + checker creation to pass context.Context.
internal/testrunner/compiler_runner.go Passes t.Context() into ForEachCheckerParallel for tracing-friendly APIs.
internal/runtimetrace/runtimetrace.go New session management for runtime trace + flight recorder via env vars, snapshot-on-stop, Unix signal hook.
internal/runtimetrace/recorder.go New FlightRecorder wrapper around runtime/trace flight recorder APIs (start/snapshot/stop).
internal/runtimetrace/api.go New helpers for trace regions/tasks and safe/unsafe logging conventions.
internal/runtimetrace/signal_unix.go Unix SIGUSR1 handler to trigger a flight snapshot.
internal/runtimetrace/signal_other.go Non-Unix no-op signal handler implementation.
internal/project/snapshot.go Adds trace region around snapshot cloning.
internal/project/session.go Adds trace region around snapshot updates.
internal/project/project.go Adapts program binding call to new context-aware BindSourceFiles.
internal/project/checkerpool.go Threads ctx into checker creation in the project checker pool.
internal/parser/parser.go Adds trace region + trace log events to parsing entrypoint.
internal/module/resolver.go Adds trace region + trace log events to module resolution entrypoint.
internal/lsp/server.go Adds per-request trace tasks/logging and new handlers for flight recorder control.
internal/lsp/lsproto/lsp_generated.go Generated protocol additions for flight recorder requests/params/results.
internal/lsp/lsproto/_generate/generate.mts Adds generator specs for flight recorder protocol structures/requests.
internal/ls/autoimport/util.go Makes checker pool acquisition context-aware and threads ctx into checker creation.
internal/ls/autoimport/registry.go Updates checker creation/usage to pass ctx and handle canceled acquisition.
internal/ls/autoimport/aliasresolver.go Updates BindSourceFiles signature for ctx-aware checker.Program.
internal/execute/tsc.go Adds trace regions around compilation entrypoints.
internal/compiler/program.go Adds trace regions/logging in binding/diagnostics/emit, and threads ctx through checker pool APIs.
internal/compiler/fileloader.go Adds trace region around program file processing.
internal/compiler/emitter.go Adds trace region/logging for emit and adds additional transformer regions.
internal/compiler/checkerpool.go Threads ctx into checker creation and adds trace region/logging around checker pool init.
internal/checker/checker_test.go Updates tests/bench to new ctx-aware BindSourceFiles/NewChecker.
internal/checker/checker.go Makes Program.BindSourceFiles ctx-aware and adds trace regions/logging in checker creation + file checking.
cmd/tsgo/main.go Starts/stops runtime tracing session for the tsgo process lifetime.
_extension/src/session.ts Adds VS Code commands for start/snapshot/stop flight recorder and context key wiring.
_extension/src/client.ts Adds client methods to call new custom LSP flight recorder requests.
_extension/package.json Registers new extension commands and enablement conditions.
.golangci.yml Updates depguard rules to forbid direct runtime/trace usage outside internal/runtimetrace and adjusts linter exclusions.

Comment thread internal/lsp/server.go
Comment on lines +1817 to +1820
func (s *Server) handleSnapshotFlightRecorder(_ context.Context, params *lsproto.ProfileParams, _ *lsproto.RequestMessage) (*lsproto.ProfileResult, error) {
filePath, err := s.flightRecorder.Snapshot(params.Dir)
if err != nil {
return nil, err
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

handleSnapshotFlightRecorder dereferences params.Dir without checking whether params is nil. Since the generated handler signature accepts *ProfileParams, a malformed/null params payload would panic the server. Consider validating params != nil (and returning an InvalidRequest-style error) before using it.

Copilot uses AI. Check for mistakes.

switch os.Getenv(envRuntimeTraceDetail) {
case "", "0", "false", "no", "off":
// detail logging disabled
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

Start only ever sets unsafeLogging to true; it never resets it to false when TSGO_RUNTIME_TRACE_DETAIL is unset/falsey. If Start is called multiple times in-process (tests, multiple entrypoints, etc.), unsafe logging can remain enabled unexpectedly and cause user-data to be emitted even when the env var is off. Consider explicitly storing false in the disabled case (and/or resetting on Stop) so the value always reflects the current configuration.

Suggested change
// detail logging disabled
unsafeLogging.Store(false)

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +52
const (
envRuntimeTrace = "TSGO_RUNTIME_TRACE"
envRuntimeTraceFlight = "TSGO_RUNTIME_TRACE_FLIGHT"
envRuntimeTraceFlightMinAge = "TSGO_RUNTIME_TRACE_FLIGHT_MIN_AGE"
envRuntimeTraceFlightMaxByte = "TSGO_RUNTIME_TRACE_FLIGHT_MAX_BYTES"
envRuntimeTraceDetail = "TSGO_RUNTIME_TRACE_DETAIL"
)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The constant name envRuntimeTraceFlightMaxByte is singular, but it corresponds to TSGO_RUNTIME_TRACE_FLIGHT_MAX_BYTES (plural). Renaming it to ...MaxBytes would avoid confusion and better reflect the actual env var and meaning.

Copilot uses AI. Check for mistakes.
Comment on lines 64 to +76
@@ -67,6 +73,7 @@ func (e *emitter) runScriptTransformers(emitContext *printer.EmitContext, source
}

func (e *emitter) runDeclarationTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath, declarationMapPath string) (*ast.SourceFile, []*ast.Diagnostic) {
defer runtimetrace.Region(context.TODO(), "emitter.runDeclarationTransformers")()
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

runScriptTransformers/runDeclarationTransformers start trace regions with context.TODO(), which breaks nesting/association with the request/task context passed into emit(ctx). Since emit already has a context, consider threading it through to these helpers (e.g., add a ctx parameter) so transformer regions appear under the correct task/region in traces.

Copilot uses AI. Check for mistakes.
Comment on lines 204 to 230
@@ -214,11 +216,15 @@ func createCheckerPool(program checker.Program) (getChecker func() (*checker.Che
current := created.Load()
if current >= maxSize {
// At limit, wait for one to become available
ch := <-pool
return ch, func() { pool <- ch }
select {
case ch := <-pool:
return ch, func() { pool <- ch }
case <-ctx.Done():
return nil, func() {}
}
}
if created.CompareAndSwap(current, current+1) {
ch := core.FirstResult(checker.NewChecker(program, nil))
ch := core.FirstResult(checker.NewChecker(ctx, program, nil))
return ch, func() { pool <- ch }
}
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

createCheckerPool returns early on <-ctx.Done() in a couple of places, but if the context is canceled after the initial non-blocking select and before/while the CAS loop runs, it can still spin and/or create a new checker even though cancellation was requested. Consider checking ctx.Err() inside the for { ... } loop (and before creating a new checker) so cancellation reliably stops work.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants