Skip to main content

nodejs/stdlib/
trace_events.rs

1//! Node `trace_events` module.
2//!
3//! Honest scope note: node-js has NO trace-event sink — it emits no
4//! Chrome-trace / perfetto JSON and writes no `node_trace.*.log` file. What is
5//! real here is the *object model and its state*: `createTracing({categories})`
6//! returns a `Tracing` whose `.enable()`/`.disable()` genuinely flip its
7//! `enabled` flag, whose `.categories` is the comma-joined category string, and
8//! whose enabled categories are reflected by `getEnabledCategories()`. The
9//! object behaves exactly as specified; it simply does not produce trace output.
10//! This is a truthful "state, not sink" implementation, never a silent fake.
11
12use crate::host::{type_error, with_host, JsObj};
13use fusevm::Value;
14use indexmap::IndexMap;
15use std::cell::RefCell;
16
17/// Module-level methods (`require('trace_events').createTracing(...)`).
18pub const METHODS: &[&str] = &["createTracing", "getEnabledCategories"];
19
20/// Instance methods on a `Tracing` object (`.enabled`/`.categories` are plain
21/// data properties, not methods).
22pub const TRACING_METHODS: &[&str] = &["enable", "disable"];
23
24thread_local! {
25    /// Category → number of currently-enabled `Tracing` objects that include it.
26    /// A category is "enabled" while its count is > 0; insertion order drives the
27    /// `getEnabledCategories()` listing.
28    static ENABLED: RefCell<IndexMap<String, usize>> = RefCell::new(IndexMap::new());
29}
30
31// ── module dispatch ──────────────────────────────────────────────────────────
32
33pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
34    match method {
35        "createTracing" => Some(Ok(new_tracing(args.first()))),
36        "getEnabledCategories" => Some(Ok(get_enabled_categories())),
37        _ => None,
38    }
39}
40
41/// `new trace_events.Tracing(...)` is not part of Node's public API, but the
42/// constructor path builds the same object as `createTracing`.
43pub fn construct(args: &[Value]) -> Result<Value, String> {
44    Ok(new_tracing(args.first()))
45}
46
47/// Build a `Tracing` from an options object `{ categories: [...] }`. `categories`
48/// (comma-joined) and `enabled` (starting `false`) are stored as real data props
49/// so `tracing.categories` / `tracing.enabled` read directly.
50fn new_tracing(options: Option<&Value>) -> Value {
51    let cats = read_categories(options);
52    with_host(|h| {
53        let joined = h.new_str(cats.join(","));
54        let cat_arr: Vec<Value> = cats.iter().map(|c| h.new_str(c.clone())).collect();
55        let cat_arr = h.new_array(cat_arr);
56        let mut m = IndexMap::new();
57        m.insert("@@native".to_string(), h.new_str("Tracing"));
58        m.insert("@@categories".to_string(), cat_arr);
59        m.insert("categories".to_string(), joined);
60        m.insert("enabled".to_string(), Value::Bool(false));
61        h.new_object(m)
62    })
63}
64
65/// `getEnabledCategories()` — the comma-joined set of categories enabled by any
66/// live `Tracing`, or `undefined` when none are enabled.
67fn get_enabled_categories() -> Value {
68    let joined = ENABLED.with(|e| {
69        e.borrow()
70            .iter()
71            .filter(|(_, &n)| n > 0)
72            .map(|(k, _)| k.clone())
73            .collect::<Vec<_>>()
74            .join(",")
75    });
76    if joined.is_empty() {
77        Value::Undef
78    } else {
79        with_host(|h| h.new_str(joined))
80    }
81}
82
83// ── instance dispatch ────────────────────────────────────────────────────────
84
85pub fn instance_call(recv: &Value, method: &str, _args: Vec<Value>) -> Result<Value, String> {
86    match method {
87        "enable" => {
88            set_enabled(recv, true);
89            Ok(recv.clone())
90        }
91        "disable" => {
92            set_enabled(recv, false);
93            Ok(recv.clone())
94        }
95        _ => Err(type_error(&format!("tracing.{method} is not a function"))),
96    }
97}
98
99/// Flip the `enabled` flag and adjust the thread-local category counts. Toggling
100/// to the state it is already in is a no-op (Node coalesces repeat calls).
101fn set_enabled(recv: &Value, on: bool) {
102    let already = matches!(get_prop(recv, "enabled"), Some(Value::Bool(true)));
103    if already == on {
104        return;
105    }
106    let cats = categories_of(recv);
107    with_host(|h| {
108        if let Some(JsObj::Object(p)) = h.get_mut(recv) {
109            p.insert("enabled".to_string(), Value::Bool(on));
110        }
111    });
112    ENABLED.with(|e| {
113        let mut e = e.borrow_mut();
114        for c in cats {
115            let slot = e.entry(c).or_insert(0);
116            if on {
117                *slot += 1;
118            } else if *slot > 0 {
119                *slot -= 1;
120            }
121        }
122    });
123}
124
125// ── helpers ──────────────────────────────────────────────────────────────────
126
127fn get_prop(recv: &Value, key: &str) -> Option<Value> {
128    with_host(|h| match h.get(recv) {
129        Some(JsObj::Object(p)) => p.get(key).cloned(),
130        _ => None,
131    })
132}
133
134/// The `Tracing`'s categories (from its hidden `@@categories` array).
135fn categories_of(recv: &Value) -> Vec<String> {
136    with_host(|h| match h.get(recv) {
137        Some(JsObj::Object(p)) => match p.get("@@categories").and_then(|a| h.get(a)) {
138            Some(JsObj::Array(items)) => items.iter().map(|v| h.str_of(v)).collect(),
139            _ => Vec::new(),
140        },
141        _ => Vec::new(),
142    })
143}
144
145/// Extract `options.categories` (an array of strings) from a `createTracing`
146/// options object.
147fn read_categories(options: Option<&Value>) -> Vec<String> {
148    with_host(|h| {
149        let Some(o) = options else { return Vec::new() };
150        let Some(JsObj::Object(p)) = h.get(o) else {
151            return Vec::new();
152        };
153        match p.get("categories").and_then(|c| h.get(c)) {
154            Some(JsObj::Array(items)) => items.iter().map(|v| h.str_of(v)).collect(),
155            _ => Vec::new(),
156        }
157    })
158}