Skip to content
Gordon Williams edited this page Apr 18, 2024 · 3 revisions

This page will eventually become part of the espruino.com docs - please share your debugging tips here!

General

Bangle.js apps (particularly clocks) have all the code inside { ... } and use let/const to define vars, which means none of their code is defined at the global scope and is easy to free (for fast load). When debugging you may want to remove those outer brackets so you can actually check the contents of those variables (and run the functions) from the REPL.

Espruino has a built-in line-by-line debugger that can be triggered by adding the debugger keyword in your code. More info at https://www.espruino.com/Debugger

Where was a function called?

Sometimes you might have some code that calls a function, and you need to find out where/when the function was called.

In that case you can replace the function with one that throws an Error instead:

foo = function() { throw new Error("Foo was called here"); }

When the function is called, a stack trace will then be printed to the console.

You can also just add the debugger keyword with foo = function() { debugger } and Espruino will jump into debug mode, allowing you to step out/through the code at the point that the function was executed (see https://www.espruino.com/Debugger).

How often or with what arguments is a function called?

Sometimes you might want to print something when a function is called, or know what arguments it was called with. In this case you can just replace the function with a new one, which still calls the old one but prints extra information:

(function(old) {
  functionToCheck = function(arg1,arg2) {
    console.log("Function was called with ",arg1,arg2);
    return old(arg1,arg2);
  };
}(functionToCheck);

Debugging JIT

The JIT compiler is still quite a recent development, and so may have issues.

For debugging JIT code we'd suggest first removing the "jit" keyword and checking if the function works. If it does, then try removing code from the JIT function until you have a very small snippet you can reproduce the problem with. You can then either change that part of your code or ideally submit a bug report with it.

Crashes

If some code crashes or misbehaves, make sure you try running it when connected to the Web IDE. Often an error will produce a stack trace on the IDE's console that will point you to the exact error. If the line numbers aren't correct try disabling pretokenisation and minification in the App Loader/Web IDE and re-upload to ensure that the code on the device is exactly the same as your code.

If the crash is happening when you can't be connected to the IDE you can also set Settings -> Utils -> Debug to Log, and you can then connect with the IDE at a later time and load the file log.txt from the Storage menu.

If you're using Espruino (not Bangle.js) there are some examples of saving errors that occur when you're not connected here

Memory usage

Espruino stores normal arrays as 'sparse' arrays - so each entry uses one variable. If you want to store any more than 20 items in an array, consider using types arrays like Uint8Array - they use less memory and are faster more info here.

Checking for memory leaks

If you type process.memory().usage on the console it'll show you how many JS variables are used. The first time you run it it'll add one variable's worth of memory usage (as process gets allocated), but ideally in most code if check memory usage every so often, the value you get shouldn't keep rising.

Many clocks have a draw() function and you can call that function manually and then call process.memory().usage and see if memory goes up. You can sometimes narrow it down to some other function that when called increases memory usage.

Tracking down memory usage/leaks

You can use E.getSizeOf(global,1) which will return an array of all the contents of the global object and how big they are (however the size values can sometimes be confusing as objects often end up interlinked). You can then try and recreate the memory leak and run E.getSizeOf(global,1) again, and can compare to see if one of the sizes has got bigger. You can narrow it down by specifying what you're interested in instead of just global, or can specify E.getSizeOf(global,2) to provide a multi-level array that digs down further into the structures.

Advanced: you can dump all variables, one per line with E.dumpVariables (you may have to enable logging in the IDE to capture all the lines). It's then possible to compare the two dumps with a diff tool to see if there are any new variables defined. Tracking down what a variable corresponds to get be tricky.

You can also use EspruinoMemView - this is a web app that connects to your device, calls E.dumpVariables and shows you the contents of memory in a graphical format. It can help you find areas of your code where a lot of memory is used.

Memory leaks during 'fast load'

There are some good examples of testing at https://www.espruino.com/Bangle.js+Fast+Load#testing-afterwards

After you've unloaded an app, you can run dump() which attempts to dump the contents of RAM in a human readable form - you may be able to see some variables/functions defined there that look like they shouldn't be. It may help to uninstall all widgets first to ensure that they're not making it harder to find the real source of memory usage.

Where was a variable written?

Do you have a variable that's been written, but you don't know where from? You can redefine it as a setter which throws an error, and it will then create a stack trace next time it is set. In this example we're testing width on the global object.

Object.defineProperty(global, "width", { set: () => { throw new Error("tried to set width"); } })

Low Level

Espruino stores a bunch of internal state in a 'hidden' variable in the root scope. You can access it with global["\xFF"]

Some useful fields are:

  • global["\xFF"].timers is a list of all active timers/intervals set with setInterval/setTimeout.
    • callback/cb is the function that'll be called when the timeout expires
    • time is the time until it fires (in system 'ticks' - 1/1024*1024 on nRF52 builds)
    • interval/int (if set) means it's an interval, and is the time for each repetition
  • global["\xFF"].watches is a list of all active watches from setWatch
  • global["\xFF"].modules is an object showing all loaded modules