|
9 | 9 | #include "node_context_data.h" |
10 | 10 | #include "node_contextify.h" |
11 | 11 | #include "node_errors.h" |
| 12 | +#include "node_file_utils.h" |
12 | 13 | #include "node_internals.h" |
13 | 14 | #include "node_options-inl.h" |
14 | 15 | #include "node_process-inl.h" |
| 16 | +#include "node_profiling.h" |
15 | 17 | #include "node_shadow_realm.h" |
16 | 18 | #include "node_snapshotable.h" |
17 | 19 | #include "node_v8_platform-inl.h" |
|
36 | 38 | #include <limits> |
37 | 39 | #include <memory> |
38 | 40 | #include <optional> |
| 41 | +#include <sstream> |
39 | 42 | #include <unordered_map> |
40 | 43 |
|
41 | 44 | namespace node { |
@@ -1112,6 +1115,9 @@ Environment::~Environment() { |
1112 | 1115 | if (heapsnapshot_near_heap_limit_callback_added_) { |
1113 | 1116 | RemoveHeapSnapshotNearHeapLimitCallback(0); |
1114 | 1117 | } |
| 1118 | + if (heap_profile_near_heap_limit_callback_added_) { |
| 1119 | + RemoveHeapProfileNearHeapLimitCallback(0); |
| 1120 | + } |
1115 | 1121 |
|
1116 | 1122 | isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback( |
1117 | 1123 | BuildEmbedderGraph, this); |
@@ -2179,14 +2185,49 @@ void Environment::TracePromises(PromiseHookType type, |
2179 | 2185 | PrintCurrentStackTrace(isolate); |
2180 | 2186 | } |
2181 | 2187 |
|
| 2188 | +// V8 only invokes the most recently registered near-heap-limit callback. |
| 2189 | +// To allow the snapshot and profile handlers to coexist on the same |
| 2190 | +// isolate, both register through this single V8-facing entry point. It fans |
| 2191 | +// out to whichever sub-handlers are active and returns the largest requested |
| 2192 | +// heap limit. |
2182 | 2193 | size_t Environment::NearHeapLimitCallback(void* data, |
2183 | 2194 | size_t current_heap_limit, |
2184 | 2195 | size_t initial_heap_limit) { |
2185 | 2196 | auto* env = static_cast<Environment*>(data); |
2186 | 2197 |
|
| 2198 | + // Profile and snapshot generation can allocate and invoke this callback |
| 2199 | + // recursively. Only extend the heap for the operation already in progress; |
| 2200 | + // do not start the other diagnostic from the nested invocation. |
| 2201 | + if (env->is_in_heapsnapshot_heap_limit_callback_) { |
| 2202 | + return HeapSnapshotNearHeapLimitCallback( |
| 2203 | + data, current_heap_limit, initial_heap_limit); |
| 2204 | + } |
| 2205 | + if (env->is_in_heap_profile_near_heap_limit_callback_) { |
| 2206 | + return HeapProfileNearHeapLimitCallback( |
| 2207 | + data, current_heap_limit, initial_heap_limit); |
| 2208 | + } |
| 2209 | + |
| 2210 | + size_t new_limit = current_heap_limit; |
| 2211 | + if (env->heapsnapshot_near_heap_limit_callback_added_) { |
| 2212 | + new_limit = std::max(new_limit, |
| 2213 | + HeapSnapshotNearHeapLimitCallback( |
| 2214 | + data, current_heap_limit, initial_heap_limit)); |
| 2215 | + } |
| 2216 | + if (env->heap_profile_near_heap_limit_callback_added_) { |
| 2217 | + new_limit = std::max(new_limit, |
| 2218 | + HeapProfileNearHeapLimitCallback( |
| 2219 | + data, current_heap_limit, initial_heap_limit)); |
| 2220 | + } |
| 2221 | + return new_limit; |
| 2222 | +} |
| 2223 | + |
| 2224 | +size_t Environment::HeapSnapshotNearHeapLimitCallback( |
| 2225 | + void* data, size_t current_heap_limit, size_t initial_heap_limit) { |
| 2226 | + auto* env = static_cast<Environment*>(data); |
| 2227 | + |
2187 | 2228 | Debug(env, |
2188 | 2229 | DebugCategory::DIAGNOSTICS, |
2189 | | - "Invoked NearHeapLimitCallback, processing=%d, " |
| 2230 | + "Invoked HeapSnapshotNearHeapLimitCallback, processing=%d, " |
2190 | 2231 | "current_limit=%" PRIu64 ", " |
2191 | 2232 | "initial_limit=%" PRIu64 "\n", |
2192 | 2233 | env->is_in_heapsnapshot_heap_limit_callback_, |
@@ -2308,6 +2349,85 @@ size_t Environment::NearHeapLimitCallback(void* data, |
2308 | 2349 | return new_limit; |
2309 | 2350 | } |
2310 | 2351 |
|
| 2352 | +size_t Environment::HeapProfileNearHeapLimitCallback( |
| 2353 | + void* data, size_t current_heap_limit, size_t initial_heap_limit) { |
| 2354 | + auto* env = static_cast<Environment*>(data); |
| 2355 | + |
| 2356 | + Debug(env, |
| 2357 | + DebugCategory::DIAGNOSTICS, |
| 2358 | + "Invoked HeapProfileNearHeapLimitCallback, processing=%d, " |
| 2359 | + "current_limit=%" PRIu64 ", " |
| 2360 | + "initial_limit=%" PRIu64 "\n", |
| 2361 | + env->is_in_heap_profile_near_heap_limit_callback_, |
| 2362 | + static_cast<uint64_t>(current_heap_limit), |
| 2363 | + static_cast<uint64_t>(initial_heap_limit)); |
| 2364 | + |
| 2365 | + const size_t max_young_gen_size = env->isolate_data()->max_young_gen_size; |
| 2366 | + const size_t new_limit = current_heap_limit + max_young_gen_size; |
| 2367 | + |
| 2368 | + if (env->is_in_heap_profile_near_heap_limit_callback_) { |
| 2369 | + return new_limit; |
| 2370 | + } |
| 2371 | + |
| 2372 | + env->is_in_heap_profile_near_heap_limit_callback_ = true; |
| 2373 | + auto reset_in_callback = OnScopeLeave( |
| 2374 | + [env]() { env->is_in_heap_profile_near_heap_limit_callback_ = false; }); |
| 2375 | + auto restore_initial_limit = OnScopeLeave( |
| 2376 | + [env]() { env->isolate()->AutomaticallyRestoreInitialHeapLimit(0.95); }); |
| 2377 | + auto uninstall_callback = [env]() { |
| 2378 | + env->RemoveHeapProfileNearHeapLimitCallback(0); |
| 2379 | + }; |
| 2380 | + |
| 2381 | + std::string dir = env->options()->diagnostic_dir; |
| 2382 | + if (dir.empty()) { |
| 2383 | + dir = Environment::GetCwd(env->exec_path_); |
| 2384 | + } |
| 2385 | + DiagnosticFilename name(env, "Heap", "heapprofile"); |
| 2386 | + std::string filename = dir + kPathSeparator + (*name); |
| 2387 | + |
| 2388 | + Debug(env, DebugCategory::DIAGNOSTICS, "Writing %s...\n", *name); |
| 2389 | + |
| 2390 | + std::ostringstream out; |
| 2391 | + if (!node::SerializeHeapProfile(env->isolate(), out)) { |
| 2392 | + Debug(env, |
| 2393 | + DebugCategory::DIAGNOSTICS, |
| 2394 | + "No sampling heap profile active; uninstalling callback.\n"); |
| 2395 | + uninstall_callback(); |
| 2396 | + return new_limit; |
| 2397 | + } |
| 2398 | + |
| 2399 | + std::string profile = std::move(out).str(); |
| 2400 | + uv_buf_t buffer = uv_buf_init(profile.data(), profile.size()); |
| 2401 | + const int err = WriteFileSync(filename.c_str(), buffer); |
| 2402 | + if (err != 0) { |
| 2403 | + FPrintF(stderr, |
| 2404 | + "Failed to write heap profile %s: %s\n", |
| 2405 | + filename, |
| 2406 | + uv_strerror(err)); |
| 2407 | + uninstall_callback(); |
| 2408 | + return new_limit; |
| 2409 | + } |
| 2410 | + |
| 2411 | + env->heap_limit_profile_taken_ += 1; |
| 2412 | + FPrintF(stderr, "Wrote heap profile to %s\n", filename); |
| 2413 | + |
| 2414 | + Debug(env, |
| 2415 | + DebugCategory::DIAGNOSTICS, |
| 2416 | + "%" PRIu32 "/%" PRIu32 " heap profiles written.\n", |
| 2417 | + env->heap_limit_profile_taken_, |
| 2418 | + env->heap_profile_near_heap_limit_); |
| 2419 | + |
| 2420 | + if (env->heap_limit_profile_taken_ == env->heap_profile_near_heap_limit_) { |
| 2421 | + Debug(env, |
| 2422 | + DebugCategory::DIAGNOSTICS, |
| 2423 | + "Removing the near heap limit callback"); |
| 2424 | + uninstall_callback(); |
| 2425 | + } |
| 2426 | + |
| 2427 | + // Returning a larger value is required by V8 even after the final write. |
| 2428 | + return new_limit; |
| 2429 | +} |
| 2430 | + |
2311 | 2431 | inline size_t Environment::SelfSize() const { |
2312 | 2432 | size_t size = sizeof(*this); |
2313 | 2433 | // Remove non pointer fields that will be tracked in MemoryInfo() |
|
0 commit comments