forked from deepak1556/node-memwatch
-
Notifications
You must be signed in to change notification settings - Fork 64
/
memwatch.cc
259 lines (209 loc) · 8.57 KB
/
memwatch.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
* 2012|lloyd|http://wtfpl.org
*/
#include "platformcompat.hh"
#include "memwatch.hh"
#include "heapdiff.hh"
#include "util.hh"
#include <node.h>
#include <node_version.h>
#include <string>
#include <cstring>
#include <sstream>
#include <math.h> // for pow
#include <time.h> // for time
using namespace v8;
using namespace node;
Handle<Object> g_context;
Nan::Callback *g_cb;
struct Baton {
uv_work_t req;
size_t heapUsage;
GCType type;
GCCallbackFlags flags;
};
static const unsigned int RECENT_PERIOD = 10;
static const unsigned int ANCIENT_PERIOD = 120;
static struct
{
// counts of different types of gc events
unsigned int gc_full;
unsigned int gc_inc;
unsigned int gc_compact;
// last base heap size as measured *right* after GC
unsigned int last_base;
// the estimated "base memory" usage of the javascript heap
// over the RECENT_PERIOD number of GC runs
unsigned int base_recent;
// the estimated "base memory" usage of the javascript heap
// over the ANCIENT_PERIOD number of GC runs
unsigned int base_ancient;
// the most extreme values we've seen for base heap size
unsigned int base_max;
unsigned int base_min;
// leak detection!
// the period from which this leak analysis starts
time_t leak_time_start;
// the base memory for the detection period
time_t leak_base_start;
// the number of consecutive compactions for which we've grown
unsigned int consecutive_growth;
} s_stats;
static Local<Value> getLeakReport(size_t heapUsage)
{
Nan::EscapableHandleScope scope;
size_t growth = heapUsage - s_stats.leak_base_start;
int now = time(NULL);
int delta = now - s_stats.leak_time_start;
Local<Object> leakReport = Nan::New<v8::Object>();
//leakReport->Set(Nan::New("start").ToLocalChecked(), NODE_UNIXTIME_V8(s_stats.leak_time_start));
//leakReport->Set(Nan::New("end").ToLocalChecked(), NODE_UNIXTIME_V8(now));
leakReport->Set(Nan::New("growth").ToLocalChecked(), Nan::New<v8::Number>(growth));
std::stringstream ss;
ss << "heap growth over 5 consecutive GCs ("
<< mw_util::niceDelta(delta) << ") - "
<< mw_util::niceSize(growth / ((double) delta / (60.0 * 60.0))) << "/hr";
leakReport->Set(Nan::New("reason").ToLocalChecked(), Nan::New(ss.str().c_str()).ToLocalChecked());
return scope.Escape(leakReport);
}
static void AsyncMemwatchAfter(uv_work_t* request) {
Nan::HandleScope scope;
Baton * b = (Baton *) request->data;
// do the math in C++, permanent
// record the type of GC event that occured
if (b->type == kGCTypeMarkSweepCompact) s_stats.gc_full++;
else s_stats.gc_inc++;
if (
#if NODE_VERSION_AT_LEAST(0,8,0)
b->type == kGCTypeMarkSweepCompact
#else
b->flags == kGCCallbackFlagCompacted
#endif
) {
// leak detection code. has the heap usage grown?
if (s_stats.last_base < b->heapUsage) {
if (s_stats.consecutive_growth == 0) {
s_stats.leak_time_start = time(NULL);
s_stats.leak_base_start = b->heapUsage;
}
s_stats.consecutive_growth++;
// consecutive growth over 5 GCs suggests a leak
if (s_stats.consecutive_growth >= 5) {
// reset to zero
s_stats.consecutive_growth = 0;
// emit a leak report!
Local<Value> argv[3];
argv[0] = Nan::New<v8::Boolean>(false);
// the type of event to emit
argv[1] = Nan::New("leak").ToLocalChecked();
argv[2] = getLeakReport(b->heapUsage);
g_cb->Call(3, argv);
}
} else {
s_stats.consecutive_growth = 0;
}
// update last_base
s_stats.last_base = b->heapUsage;
// update compaction count
s_stats.gc_compact++;
// the first ten compactions we'll use a different algorithm to
// dampen out wider memory fluctuation at startup
if (s_stats.gc_compact < RECENT_PERIOD) {
double decay = pow(s_stats.gc_compact / RECENT_PERIOD, 2.5);
decay *= s_stats.gc_compact;
if (ISINF(decay) || ISNAN(decay)) decay = 0;
s_stats.base_recent = ((s_stats.base_recent * decay) +
s_stats.last_base) / (decay + 1);
decay = pow(s_stats.gc_compact / RECENT_PERIOD, 2.4);
decay *= s_stats.gc_compact;
s_stats.base_ancient = ((s_stats.base_ancient * decay) +
s_stats.last_base) / (1 + decay);
} else {
s_stats.base_recent = ((s_stats.base_recent * (RECENT_PERIOD - 1)) +
s_stats.last_base) / RECENT_PERIOD;
double decay = FMIN(ANCIENT_PERIOD, s_stats.gc_compact);
s_stats.base_ancient = ((s_stats.base_ancient * (decay - 1)) +
s_stats.last_base) / decay;
}
// only record min/max after 3 gcs to let initial instability settle
if (s_stats.gc_compact >= 3) {
if (!s_stats.base_min || s_stats.base_min > s_stats.last_base) {
s_stats.base_min = s_stats.last_base;
}
if (!s_stats.base_max || s_stats.base_max < s_stats.last_base) {
s_stats.base_max = s_stats.last_base;
}
}
// if there are any listeners, it's time to emit!
if (!g_cb->IsEmpty()) {
Local<Value> argv[3];
// magic argument to indicate to the callback all we want to know is whether there are
// listeners (here we don't)
argv[0] = Nan::New<v8::Boolean>(true);
//Handle<Value> haveListeners = g_cb->call(1, argv);
double ut= 0.0;
if (s_stats.base_ancient) {
ut = (double) ROUND(((double) (s_stats.base_recent - s_stats.base_ancient) /
(double) s_stats.base_ancient) * 1000.0) / 10.0;
}
// ok, there are listeners, we actually must serialize and emit this stats event
Local<Object> stats = Nan::New<v8::Object>();
stats->Set(Nan::New("num_full_gc").ToLocalChecked(), Nan::New(s_stats.gc_full));
stats->Set(Nan::New("num_inc_gc").ToLocalChecked(), Nan::New(s_stats.gc_inc));
stats->Set(Nan::New("heap_compactions").ToLocalChecked(), Nan::New(s_stats.gc_compact));
stats->Set(Nan::New("usage_trend").ToLocalChecked(), Nan::New(ut));
stats->Set(Nan::New("estimated_base").ToLocalChecked(), Nan::New(s_stats.base_recent));
stats->Set(Nan::New("current_base").ToLocalChecked(), Nan::New(s_stats.last_base));
stats->Set(Nan::New("min").ToLocalChecked(), Nan::New(s_stats.base_min));
stats->Set(Nan::New("max").ToLocalChecked(), Nan::New(s_stats.base_max));
argv[0] = Nan::New<v8::Boolean>(false);
// the type of event to emit
argv[1] = Nan::New("stats").ToLocalChecked();
argv[2] = stats;
g_cb->Call(3, argv);
}
}
delete b;
}
static void noop_work_func(uv_work_t *) { }
void memwatch::after_gc(GCType type, GCCallbackFlags flags)
{
if (heapdiff::HeapDiff::InProgress()) return;
Nan::HandleScope scope;
Baton * baton = new Baton;
v8::HeapStatistics hs;
Nan::GetHeapStatistics(&hs);
baton->heapUsage = hs.used_heap_size();
baton->type = type;
baton->flags = flags;
baton->req.data = (void *) baton;
// schedule our work to run in a moment, once gc has fully completed.
//
// here we pass a noop work function to work around a flaw in libuv,
// uv_queue_work on unix works fine, but will will crash on
// windows. see: https://github.com/joyent/libuv/pull/629
uv_queue_work(uv_default_loop(), &(baton->req),
noop_work_func, (uv_after_work_cb)AsyncMemwatchAfter);
}
NAN_METHOD(memwatch::upon_gc) {
Nan::HandleScope scope;
if (info.Length() >= 1 && info[0]->IsFunction()) {
g_cb = new Nan::Callback(info[0].As<v8::Function>());
}
info.GetReturnValue().Set(Nan::Undefined());
}
NAN_METHOD(memwatch::trigger_gc) {
Nan::HandleScope scope;
int deadline_in_ms = 500;
if (info.Length() >= 1 && info[0]->IsNumber()) {
deadline_in_ms = (int)(info[0]->Int32Value());
}
#if (NODE_MODULE_VERSION >= 0x002D)
Nan::IdleNotification(deadline_in_ms);
Nan::LowMemoryNotification();
#else
while(!Nan::IdleNotification(deadline_in_ms)) {};
Nan::LowMemoryNotification();
#endif
info.GetReturnValue().Set(Nan::Undefined());
}