Skip to content

Commit

Permalink
get heap statistics from deno (#558)
Browse files Browse the repository at this point in the history
This adds a planner event to request V8 heap statistics.

This data is gathered from the JS side because the related methods from
the rust side require a `&mut` access to the runtime, which is
impossible since it already runs in an infinite loop.
Since we elected to not run the prelude parts of deno which register
functions like `Deno.memory_usage`, we intern the
[op_runtime_memory_usage
function](https://github.com/denoland/deno/blob/897159dc6e1b2319cf2f5f09d8d6cecc0d3175fa/runtime/ops/os/mod.rs#L329)
and import it directly as a host function, as we've done already with
`op_crypto_get_random_values`. To avoid hosting too much code, the `rss`
field is not filled (it needs platform specific functions), since that
info can generally be obtained by other means.
  • Loading branch information
Geal authored Aug 8, 2024
1 parent 836ef3d commit 7ad0665
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 3 deletions.
42 changes: 41 additions & 1 deletion router-bridge/js-src/plan_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand All @@ -32,6 +46,7 @@ enum PlannerEventKind {
Introspect = "Introspect",
Signature = "Signature",
Subgraphs = "Subgraphs",
GetHeapStatistics = "GetHeapStatistics",
}

interface UpdateSchemaEvent {
Expand Down Expand Up @@ -75,13 +90,19 @@ interface Exit {
kind: PlannerEventKind.Exit;
schemaId: number;
}

interface GetHeapStatisticsEvent {
kind: PlannerEventKind.GetHeapStatistics;
}

type PlannerEvent =
| UpdateSchemaEvent
| PlanEvent
| ApiSchemaEvent
| IntrospectEvent
| SignatureEvent
| SubgraphsEvent
| GetHeapStatisticsEvent
| Exit;
type PlannerEventWithId = {
id: string;
Expand All @@ -92,19 +113,26 @@ type WorkerResultWithId = {
id?: string;
payload: WorkerResult;
};

type WorkerResult =
| PlanResult
| ApiSchemaResult
| ExecutionResult
| Map<string, string>
| String;
| String
| MemoryUsageResult;
// Plan result
type PlanResult =
| ExecutionResultWithUsageReporting<QueryPlanResult>
| FatalError;
type ApiSchemaResult = {
schema: string;
};
type MemoryUsageResult = {
heapTotal: number;
heapUsed: number;
external: number;
};

type FatalError = {
errors: (JsError | WorkerGraphQLError)[];
Expand Down Expand Up @@ -252,6 +280,7 @@ async function run() {
try {
const { id, payload: event } = await receive();
messageId = id;

try {
switch (event?.kind) {
case PlannerEventKind.UpdateSchema:
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 34 additions & 1 deletion router-bridge/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>
Expand Down Expand Up @@ -586,6 +598,11 @@ where
})
.await
}

/// Get deno's heap statistics
pub async fn get_heap_statistics(&self) -> Result<HeapStatistics, crate::error::Error> {
self.worker.request(PlanCmd::GetHeapStatistics).await
}
}

impl<T> Drop for Planner<T>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1871,6 +1891,19 @@ ofType {
insta::assert_snapshot!(schema);
}
}

#[tokio::test]
async fn heap_statistics() {
let planner =
Planner::<serde_json::Value>::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)]
Expand Down
27 changes: 26 additions & 1 deletion router-bridge/src/worker.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 7ad0665

Please sign in to comment.