{"action":"created","issue":{"url":"https://api.github.com/repos/jerryscript-project/jerryscript/issues/2605","repository_url":"https://api.github.com/repos/jerryscript-project/jerryscript","labels_url":"https://api.github.com/repos/jerryscript-project/jerryscript/issues/2605/labels{/name}","comments_url":"https://api.github.com/repos/jerryscript-project/jerryscript/issues/2605/comments","events_url":"https://api.github.com/repos/jerryscript-project/jerryscript/issues/2605/events","html_url":"https://github.com/jerryscript-project/jerryscript/pull/2605","id":382885371,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMyNDg4MTg5","number":2605,"title":"Heap snapshot API","user":{"login":"cpfair","id":271275,"node_id":"MDQ6VXNlcjI3MTI3NQ==","avatar_url":"https://avatars0.githubusercontent.com/u/271275?v=4","gravatar_id":"","url":"https://api.github.com/users/cpfair","html_url":"https://github.com/cpfair","followers_url":"https://api.github.com/users/cpfair/followers","following_url":"https://api.github.com/users/cpfair/following{/other_user}","gists_url":"https://api.github.com/users/cpfair/gists{/gist_id}","starred_url":"https://api.github.com/users/cpfair/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cpfair/subscriptions","organizations_url":"https://api.github.com/users/cpfair/orgs","repos_url":"https://api.github.com/users/cpfair/repos","events_url":"https://api.github.com/users/cpfair/events{/privacy}","received_events_url":"https://api.github.com/users/cpfair/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":11,"created_at":"2018-11-20T23:07:46Z","updated_at":"2018-12-11T13:36:44Z","closed_at":null,"author_association":"NONE","pull_request":{"url":"https://api.github.com/repos/jerryscript-project/jerryscript/pulls/2605","html_url":"https://github.com/jerryscript-project/jerryscript/pull/2605","diff_url":"https://github.com/jerryscript-project/jerryscript/pull/2605.diff","patch_url":"https://github.com/jerryscript-project/jerryscript/pull/2605.patch"},"body":"This change introduces a new public API function, `jerry_heap_snapshot_capture`, permitting developers to inspect the current state of the JerryScript heap to diagnose memory-usage problems. \r\n\r\nThe heap is exposed as a directed graph, where nodes represent allocations (w/ their type, size, and textual representation) and edges represent references (w/ type and name). Once a snapshot is taken, the heap might be viewed directly as a graph, or processed further to produce a tree-view of allocations ranked by size, etc.\r\n\r\nThe API itself is callback-based, with the developer providing function pointers to be called for every node and edge in the heap. They can then write these entries to a file, build their own in-memory model, filter for certain object types/sizes, etc. as their needs dictate.\r\n\r\nBy default, the entire feature is disabled, hopefully having little to no memory or performance impact. It can be enabled in stages:\r\n* `JERRY_FEATURE_HEAP_SNAPSHOT` - enables heap snapshots, but does not expose allocation sizes (all allocations reported as 0 bytes). Fixed memory footprint.\r\n* `JERRY_FEATURE_HEAP_SNAPSHOT` + `JERRY_FEATURE_MEM_TRACK_ALLOCATION_SIZES` - also reports allocation sizes, at the cost of an additional memory overhead equal to ~1/64th of the allocated heap size. Internal heap only.\r\n* `JERRY_FEATURE_MEM_TRACK_ALLOCATION_SIZES` - if the developer already has heap pointers and just needs to know their allocation sizes.\r\n\r\n## Overview of changes\r\n* Parameterize `ecma_gc_mark` (renamed `ecma_gc_traverse_inner`) to permit a callback to be called for every allocation that would normally be marked during an GC run. Similar changes were made to downstream functions like `ecma_gc_mark_property`.\r\n* Add new internal `ecma_gc_walk_heap` function that takes a callback and enumerates regular heap allocations, literals, bytecode objects, and \"magic strings\". While the \"magic strings\" are not actually heap-allocated, they are included in the enumeration as on-heap allocations may reference them.\r\n* New public API `jerry_heap_snapshot_capture`, which is a wrapper around `ecma_gc_walk_heap` to resolve allocation types/sizes, and generate pairs of node/edge callbacks for each allocation as appropriate.\r\n* Explicit tracking of all heap allocation sizes via `jmem_heap_allocation_size`. Normally we rely on user code to know the sizes of the allocations it's working with. However, when all we have is a pointer to an unknown allocation, a standalone function is required. This is implemented through an allocation boundary bitmap, nominally sized at ~1/64th of the heap it represents. This is only implemented for internal heaps, though could be extended to all heap modes.\r\n\r\n## Open questions\r\n\r\n* I've used some contrived inlining in `ecma-gc.c` to produce two copies of `ecma_gc_mark` (now called `ecma_gc_traverse_inner`), one with a parameterized \"traverse\" callback and the other with a pre-baked reference to `ecma_gc_set_object_visited`. This eliminates any overhead on regular `ecma_gc_mark` invocations, at the cost of inflating the binary. This may not be the correct trade-off for every user - should it be configurable?\r\n* Furthermore, I did this only for `ecma_gc_traverse_inner`, while its descendent calls are left in their generic form, imposing a marginal overhead on regular GC runs. If this is a problem, I can use the same forced-inline-as-template-metaprogramming technique as with `ecma_gc_traverse_inner`, or just force-inline the functions as-is (most of them are called only once), either of which should eliminate any runtime impact. Again, the same tradeoff applies of code space & source complexity vs. runtime performance.\r\n\r\n## What's not included\r\n### Serialization\r\n\r\nI did not attempt to include any serialization/export functionality for heap snapshots, as there does not appear to be any standard format for doing so. V8 does implement one such format, but as far as I can tell it is undocumented and intended for internal use only.\r\n\r\nInstead, I hope the example code provided in the API reference will act as a good starting point for anyone wishing to export the heap snapshot for further processing. An end-to-end example of this can be seen in the heap snapshot unit test, where a python script loads and inspects the heap from a simple flatfile.\r\n\r\n### Tooling\r\nPartially due to the above point, I did not include any ready-made tools for analyzing heap snapshots once captured. I think this would be a good idea for future work, but I believe the API is useful even on its own... and this PR is large enough as-is!\r\n\r\nJerryScript-DCO-1.0-Signed-off-by: Collin Fair cfair@fitbit.com"},"comment":{"url":"https://api.github.com/repos/jerryscript-project/jerryscript/issues/comments/446204535","html_url":"https://github.com/jerryscript-project/jerryscript/pull/2605#issuecomment-446204535","issue_url":"https://api.github.com/repos/jerryscript-project/jerryscript/issues/2605","id":446204535,"node_id":"MDEyOklzc3VlQ29tbWVudDQ0NjIwNDUzNQ==","user":{"login":"zherczeg","id":2270193,"node_id":"MDQ6VXNlcjIyNzAxOTM=","avatar_url":"https://avatars2.githubusercontent.com/u/2270193?v=4","gravatar_id":"","url":"https://api.github.com/users/zherczeg","html_url":"https://github.com/zherczeg","followers_url":"https://api.github.com/users/zherczeg/followers","following_url":"https://api.github.com/users/zherczeg/following{/other_user}","gists_url":"https://api.github.com/users/zherczeg/gists{/gist_id}","starred_url":"https://api.github.com/users/zherczeg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zherczeg/subscriptions","organizations_url":"https://api.github.com/users/zherczeg/orgs","repos_url":"https://api.github.com/users/zherczeg/repos","events_url":"https://api.github.com/users/zherczeg/events{/privacy}","received_events_url":"https://api.github.com/users/zherczeg/received_events","type":"User","site_admin":false},"created_at":"2018-12-11T13:36:44Z","updated_at":"2018-12-11T13:36:44Z","author_association":"CONTRIBUTOR","body":"> From technical perspective, I would probably introduce a struct which contains the members required for the traverse, allocate on the stack locally when the api function is called, and add a new global pointer (NULL when not used) to jerry context which points to this structure.\r\n\r\nCould you try this as well? Should have 0 effect when the feature is disabled."}}
{"actor":{"display_login":"zherczeg"}}