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()
0 commit comments