diff --git a/router-bridge/js-src/plan_worker.ts b/router-bridge/js-src/plan_worker.ts index 23b72407e..858691dac 100644 --- a/router-bridge/js-src/plan_worker.ts +++ b/router-bridge/js-src/plan_worker.ts @@ -15,6 +15,10 @@ declare namespace Deno { } } +function memoryUsage(): MemoryUsage { + return Deno.core.ops.op_runtime_memory_usage(); +} + let logFunction: (message: string) => void; declare let logger: { trace: typeof logFunction; @@ -24,6 +28,16 @@ declare let logger: { error: typeof logFunction; }; +export interface MemoryUsage { + /** The total size of the heap for V8, in bytes. */ + heapTotal: number; + /** The amount of the heap used for V8, in bytes. */ + heapUsed: number; + /** Memory, in bytes, associated with JavaScript objects outside of the + * JavaScript isolate. */ + external: number; +} + enum PlannerEventKind { UpdateSchema = "UpdateSchema", Plan = "Plan", @@ -32,6 +46,7 @@ enum PlannerEventKind { Introspect = "Introspect", Signature = "Signature", Subgraphs = "Subgraphs", + GetHeapStatistics = "GetHeapStatistics", } interface UpdateSchemaEvent { @@ -75,6 +90,11 @@ interface Exit { kind: PlannerEventKind.Exit; schemaId: number; } + +interface GetHeapStatisticsEvent { + kind: PlannerEventKind.GetHeapStatistics; +} + type PlannerEvent = | UpdateSchemaEvent | PlanEvent @@ -82,6 +102,7 @@ type PlannerEvent = | IntrospectEvent | SignatureEvent | SubgraphsEvent + | GetHeapStatisticsEvent | Exit; type PlannerEventWithId = { id: string; @@ -92,12 +113,14 @@ type WorkerResultWithId = { id?: string; payload: WorkerResult; }; + type WorkerResult = | PlanResult | ApiSchemaResult | ExecutionResult | Map - | String; + | String + | MemoryUsageResult; // Plan result type PlanResult = | ExecutionResultWithUsageReporting @@ -105,6 +128,11 @@ type PlanResult = type ApiSchemaResult = { schema: string; }; +type MemoryUsageResult = { + heapTotal: number; + heapUsed: number; + external: number; +}; type FatalError = { errors: (JsError | WorkerGraphQLError)[]; @@ -252,6 +280,7 @@ async function run() { try { const { id, payload: event } = await receive(); messageId = id; + try { switch (event?.kind) { case PlannerEventKind.UpdateSchema: @@ -290,6 +319,17 @@ async function run() { await send({ id, payload: subgraphs }); break; + case PlannerEventKind.GetHeapStatistics: + const mem = memoryUsage(); + + const result: MemoryUsageResult = { + heapTotal: mem.heapTotal, + heapUsed: mem.heapUsed, + external: mem.external, + }; + + await send({ id, payload: result }); + break; case PlannerEventKind.Exit: planners.delete(event.schemaId); if (planners.size == 0) { diff --git a/router-bridge/src/planner.rs b/router-bridge/src/planner.rs index 5bdc08fbc..c93ec8f43 100644 --- a/router-bridge/src/planner.rs +++ b/router-bridge/src/planner.rs @@ -398,6 +398,18 @@ where } } +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +/// deno's heap statistics +pub struct HeapStatistics { + /// total size of the heap for V8, in bytes + pub heap_total: u64, + /// amount of the heap used for V8, in bytes + pub heap_used: u64, + /// emory, in bytes, associated with JavaScript objects outside of the JavaScript isolate + pub external: u64, +} + /// A Deno worker backed query Planner. pub struct Planner @@ -586,6 +598,11 @@ where }) .await } + + /// Get deno's heap statistics + pub async fn get_heap_statistics(&self) -> Result { + self.worker.request(PlanCmd::GetHeapStatistics).await + } } impl Drop for Planner @@ -647,7 +664,10 @@ enum PlanCmd { Subgraphs { schema_id: u64 }, #[serde(rename_all = "camelCase")] Exit { schema_id: u64 }, + #[serde(rename_all = "camelCase")] + GetHeapStatistics, } + #[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(rename_all = "camelCase")] /// Query planner configuration @@ -767,8 +787,8 @@ pub struct QueryPlannerDebugConfig { } #[cfg(test)] mod tests { + use futures::stream; use futures::stream::StreamExt; - use futures::stream::{self}; use std::collections::BTreeMap; @@ -1871,6 +1891,19 @@ ofType { insta::assert_snapshot!(schema); } } + + #[tokio::test] + async fn heap_statistics() { + let planner = + Planner::::new(SCHEMA.to_string(), QueryPlannerConfig::default()) + .await + .unwrap(); + + let _subgraphs = planner.subgraphs().await.unwrap(); + let statistics = planner.get_heap_statistics().await.unwrap(); + + println!("statistics: {statistics:?}"); + } } #[cfg(test)] diff --git a/router-bridge/src/worker.rs b/router-bridge/src/worker.rs index b789ee415..8f9af1484 100644 --- a/router-bridge/src/worker.rs +++ b/router-bridge/src/worker.rs @@ -1,7 +1,7 @@ use crate::error::Error; use async_channel::{bounded, Receiver, Sender}; -use deno_core::Op; use deno_core::{op, Extension, OpState}; +use deno_core::{v8, Op}; use rand::rngs::StdRng; use rand::{thread_rng, Rng}; use serde::de::DeserializeOwned; @@ -79,6 +79,7 @@ impl JsWorker { log_warn::DECL, log_error::DECL, op_crypto_get_random_values::DECL, + op_runtime_memory_usage::DECL, ]), op_state_fn: Some(Box::new(move |state| { state.put(response_sender.clone()); @@ -302,6 +303,30 @@ fn op_crypto_get_random_values(state: &mut OpState, out: &mut [u8]) -> Result<() Ok(()) } +// HeapStats stores values from a isolate.get_heap_statistics() call +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct MemoryUsage { + //rss: usize, + heap_total: usize, + heap_used: usize, + external: usize, +} + +// from https://github.com/denoland/deno/blob/897159dc6e1b2319cf2f5f09d8d6cecc0d3175fa/runtime/ops/os/mod.rs#L329 +// tested in planner.rs +#[op(v8)] +fn op_runtime_memory_usage(scope: &mut v8::HandleScope<'_>) -> MemoryUsage { + let mut s = v8::HeapStatistics::default(); + scope.get_heap_statistics(&mut s); + MemoryUsage { + //rss: rss(), + heap_total: s.total_heap_size(), + heap_used: s.used_heap_size(), + external: s.external_memory(), + } +} + #[cfg(test)] mod worker_tests { use super::JsWorker;