|
3 | 3 | #include "async_wrap.h" |
4 | 4 | #include "base_object-inl.h" |
5 | 5 | #include "debug_utils-inl.h" |
| 6 | +#include "diagnosticfilename-inl.h" |
6 | 7 | #include "memory_tracker-inl.h" |
7 | 8 | #include "node_buffer.h" |
8 | 9 | #include "node_context_data.h" |
|
24 | 25 | #include <cinttypes> |
25 | 26 | #include <cstdio> |
26 | 27 | #include <iostream> |
| 28 | +#include <limits> |
27 | 29 | #include <memory> |
28 | 30 |
|
29 | 31 | namespace node { |
@@ -479,6 +481,11 @@ Environment::~Environment() { |
479 | 481 | // FreeEnvironment() should have set this. |
480 | 482 | CHECK(is_stopping()); |
481 | 483 |
|
| 484 | + if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) { |
| 485 | + isolate_->RemoveNearHeapLimitCallback(Environment::NearHeapLimitCallback, |
| 486 | + 0); |
| 487 | + } |
| 488 | + |
482 | 489 | isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback( |
483 | 490 | BuildEmbedderGraph, this); |
484 | 491 |
|
@@ -1402,6 +1409,25 @@ void Environment::DeserializeProperties(const EnvSerializeInfo* info) { |
1402 | 1409 | CHECK_EQ(ctx_from_snapshot, ctx); |
1403 | 1410 | } |
1404 | 1411 |
|
| 1412 | +uint64_t GuessMemoryAvailableToTheProcess() { |
| 1413 | + uint64_t free_in_system = uv_get_free_memory(); |
| 1414 | + size_t allowed = uv_get_constrained_memory(); |
| 1415 | + if (allowed == 0) { |
| 1416 | + return free_in_system; |
| 1417 | + } |
| 1418 | + size_t rss; |
| 1419 | + int err = uv_resident_set_memory(&rss); |
| 1420 | + if (err) { |
| 1421 | + return free_in_system; |
| 1422 | + } |
| 1423 | + if (allowed < rss) { |
| 1424 | + // Something is probably wrong. Fallback to the free memory. |
| 1425 | + return free_in_system; |
| 1426 | + } |
| 1427 | + // There may still be room for swap, but we will just leave it here. |
| 1428 | + return allowed - rss; |
| 1429 | +} |
| 1430 | + |
1405 | 1431 | void Environment::BuildEmbedderGraph(Isolate* isolate, |
1406 | 1432 | EmbedderGraph* graph, |
1407 | 1433 | void* data) { |
@@ -1414,6 +1440,126 @@ void Environment::BuildEmbedderGraph(Isolate* isolate, |
1414 | 1440 | }); |
1415 | 1441 | } |
1416 | 1442 |
|
| 1443 | +size_t Environment::NearHeapLimitCallback(void* data, |
| 1444 | + size_t current_heap_limit, |
| 1445 | + size_t initial_heap_limit) { |
| 1446 | + Environment* env = static_cast<Environment*>(data); |
| 1447 | + |
| 1448 | + Debug(env, |
| 1449 | + DebugCategory::DIAGNOSTICS, |
| 1450 | + "Invoked NearHeapLimitCallback, processing=%d, " |
| 1451 | + "current_limit=%" PRIu64 ", " |
| 1452 | + "initial_limit=%" PRIu64 "\n", |
| 1453 | + env->is_processing_heap_limit_callback_, |
| 1454 | + static_cast<uint64_t>(current_heap_limit), |
| 1455 | + static_cast<uint64_t>(initial_heap_limit)); |
| 1456 | + |
| 1457 | + size_t max_young_gen_size = env->isolate_data()->max_young_gen_size; |
| 1458 | + size_t young_gen_size = 0; |
| 1459 | + size_t old_gen_size = 0; |
| 1460 | + |
| 1461 | + v8::HeapSpaceStatistics stats; |
| 1462 | + size_t num_heap_spaces = env->isolate()->NumberOfHeapSpaces(); |
| 1463 | + for (size_t i = 0; i < num_heap_spaces; ++i) { |
| 1464 | + env->isolate()->GetHeapSpaceStatistics(&stats, i); |
| 1465 | + if (strcmp(stats.space_name(), "new_space") == 0 || |
| 1466 | + strcmp(stats.space_name(), "new_large_object_space") == 0) { |
| 1467 | + young_gen_size += stats.space_used_size(); |
| 1468 | + } else { |
| 1469 | + old_gen_size += stats.space_used_size(); |
| 1470 | + } |
| 1471 | + } |
| 1472 | + |
| 1473 | + Debug(env, |
| 1474 | + DebugCategory::DIAGNOSTICS, |
| 1475 | + "max_young_gen_size=%" PRIu64 ", " |
| 1476 | + "young_gen_size=%" PRIu64 ", " |
| 1477 | + "old_gen_size=%" PRIu64 ", " |
| 1478 | + "total_size=%" PRIu64 "\n", |
| 1479 | + static_cast<uint64_t>(max_young_gen_size), |
| 1480 | + static_cast<uint64_t>(young_gen_size), |
| 1481 | + static_cast<uint64_t>(old_gen_size), |
| 1482 | + static_cast<uint64_t>(young_gen_size + old_gen_size)); |
| 1483 | + |
| 1484 | + uint64_t available = GuessMemoryAvailableToTheProcess(); |
| 1485 | + // TODO(joyeecheung): get a better estimate about the native memory |
| 1486 | + // usage into the overhead, e.g. based on the count of objects. |
| 1487 | + uint64_t estimated_overhead = max_young_gen_size; |
| 1488 | + Debug(env, |
| 1489 | + DebugCategory::DIAGNOSTICS, |
| 1490 | + "Estimated available memory=%" PRIu64 ", " |
| 1491 | + "estimated overhead=%" PRIu64 "\n", |
| 1492 | + static_cast<uint64_t>(available), |
| 1493 | + static_cast<uint64_t>(estimated_overhead)); |
| 1494 | + |
| 1495 | + // This might be hit when the snapshot is being taken in another |
| 1496 | + // NearHeapLimitCallback invocation. |
| 1497 | + // When taking the snapshot, objects in the young generation may be |
| 1498 | + // promoted to the old generation, result in increased heap usage, |
| 1499 | + // but it should be no more than the young generation size. |
| 1500 | + // Ideally, this should be as small as possible - the heap limit |
| 1501 | + // can only be restored when the heap usage falls down below the |
| 1502 | + // new limit, so in a heap with unbounded growth the isolate |
| 1503 | + // may eventually crash with this new limit - effectively raising |
| 1504 | + // the heap limit to the new one. |
| 1505 | + if (env->is_processing_heap_limit_callback_) { |
| 1506 | + size_t new_limit = initial_heap_limit + max_young_gen_size; |
| 1507 | + Debug(env, |
| 1508 | + DebugCategory::DIAGNOSTICS, |
| 1509 | + "Not generating snapshots in nested callback. " |
| 1510 | + "new_limit=%" PRIu64 "\n", |
| 1511 | + static_cast<uint64_t>(new_limit)); |
| 1512 | + return new_limit; |
| 1513 | + } |
| 1514 | + |
| 1515 | + // Estimate whether the snapshot is going to use up all the memory |
| 1516 | + // available to the process. If so, just give up to prevent the system |
| 1517 | + // from killing the process for a system OOM. |
| 1518 | + if (estimated_overhead > available) { |
| 1519 | + Debug(env, |
| 1520 | + DebugCategory::DIAGNOSTICS, |
| 1521 | + "Not generating snapshots because it's too risky.\n"); |
| 1522 | + env->isolate()->RemoveNearHeapLimitCallback(NearHeapLimitCallback, |
| 1523 | + initial_heap_limit); |
| 1524 | + return current_heap_limit; |
| 1525 | + } |
| 1526 | + |
| 1527 | + // Take the snapshot synchronously. |
| 1528 | + env->is_processing_heap_limit_callback_ = true; |
| 1529 | + |
| 1530 | + std::string dir = env->options()->diagnostic_dir; |
| 1531 | + if (dir.empty()) { |
| 1532 | + dir = env->GetCwd(); |
| 1533 | + } |
| 1534 | + DiagnosticFilename name(env, "Heap", "heapsnapshot"); |
| 1535 | + std::string filename = dir + kPathSeparator + (*name); |
| 1536 | + |
| 1537 | + Debug(env, DebugCategory::DIAGNOSTICS, "Start generating %s...\n", *name); |
| 1538 | + |
| 1539 | + // Remove the callback first in case it's triggered when generating |
| 1540 | + // the snapshot. |
| 1541 | + env->isolate()->RemoveNearHeapLimitCallback(NearHeapLimitCallback, |
| 1542 | + initial_heap_limit); |
| 1543 | + |
| 1544 | + heap::WriteSnapshot(env->isolate(), filename.c_str()); |
| 1545 | + env->heap_limit_snapshot_taken_ += 1; |
| 1546 | + |
| 1547 | + // Don't take more snapshots than the number specified by |
| 1548 | + // --heapsnapshot-near-heap-limit. |
| 1549 | + if (env->heap_limit_snapshot_taken_ < |
| 1550 | + env->options_->heap_snapshot_near_heap_limit) { |
| 1551 | + env->isolate()->AddNearHeapLimitCallback(NearHeapLimitCallback, env); |
| 1552 | + } |
| 1553 | + |
| 1554 | + FPrintF(stderr, "Wrote snapshot to %s\n", filename.c_str()); |
| 1555 | + // Tell V8 to reset the heap limit once the heap usage falls down to |
| 1556 | + // 95% of the initial limit. |
| 1557 | + env->isolate()->AutomaticallyRestoreInitialHeapLimit(0.95); |
| 1558 | + |
| 1559 | + env->is_processing_heap_limit_callback_ = false; |
| 1560 | + return initial_heap_limit; |
| 1561 | +} |
| 1562 | + |
1417 | 1563 | inline size_t Environment::SelfSize() const { |
1418 | 1564 | size_t size = sizeof(*this); |
1419 | 1565 | // Remove non pointer fields that will be tracked in MemoryInfo() |
|