Skip to Content
Technical Articles

[SIT Belgium 2019 Recap] Debugging Node.js Applications in SCP CF. Part 4: Time Travel Debugging

Intro

This is a recap of the session “Troubleshooting Node.js applications: Debugging in SAP Cloud Platform Cloud Foundry” that took place in a recent SAP Inside Track Belgium 2019 event.

Slide deck of the session is available at SlideShare.

Overview of the session recap blog series:

 

Constraints of forward debugging technique and introduction to time travel debugging

When debugging applications and using breakpoints, a developer employs a traditional forward debugging technique when following program flow and stepping through executed statements. There are times when a breakpoint is set too late in the debugged program, and by time the program reaches the breakpoint and program’s execution is paused, a code snippet that could have caused a bug, is already missed. Usually the developer terminates current debugging session, sets a new breakpoint earlier in the program and repeats program execution with conditions that would lead program flow to hit a newly set breakpoint. The program reaches a new breakpoint, program’s execution is paused, and the developer gets back to debugging.

Alternatively, it is also common to set a breakpoint early in a source code of the program, but there is a risk of setting a breakpoint too early and facing necessity of stepping through executed statements for a long time until an appropriate code snippet is determined.

Both approaches of a forward debugging technique described above can contribute to increased time required by the developer to get to a root cause and pause execution of the debugged program at a code snippet that causes an issue.

 

It would have been convenient to be able to revert execution of the program from a breakpoint where its execution has been paused and take several steps back to analyse earlier state of the program. That is exactly a purpose of an alternative debugging technique – reverse debugging or time travel debugging – that will be discussed in this blog and that will be applied to a Node.js application.

 

Time travel debugging is mostly relevant when coping with two types of debugging scenarios:

  • Reverse debugging of a running program – to analyze earlier states of the debugged program,
  • Postmortem debugging of a program that does not run anymore (crashed or was terminated) – for root cause analysis of possible reasons of program termination.

 

A standard approach that is utilized to implement time travel debugging is based on record-replay-snapshot architecture – which is, to take snapshots of program’s state by recording non-deterministic runtime interactions within the program during normal execution of the program. Using generated and collected snapshots, it is then possible to restore state of the program and replay forward flow of the program, starting from any of available snapshots. Snapshots also make it possible to execute reverse flow – instead of stepping through snapshots in a chronological order of their collection, we can run through collected snapshots in a reverse order and explore states of the program in a reverse chronological order – essentially, reverse debugging the program. Given collected snapshots are persisted, they can be replayed even if the program does not run anymore – for example, the program might have crashed or terminated, but if earlier recorded snapshots of its execution are available, they can be replayed, making it possible to run postmortem debugging of the program.

 

Sounds wonderful, isn’t it? There are certain technical obstacles that get on the way and prevent advancement and wide adoption of this technique – most remarkable and significant are following:

  • Runtime overhead caused by collection of snapshots during forward execution of the program – collection of large volume of information about program state per each snapshot, multiplied by number of required snapshots (which is driven by snapshots generation frequency and overall duration of recording). As a side effect, it also impacts disk space that is required to store collected snapshots,
  • Response time and delays when stepping through snapshots in reverse debugging and snapshots replay modes that are caused by parsing snapshots and restoring state of the program at each discrete point of collection over time series.

 

There are technical implementations of time travel debugging systems for a number of runtimes. A goal of such systems is to provision affordable time travel debugging system that addresses above mentioned constraints and finds a compromise between ultimate reverse debugging functionality and overhead and costs that are incurred by a system and associated with such functionality.

 

Tools for time travel debugging in Node.js

When it comes to JavaScript engine in general and Node.js in particular, probably most commonly referred and used implementation of time travel debugging system that has gained popularity in Node.js ecosystem, is based on usage of:

  • Alternative Node.js runtime named Node ChakraCore,
  • An alternative JavaScript debugger named Jardis that implements principles of time travel debugging described earlier. For Visual Studio Code, there is a relevant extension – NodeChakra Time Travel Debug – that enhances Visual Studio Code debugger capabilities with those required for time travel debugging (such as reverse steps and replay):

The extension also adds a new debugging configuration type that can be used as a shortcut to create an appropriate debugging configuration for launching a Node.js application and using Node ChakraCore runtime for it:

Alternatively, arguments and parameterization that are required for usage of time travel debugging functionality in Node ChakraCore, can be provided manually when starting the Node.js application or composing debugging configuration for it.

Note that default Node.js runtime that is based on Chrome V8 JavaScript engine, currently does not provide support for time travel debugging.

Another remark that shall be put in advance, is about Node ChakraCore engine: as per current documentation, “this project is still work in progress and not an officially supported Node.js branch”, so usage of Node ChakraCore engine shall be well-considered and taken with precaution.

 

Time travel debugging in action

In sake of simplicity, following debugging demo is conducted in a local environment. The debugged Node.js application has been launched using debugging configuration mentioned above, and when the breakpoint has been reached and program flow has been paused, it can be observed that a debugger got several new buttons that are a part of a time travel debugger and are used to perform reverse step and reverse step dynamic operations. Reverse step operation is used to navigate to a previous step in the current frame, whereas reverse step dynamic is used as an opposite action to step into functionality in a traditional forward debugging technique.

When using a time travel debugger in Visual Studio Code, it is possible to choose between forward debugging the program or switching to Time-Travel Replay mode and analyzing the program state based on collected snapshots, including possibility to step back to previously executed statements:

Stepping back through recorded snapshots can be conducted even if the debugged program has already crashed or has been terminated.

 

Tools for rapid switch between JavaScript engines

When running Node.js applications on different JavaScript engines (for example, Chrome V8 and Node ChakraCore) or on different versions of the same JavaScript engine (for example, Node.js 11 and 12 that use different versions of Chrome V8 JavaScript engine), developers might want to be able to quickly switch over between various used JavaScript engines and Node.js runtimes. This can be done by calling corresponding executable that will start a Node process by its absolute path, or manually adjusting path to default Node executable. Or alternatively, the latter option can be simplified and automated using specifically provided tools – such as Node Version Switcher. Node Version Switcher is a cross-platform command line tool that provides features to download required versions of certain Node.js runtimes and then to quickly switch between JavaScript engines used by Node.js runtime, and Node.js versions.

It is possible to download required Node.js runtimes and switch between them using interactive interface of the tool, or to utilize corresponding commands and handy shortcuts:

When switching over to a target Node.js runtime, Node Version Switcher dynamically overwrites path environment variable.

 

Together with usage of command line interface, it is also possible to make use of Node Version Switcher from within debugging configuration in Visual Studio Code and specify required Node.js runtime in there (assuming that target Node.js runtime has been added in Node Version Switcher already):

 

When switching between various Node.js runtimes, we can still make use of Node process attributes to check further details of JavaScript engine and Node.js runtime that are currently used:

  • Example with Node.js 12 that utilizes Chrome V8 JavaScript engine:

  • Example with Node ChakraCore engine:

2 Comments
You must be Logged on to comment or reply to a post.
    • Hello Marcello,

      Yes, you are absolutely right: this part of the demo was conducted in a local environment.

      A default Node.js buildpack in SCP Cloud Foundry uses Chrome V8 JavaScript engine. On the other hand, if it is required to bring Node ChakraCore to Cloud Foundry, it is possible to compose a custom buildpack that will use Node ChakraCore instead of Chrome V8, and use that custom buildpack instead of a default one when pushing a Node.js application to Cloud Foundry.

      Regards,

      Vadim