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"
22
23
#include < algorithm>
23
24
#include < atomic>
24
25
#include < cstdio>
26
+ #include < limits>
25
27
#include < memory>
26
28
27
29
namespace node {
@@ -465,6 +467,11 @@ Environment::~Environment() {
465
467
// FreeEnvironment() should have set this.
466
468
CHECK (is_stopping ());
467
469
470
+ if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) {
471
+ isolate_->RemoveNearHeapLimitCallback (Environment::NearHeapLimitCallback,
472
+ 0 );
473
+ }
474
+
468
475
isolate ()->GetHeapProfiler ()->RemoveBuildEmbedderGraphCallback (
469
476
BuildEmbedderGraph, this );
470
477
@@ -1097,6 +1104,25 @@ void Environment::VerifyNoStrongBaseObjects() {
1097
1104
});
1098
1105
}
1099
1106
1107
+ uint64_t GuessMemoryAvailableToTheProcess () {
1108
+ uint64_t free_in_system = uv_get_free_memory ();
1109
+ size_t allowed = uv_get_constrained_memory ();
1110
+ if (allowed == 0 ) {
1111
+ return free_in_system;
1112
+ }
1113
+ size_t rss;
1114
+ int err = uv_resident_set_memory (&rss);
1115
+ if (err) {
1116
+ return free_in_system;
1117
+ }
1118
+ if (allowed < rss) {
1119
+ // Something is probably wrong. Fallback to the free memory.
1120
+ return free_in_system;
1121
+ }
1122
+ // There may still be room for swap, but we will just leave it here.
1123
+ return allowed - rss;
1124
+ }
1125
+
1100
1126
void Environment::BuildEmbedderGraph (Isolate* isolate,
1101
1127
EmbedderGraph* graph,
1102
1128
void * data) {
@@ -1109,6 +1135,126 @@ void Environment::BuildEmbedderGraph(Isolate* isolate,
1109
1135
});
1110
1136
}
1111
1137
1138
+ size_t Environment::NearHeapLimitCallback (void * data,
1139
+ size_t current_heap_limit,
1140
+ size_t initial_heap_limit) {
1141
+ Environment* env = static_cast <Environment*>(data);
1142
+
1143
+ Debug (env,
1144
+ DebugCategory::DIAGNOSTICS,
1145
+ " Invoked NearHeapLimitCallback, processing=%d, "
1146
+ " current_limit=%" PRIu64 " , "
1147
+ " initial_limit=%" PRIu64 " \n " ,
1148
+ env->is_processing_heap_limit_callback_ ,
1149
+ static_cast <uint64_t >(current_heap_limit),
1150
+ static_cast <uint64_t >(initial_heap_limit));
1151
+
1152
+ size_t max_young_gen_size = env->isolate_data ()->max_young_gen_size ;
1153
+ size_t young_gen_size = 0 ;
1154
+ size_t old_gen_size = 0 ;
1155
+
1156
+ v8::HeapSpaceStatistics stats;
1157
+ size_t num_heap_spaces = env->isolate ()->NumberOfHeapSpaces ();
1158
+ for (size_t i = 0 ; i < num_heap_spaces; ++i) {
1159
+ env->isolate ()->GetHeapSpaceStatistics (&stats, i);
1160
+ if (strcmp (stats.space_name (), " new_space" ) == 0 ||
1161
+ strcmp (stats.space_name (), " new_large_object_space" ) == 0 ) {
1162
+ young_gen_size += stats.space_used_size ();
1163
+ } else {
1164
+ old_gen_size += stats.space_used_size ();
1165
+ }
1166
+ }
1167
+
1168
+ Debug (env,
1169
+ DebugCategory::DIAGNOSTICS,
1170
+ " max_young_gen_size=%" PRIu64 " , "
1171
+ " young_gen_size=%" PRIu64 " , "
1172
+ " old_gen_size=%" PRIu64 " , "
1173
+ " total_size=%" PRIu64 " \n " ,
1174
+ static_cast <uint64_t >(max_young_gen_size),
1175
+ static_cast <uint64_t >(young_gen_size),
1176
+ static_cast <uint64_t >(old_gen_size),
1177
+ static_cast <uint64_t >(young_gen_size + old_gen_size));
1178
+
1179
+ uint64_t available = GuessMemoryAvailableToTheProcess ();
1180
+ // TODO(joyeecheung): get a better estimate about the native memory
1181
+ // usage into the overhead, e.g. based on the count of objects.
1182
+ uint64_t estimated_overhead = max_young_gen_size;
1183
+ Debug (env,
1184
+ DebugCategory::DIAGNOSTICS,
1185
+ " Estimated available memory=%" PRIu64 " , "
1186
+ " estimated overhead=%" PRIu64 " \n " ,
1187
+ static_cast <uint64_t >(available),
1188
+ static_cast <uint64_t >(estimated_overhead));
1189
+
1190
+ // This might be hit when the snapshot is being taken in another
1191
+ // NearHeapLimitCallback invocation.
1192
+ // When taking the snapshot, objects in the young generation may be
1193
+ // promoted to the old generation, result in increased heap usage,
1194
+ // but it should be no more than the young generation size.
1195
+ // Ideally, this should be as small as possible - the heap limit
1196
+ // can only be restored when the heap usage falls down below the
1197
+ // new limit, so in a heap with unbounded growth the isolate
1198
+ // may eventually crash with this new limit - effectively raising
1199
+ // the heap limit to the new one.
1200
+ if (env->is_processing_heap_limit_callback_ ) {
1201
+ size_t new_limit = initial_heap_limit + max_young_gen_size;
1202
+ Debug (env,
1203
+ DebugCategory::DIAGNOSTICS,
1204
+ " Not generating snapshots in nested callback. "
1205
+ " new_limit=%" PRIu64 " \n " ,
1206
+ static_cast <uint64_t >(new_limit));
1207
+ return new_limit;
1208
+ }
1209
+
1210
+ // Estimate whether the snapshot is going to use up all the memory
1211
+ // available to the process. If so, just give up to prevent the system
1212
+ // from killing the process for a system OOM.
1213
+ if (estimated_overhead > available) {
1214
+ Debug (env,
1215
+ DebugCategory::DIAGNOSTICS,
1216
+ " Not generating snapshots because it's too risky.\n " );
1217
+ env->isolate ()->RemoveNearHeapLimitCallback (NearHeapLimitCallback,
1218
+ initial_heap_limit);
1219
+ return current_heap_limit;
1220
+ }
1221
+
1222
+ // Take the snapshot synchronously.
1223
+ env->is_processing_heap_limit_callback_ = true ;
1224
+
1225
+ std::string dir = env->options ()->diagnostic_dir ;
1226
+ if (dir.empty ()) {
1227
+ dir = env->GetCwd ();
1228
+ }
1229
+ DiagnosticFilename name (env, " Heap" , " heapsnapshot" );
1230
+ std::string filename = dir + kPathSeparator + (*name);
1231
+
1232
+ Debug (env, DebugCategory::DIAGNOSTICS, " Start generating %s...\n " , *name);
1233
+
1234
+ // Remove the callback first in case it's triggered when generating
1235
+ // the snapshot.
1236
+ env->isolate ()->RemoveNearHeapLimitCallback (NearHeapLimitCallback,
1237
+ initial_heap_limit);
1238
+
1239
+ heap::WriteSnapshot (env->isolate (), filename.c_str ());
1240
+ env->heap_limit_snapshot_taken_ += 1 ;
1241
+
1242
+ // Don't take more snapshots than the number specified by
1243
+ // --heapsnapshot-near-heap-limit.
1244
+ if (env->heap_limit_snapshot_taken_ <
1245
+ env->options_ ->heap_snapshot_near_heap_limit ) {
1246
+ env->isolate ()->AddNearHeapLimitCallback (NearHeapLimitCallback, env);
1247
+ }
1248
+
1249
+ FPrintF (stderr, " Wrote snapshot to %s\n " , filename.c_str ());
1250
+ // Tell V8 to reset the heap limit once the heap usage falls down to
1251
+ // 95% of the initial limit.
1252
+ env->isolate ()->AutomaticallyRestoreInitialHeapLimit (0.95 );
1253
+
1254
+ env->is_processing_heap_limit_callback_ = false ;
1255
+ return initial_heap_limit;
1256
+ }
1257
+
1112
1258
inline size_t Environment::SelfSize () const {
1113
1259
size_t size = sizeof (*this );
1114
1260
// Remove non pointer fields that will be tracked in MemoryInfo()
0 commit comments