Skip to content

Commit

Permalink
feat(core): Deno.core.heapStats() (#9659)
Browse files Browse the repository at this point in the history
This commit implements "Deno.core.heapStats()" function 
that allows to programatically measure isolate heap-usage.
  • Loading branch information
AaronO authored Mar 23, 2021
1 parent 26f7a3f commit 876f075
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 0 deletions.
36 changes: 36 additions & 0 deletions cli/tests/heapstats.js
Original file line number Diff line number Diff line change
@@ -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();
2 changes: 2 additions & 0 deletions cli/tests/heapstats.js.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Allocated: 4MB
Freed: -4MB
5 changes: 5 additions & 0 deletions cli/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
103 changes: 103 additions & 0 deletions core/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ lazy_static! {
v8::ExternalReference {
function: get_proxy_details.map_fn_to()
},
v8::ExternalReference {
function: heap_stats.map_fn_to(),
},
]);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(),
}
}
3 changes: 3 additions & 0 deletions core/lib.deno_core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
}

0 comments on commit 876f075

Please sign in to comment.