|
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" |
|
33 | 35 | #include <limits> |
34 | 36 | #include <memory> |
35 | 37 | #include <optional> |
| 38 | +#include <sstream> |
36 | 39 | #include <unordered_map> |
37 | 40 |
|
38 | 41 | namespace node { |
@@ -1101,6 +1104,9 @@ Environment::~Environment() { |
1101 | 1104 | if (heapsnapshot_near_heap_limit_callback_added_) { |
1102 | 1105 | RemoveHeapSnapshotNearHeapLimitCallback(0); |
1103 | 1106 | } |
| 1107 | + if (heap_profile_near_heap_limit_callback_added_) { |
| 1108 | + RemoveHeapProfileNearHeapLimitCallback(0); |
| 1109 | + } |
1104 | 1110 |
|
1105 | 1111 | isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback( |
1106 | 1112 | BuildEmbedderGraph, this); |
@@ -2159,14 +2165,49 @@ void Environment::TracePromises(PromiseHookType type, |
2159 | 2165 | PrintCurrentStackTrace(isolate); |
2160 | 2166 | } |
2161 | 2167 |
|
| 2168 | +// V8 only invokes the most recently registered near-heap-limit callback. |
| 2169 | +// To allow the snapshot and profile handlers to coexist on the same |
| 2170 | +// isolate, both register through this single V8-facing entry point. It fans |
| 2171 | +// out to whichever sub-handlers are active and returns the largest requested |
| 2172 | +// heap limit. |
2162 | 2173 | size_t Environment::NearHeapLimitCallback(void* data, |
2163 | 2174 | size_t current_heap_limit, |
2164 | 2175 | size_t initial_heap_limit) { |
2165 | 2176 | auto* env = static_cast<Environment*>(data); |
2166 | 2177 |
|
| 2178 | + // Profile and snapshot generation can allocate and invoke this callback |
| 2179 | + // recursively. Only extend the heap for the operation already in progress; |
| 2180 | + // do not start the other diagnostic from the nested invocation. |
| 2181 | + if (env->is_in_heapsnapshot_heap_limit_callback_) { |
| 2182 | + return HeapSnapshotNearHeapLimitCallback( |
| 2183 | + data, current_heap_limit, initial_heap_limit); |
| 2184 | + } |
| 2185 | + if (env->is_in_heap_profile_near_heap_limit_callback_) { |
| 2186 | + return HeapProfileNearHeapLimitCallback( |
| 2187 | + data, current_heap_limit, initial_heap_limit); |
| 2188 | + } |
| 2189 | + |
| 2190 | + size_t new_limit = current_heap_limit; |
| 2191 | + if (env->heapsnapshot_near_heap_limit_callback_added_) { |
| 2192 | + new_limit = std::max(new_limit, |
| 2193 | + HeapSnapshotNearHeapLimitCallback( |
| 2194 | + data, current_heap_limit, initial_heap_limit)); |
| 2195 | + } |
| 2196 | + if (env->heap_profile_near_heap_limit_callback_added_) { |
| 2197 | + new_limit = std::max(new_limit, |
| 2198 | + HeapProfileNearHeapLimitCallback( |
| 2199 | + data, current_heap_limit, initial_heap_limit)); |
| 2200 | + } |
| 2201 | + return new_limit; |
| 2202 | +} |
| 2203 | + |
| 2204 | +size_t Environment::HeapSnapshotNearHeapLimitCallback( |
| 2205 | + void* data, size_t current_heap_limit, size_t initial_heap_limit) { |
| 2206 | + auto* env = static_cast<Environment*>(data); |
| 2207 | + |
2167 | 2208 | Debug(env, |
2168 | 2209 | DebugCategory::DIAGNOSTICS, |
2169 | | - "Invoked NearHeapLimitCallback, processing=%d, " |
| 2210 | + "Invoked HeapSnapshotNearHeapLimitCallback, processing=%d, " |
2170 | 2211 | "current_limit=%" PRIu64 ", " |
2171 | 2212 | "initial_limit=%" PRIu64 "\n", |
2172 | 2213 | env->is_in_heapsnapshot_heap_limit_callback_, |
@@ -2288,6 +2329,85 @@ size_t Environment::NearHeapLimitCallback(void* data, |
2288 | 2329 | return new_limit; |
2289 | 2330 | } |
2290 | 2331 |
|
| 2332 | +size_t Environment::HeapProfileNearHeapLimitCallback( |
| 2333 | + void* data, size_t current_heap_limit, size_t initial_heap_limit) { |
| 2334 | + auto* env = static_cast<Environment*>(data); |
| 2335 | + |
| 2336 | + Debug(env, |
| 2337 | + DebugCategory::DIAGNOSTICS, |
| 2338 | + "Invoked HeapProfileNearHeapLimitCallback, processing=%d, " |
| 2339 | + "current_limit=%" PRIu64 ", " |
| 2340 | + "initial_limit=%" PRIu64 "\n", |
| 2341 | + env->is_in_heap_profile_near_heap_limit_callback_, |
| 2342 | + static_cast<uint64_t>(current_heap_limit), |
| 2343 | + static_cast<uint64_t>(initial_heap_limit)); |
| 2344 | + |
| 2345 | + const size_t max_young_gen_size = env->isolate_data()->max_young_gen_size; |
| 2346 | + const size_t new_limit = current_heap_limit + max_young_gen_size; |
| 2347 | + |
| 2348 | + if (env->is_in_heap_profile_near_heap_limit_callback_) { |
| 2349 | + return new_limit; |
| 2350 | + } |
| 2351 | + |
| 2352 | + env->is_in_heap_profile_near_heap_limit_callback_ = true; |
| 2353 | + auto reset_in_callback = OnScopeLeave( |
| 2354 | + [env]() { env->is_in_heap_profile_near_heap_limit_callback_ = false; }); |
| 2355 | + auto restore_initial_limit = OnScopeLeave( |
| 2356 | + [env]() { env->isolate()->AutomaticallyRestoreInitialHeapLimit(0.95); }); |
| 2357 | + auto uninstall_callback = [env]() { |
| 2358 | + env->RemoveHeapProfileNearHeapLimitCallback(0); |
| 2359 | + }; |
| 2360 | + |
| 2361 | + std::string dir = env->options()->diagnostic_dir; |
| 2362 | + if (dir.empty()) { |
| 2363 | + dir = Environment::GetCwd(env->exec_path_); |
| 2364 | + } |
| 2365 | + DiagnosticFilename name(env, "Heap", "heapprofile"); |
| 2366 | + std::string filename = dir + kPathSeparator + (*name); |
| 2367 | + |
| 2368 | + Debug(env, DebugCategory::DIAGNOSTICS, "Writing %s...\n", *name); |
| 2369 | + |
| 2370 | + std::ostringstream out; |
| 2371 | + if (!node::SerializeHeapProfile(env->isolate(), out)) { |
| 2372 | + Debug(env, |
| 2373 | + DebugCategory::DIAGNOSTICS, |
| 2374 | + "No sampling heap profile active; uninstalling callback.\n"); |
| 2375 | + uninstall_callback(); |
| 2376 | + return new_limit; |
| 2377 | + } |
| 2378 | + |
| 2379 | + std::string profile = std::move(out).str(); |
| 2380 | + uv_buf_t buffer = uv_buf_init(profile.data(), profile.size()); |
| 2381 | + const int err = WriteFileSync(filename.c_str(), buffer); |
| 2382 | + if (err != 0) { |
| 2383 | + FPrintF(stderr, |
| 2384 | + "Failed to write heap profile %s: %s\n", |
| 2385 | + filename, |
| 2386 | + uv_strerror(err)); |
| 2387 | + uninstall_callback(); |
| 2388 | + return new_limit; |
| 2389 | + } |
| 2390 | + |
| 2391 | + env->heap_limit_profile_taken_ += 1; |
| 2392 | + FPrintF(stderr, "Wrote heap profile to %s\n", filename); |
| 2393 | + |
| 2394 | + Debug(env, |
| 2395 | + DebugCategory::DIAGNOSTICS, |
| 2396 | + "%" PRIu32 "/%" PRIu32 " heap profiles written.\n", |
| 2397 | + env->heap_limit_profile_taken_, |
| 2398 | + env->heap_profile_near_heap_limit_); |
| 2399 | + |
| 2400 | + if (env->heap_limit_profile_taken_ == env->heap_profile_near_heap_limit_) { |
| 2401 | + Debug(env, |
| 2402 | + DebugCategory::DIAGNOSTICS, |
| 2403 | + "Removing the near heap limit callback"); |
| 2404 | + uninstall_callback(); |
| 2405 | + } |
| 2406 | + |
| 2407 | + // Returning a larger value is required by V8 even after the final write. |
| 2408 | + return new_limit; |
| 2409 | +} |
| 2410 | + |
2291 | 2411 | inline size_t Environment::SelfSize() const { |
2292 | 2412 | size_t size = sizeof(*this); |
2293 | 2413 | // Remove non pointer fields that will be tracked in MemoryInfo() |
|
0 commit comments