Skip to content

Better inspection of heap values #2557

@rakudrama

Description

@rakudrama

I have been using Observatory for trying to understand the program heap and finding devtools lacks some equivalent general abilities.

The two key features are:

  • Evaluation of an expression in any context.
  • Making heap queries available as Dart runtime values.

I want to be able to 'move' around the memory graph.

I want to be able to select some object and then be there.
In observatory I can shift-click on an object and get a new tab which is 'at' the object.
Expressions are evaluated with this bound to the object, e.g. in a _List, the expression length will give the length of the list.
In standard debugging I can single-step to a location and see the values stored in variables in scope, but I want that power without first running the program to some point.
Disconnecting a REPL from the control flow of the application is the fundamental feature here.

There might be an alternative to having many 'object inspector' tabs that are 'at' different objects.
Perhaps the inspected value could be copied to the console's value history and be used from there.
The value history could be made available by injected variable names.

Instead of the console showing

>  123
   123
> 

it would name all the values

>    123
$21: 123
>    $21 + 100
$22: 223
> 

Clicking on some node in some other view might then add it to the history

$23: {  ⊞  }   // another clickable expandable reference to the object that was under the pointer

I can now use it in an expression:

>    $23.length
$24: 2039
>

I think it would be helpful for this approach to be able to expand values in the console to 'grab' sub-elements, but currently I see unhelpful text like Instance of 'CompilerOptions'.

I want to be able to get a list of all instances of a class

It is very powerful to be able to programatically inspect all instances of a class. You can do things like ask how many strings contain "hello"

this.where((s) => s.contains('hello')).length

The this. would normally be implicit.

I have been asking questions that would normally require modifying the program.

  • How many tree nodes have a specific kind of parent node?
  • Get a random sample of ten elements: (this..shuffle()).sublist(0, 10)
  • Group by length (or some other attribute): fold({}, (Map m, s) {(m[s.length]??=[]).add(s); return m;})

There are three levels of the 'all instances' feature, and all are useful:

  • All instances of a class ('all')
  • All instances of a class and its subclasses ('all + extends')
  • All instances of a class and its subtypes ('all + implements')

In Observatory, I often get to this place from an allocation profile.

  • Look at the allocation profile.
  • Click on a class.
  • Click on the ↺ to fetch the list of instances
  • Click on the instance of _List returned for the instances
  • Evaluate the query, e.g. where((s) => s.contains("hello")).length.

Inbound references to a object

I use inbound references a lot in understanding why a value has not been GC-ed.
It might be useful for this to be surfaced as an object that can be used programmatically.
Even if it is not that useful, a reified set of references can be inspected as any other object rather than via a special UI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3issues we think are valid but not importantscreen: debuggerIssues with the Debugger screenscreen: memoryIssues with the Memory screen.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions