diff options
author | Aaron O'Mullan <aaron.omullan@gmail.com> | 2021-03-23 15:33:06 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-23 15:33:06 +0100 |
commit | 876f075ddebfaa69e8b80c5556a5da3c47a98764 (patch) | |
tree | 3fae034db15eeed8ca45b4db3d8f898df18a8e76 | |
parent | 26f7a3f185881c12081eb6d3cedbf9e637376d86 (diff) |
feat(core): Deno.core.heapStats() (#9659)
This commit implements "Deno.core.heapStats()" function
that allows to programatically measure isolate heap-usage.
-rw-r--r-- | cli/tests/heapstats.js | 36 | ||||
-rw-r--r-- | cli/tests/heapstats.js.out | 2 | ||||
-rw-r--r-- | cli/tests/integration_tests.rs | 5 | ||||
-rw-r--r-- | core/bindings.rs | 103 | ||||
-rw-r--r-- | core/lib.deno_core.d.ts | 3 |
5 files changed, 149 insertions, 0 deletions
diff --git a/cli/tests/heapstats.js b/cli/tests/heapstats.js new file mode 100644 index 000000000..0ed623e56 --- /dev/null +++ b/cli/tests/heapstats.js @@ -0,0 +1,36 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +function allocTest(alloc, allocAssert, deallocAssert) { + // Helper func that GCs then returns heapStats + const sample = () => { + // deno-lint-ignore no-undef + gc(); + return Deno.core.heapStats(); + }; + const delta = (t1, t2) => t2.usedHeapSize - t1.usedHeapSize; + + // Sample "clean" heapStats + const t1 = sample(); + + // Alloc + let x = alloc(); + const t2 = sample(); + allocAssert(delta(t1, t2)); + + // Free + x = null; + const t3 = sample(); + deallocAssert(delta(t2, t3)); +} + +function main() { + // Large-array test, 1M slot array consumes ~4MB (4B per slot) + allocTest( + () => new Array(1e6), + (delta) => console.log("Allocated:", Math.round(delta / 1e6) + "MB"), + (delta) => console.log("Freed:", Math.round(delta / 1e6) + "MB"), + ); +} + +main(); diff --git a/cli/tests/heapstats.js.out b/cli/tests/heapstats.js.out new file mode 100644 index 000000000..b75a755f8 --- /dev/null +++ b/cli/tests/heapstats.js.out @@ -0,0 +1,2 @@ +Allocated: 4MB +Freed: -4MB diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index e7fc7797b..96223c438 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -3217,6 +3217,11 @@ console.log("finish"); output: "exit_error42.ts.out", }); + itest!(heapstats { + args: "run --quiet --v8-flags=--expose-gc heapstats.js", + output: "heapstats.js.out", + }); + itest!(https_import { args: "run --quiet --reload --cert tls/RootCA.pem https_import.ts", output: "https_import.ts.out", diff --git a/core/bindings.rs b/core/bindings.rs index 23cd86a17..c5ed5b601 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -59,6 +59,9 @@ lazy_static! { v8::ExternalReference { function: get_proxy_details.map_fn_to() }, + v8::ExternalReference { + function: heap_stats.map_fn_to(), + }, ]); } @@ -135,6 +138,7 @@ pub fn initialize_context<'s>( set_func(scope, core_val, "deserialize", deserialize); set_func(scope, core_val, "getPromiseDetails", get_promise_details); set_func(scope, core_val, "getProxyDetails", get_proxy_details); + set_func(scope, core_val, "heapStats", heap_stats); let shared_key = v8::String::new(scope, "shared").unwrap(); core_val.set_accessor(scope, shared_key.into(), shared_getter); @@ -923,3 +927,102 @@ fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) { let exception = v8::Exception::type_error(scope, message); scope.throw_exception(exception); } + +fn heap_stats( + scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + fn set_prop( + scope: &mut v8::HandleScope, + obj: v8::Local<v8::Object>, + name: &'static str, + value: usize, + ) { + let key = v8::String::new(scope, name).unwrap(); + let val = v8::Number::new(scope, value as f64); + obj.set(scope, key.into(), val.into()); + } + + let s = get_heap_stats(scope); + + // TODO: use serde for this once we have serde_v8 + let obj = v8::Object::new(scope); + set_prop(scope, obj, "totalHeapSize", s.total_heap_size); + set_prop( + scope, + obj, + "totalHeapSizexecutable", + s.total_heap_size_executable, + ); + set_prop(scope, obj, "totalPhysicalSize", s.total_physical_size); + set_prop(scope, obj, "totalAvailableSize", s.total_available_size); + set_prop( + scope, + obj, + "totalGlobalHandlesSize", + s.total_global_handles_size, + ); + set_prop( + scope, + obj, + "usedGlobalHandlesSize", + s.used_global_handles_size, + ); + set_prop(scope, obj, "usedHeapSize", s.used_heap_size); + set_prop(scope, obj, "heapSizeLimit", s.heap_size_limit); + set_prop(scope, obj, "mallocedMemory", s.malloced_memory); + set_prop(scope, obj, "externalMemory", s.external_memory); + set_prop(scope, obj, "peakMallocedMemory", s.peak_malloced_memory); + set_prop( + scope, + obj, + "numberOfNativeContexts", + s.number_of_native_contexts, + ); + set_prop( + scope, + obj, + "numberOfDetachedContexts", + s.number_of_detached_contexts, + ); + + rv.set(obj.into()); +} + +// HeapStats stores values from a isolate.get_heap_statistics() call +struct HeapStats { + total_heap_size: usize, + total_heap_size_executable: usize, + total_physical_size: usize, + total_available_size: usize, + total_global_handles_size: usize, + used_global_handles_size: usize, + used_heap_size: usize, + heap_size_limit: usize, + malloced_memory: usize, + external_memory: usize, + peak_malloced_memory: usize, + number_of_native_contexts: usize, + number_of_detached_contexts: usize, +} +fn get_heap_stats(isolate: &mut v8::Isolate) -> HeapStats { + let mut s = v8::HeapStatistics::default(); + isolate.get_heap_statistics(&mut s); + + HeapStats { + total_heap_size: s.total_heap_size(), + total_heap_size_executable: s.total_heap_size_executable(), + total_physical_size: s.total_physical_size(), + total_available_size: s.total_available_size(), + total_global_handles_size: s.total_global_handles_size(), + used_global_handles_size: s.used_global_handles_size(), + used_heap_size: s.used_heap_size(), + heap_size_limit: s.heap_size_limit(), + malloced_memory: s.malloced_memory(), + external_memory: s.external_memory(), + peak_malloced_memory: s.peak_malloced_memory(), + number_of_native_contexts: s.number_of_native_contexts(), + number_of_detached_contexts: s.number_of_detached_contexts(), + } +} diff --git a/core/lib.deno_core.d.ts b/core/lib.deno_core.d.ts index 1efb92dc6..004ed0529 100644 --- a/core/lib.deno_core.d.ts +++ b/core/lib.deno_core.d.ts @@ -35,5 +35,8 @@ declare namespace Deno { /** Close the resource with the specified op id. */ function close(rid: number): void; + + /** Get heap stats for current isolate/worker */ + function heapStats(): Record<string, number>; } } |