@depup/node-memwatch 1.0.1-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.travis.yml +15 -0
- package/ChangeLog +26 -0
- package/README.md +32 -0
- package/binding.gyp +16 -0
- package/build/Makefile +354 -0
- package/build/Release/.deps/Release/obj.target/memwatch/src/heapdiff.o.d.raw +90 -0
- package/build/binding.Makefile +6 -0
- package/build/memwatch.target.mk +161 -0
- package/changes.json +14 -0
- package/examples/basic_heapdiff.js +22 -0
- package/examples/do_nothing_server.js +16 -0
- package/examples/slightly_leaky.js +61 -0
- package/include.js +16 -0
- package/package.json +64 -0
- package/src/heapdiff.cc +351 -0
- package/src/heapdiff.hh +34 -0
- package/src/init.cc +26 -0
- package/src/memwatch.cc +263 -0
- package/src/memwatch.hh +18 -0
- package/src/platformcompat.hh +19 -0
- package/src/util.cc +46 -0
- package/src/util.hh +16 -0
- package/tests.js +64 -0
package/src/heapdiff.cc
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 2012|lloyd|http://wtfpl.org
|
|
3
|
+
*/
|
|
4
|
+
#include <map>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <set>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include <stdlib.h> // abs()
|
|
10
|
+
#include <time.h> // time()
|
|
11
|
+
|
|
12
|
+
#include "heapdiff.hh"
|
|
13
|
+
#include "util.hh"
|
|
14
|
+
|
|
15
|
+
using namespace v8;
|
|
16
|
+
using namespace node;
|
|
17
|
+
using namespace std;
|
|
18
|
+
|
|
19
|
+
static bool s_inProgress = false;
|
|
20
|
+
static time_t s_startTime;
|
|
21
|
+
|
|
22
|
+
bool heapdiff::HeapDiff::InProgress()
|
|
23
|
+
{
|
|
24
|
+
return s_inProgress;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
heapdiff::HeapDiff::HeapDiff() : ObjectWrap(), before(NULL), after(NULL),
|
|
28
|
+
ended(false)
|
|
29
|
+
{
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
heapdiff::HeapDiff::~HeapDiff()
|
|
33
|
+
{
|
|
34
|
+
if (before) {
|
|
35
|
+
((HeapSnapshot *) before)->Delete();
|
|
36
|
+
before = NULL;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (after) {
|
|
40
|
+
((HeapSnapshot *) after)->Delete();
|
|
41
|
+
after = NULL;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void
|
|
46
|
+
heapdiff::HeapDiff::Initialize ( v8::Handle<v8::Object> target )
|
|
47
|
+
{
|
|
48
|
+
Nan::HandleScope scope;
|
|
49
|
+
|
|
50
|
+
v8::Local<v8::FunctionTemplate> t = Nan::New<v8::FunctionTemplate>(New);
|
|
51
|
+
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
52
|
+
t->SetClassName(Nan::New<v8::String>("HeapDiff").ToLocalChecked());
|
|
53
|
+
|
|
54
|
+
Nan::SetPrototypeMethod(t, "end", End);
|
|
55
|
+
|
|
56
|
+
target->Set(Nan::New<v8::String>("HeapDiff").ToLocalChecked(), t->GetFunction());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
NAN_METHOD(heapdiff::HeapDiff::New)
|
|
60
|
+
{
|
|
61
|
+
// Don't blow up when the caller says "new require('memwatch').HeapDiff()"
|
|
62
|
+
// issue #30
|
|
63
|
+
// stolen from: https://github.com/kkaefer/node-cpp-modules/commit/bd9432026affafd8450ecfd9b49b7dc647b6d348
|
|
64
|
+
if (!info.IsConstructCall()) {
|
|
65
|
+
return Nan::ThrowTypeError("Use the new operator to create instances of this object.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Nan::HandleScope scope;
|
|
69
|
+
|
|
70
|
+
// allocate the underlying c++ class and wrap it up in the this pointer
|
|
71
|
+
HeapDiff * self = new HeapDiff();
|
|
72
|
+
self->Wrap(info.This());
|
|
73
|
+
|
|
74
|
+
// take a snapshot and save a pointer to it
|
|
75
|
+
s_inProgress = true;
|
|
76
|
+
s_startTime = time(NULL);
|
|
77
|
+
|
|
78
|
+
#if (NODE_MODULE_VERSION >= 0x002D)
|
|
79
|
+
self->before = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(NULL);
|
|
80
|
+
#else
|
|
81
|
+
#if (NODE_MODULE_VERSION > 0x000B)
|
|
82
|
+
self->before = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(Nan::New<v8::String>("").ToLocalChecked(), NULL);
|
|
83
|
+
#else
|
|
84
|
+
self->before = v8::HeapProfiler::TakeSnapshot(Nan::New<v8::String>("").ToLocalChecked(), HeapSnapshot::kFull, NULL);
|
|
85
|
+
#endif
|
|
86
|
+
#endif
|
|
87
|
+
|
|
88
|
+
s_inProgress = false;
|
|
89
|
+
|
|
90
|
+
info.GetReturnValue().Set(info.This());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static string handleToStr(const Local<Value> & str)
|
|
94
|
+
{
|
|
95
|
+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
|
96
|
+
|
|
97
|
+
String::Utf8Value utfString(isolate, str->ToString());
|
|
98
|
+
|
|
99
|
+
return *utfString;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static void
|
|
103
|
+
buildIDSet(set<uint64_t> * seen, const HeapGraphNode* cur, int & s)
|
|
104
|
+
{
|
|
105
|
+
Nan::HandleScope scope;
|
|
106
|
+
|
|
107
|
+
// cycle detection
|
|
108
|
+
if (seen->find(cur->GetId()) != seen->end()) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// always ignore HeapDiff related memory
|
|
112
|
+
if (cur->GetType() == HeapGraphNode::kObject &&
|
|
113
|
+
handleToStr(cur->GetName()).compare("HeapDiff") == 0)
|
|
114
|
+
{
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// update memory usage as we go
|
|
119
|
+
#if (NODE_MODULE_VERSION >= 0x002D)
|
|
120
|
+
s += cur->GetShallowSize();
|
|
121
|
+
#else
|
|
122
|
+
s += cur->GetSelfSize();
|
|
123
|
+
#endif
|
|
124
|
+
seen->insert(cur->GetId());
|
|
125
|
+
|
|
126
|
+
for (int i=0; i < cur->GetChildrenCount(); i++) {
|
|
127
|
+
buildIDSet(seen, cur->GetChild(i)->GetToNode(), s);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
typedef set<uint64_t> idset;
|
|
132
|
+
|
|
133
|
+
// why doesn't STL work?
|
|
134
|
+
// XXX: improve this algorithm
|
|
135
|
+
void setDiff(idset a, idset b, vector<uint64_t> &c)
|
|
136
|
+
{
|
|
137
|
+
for (idset::iterator i = a.begin(); i != a.end(); i++) {
|
|
138
|
+
if (b.find(*i) == b.end()) c.push_back(*i);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class example
|
|
144
|
+
{
|
|
145
|
+
public:
|
|
146
|
+
HeapGraphEdge::Type context;
|
|
147
|
+
HeapGraphNode::Type type;
|
|
148
|
+
std::string name;
|
|
149
|
+
std::string value;
|
|
150
|
+
std::string heap_value;
|
|
151
|
+
int self_size;
|
|
152
|
+
int retained_size;
|
|
153
|
+
int retainers;
|
|
154
|
+
|
|
155
|
+
example() : context(HeapGraphEdge::kHidden),
|
|
156
|
+
type(HeapGraphNode::kHidden),
|
|
157
|
+
self_size(0), retained_size(0), retainers(0) { };
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
class change
|
|
161
|
+
{
|
|
162
|
+
public:
|
|
163
|
+
long int size;
|
|
164
|
+
long int added;
|
|
165
|
+
long int released;
|
|
166
|
+
std::vector<example> examples;
|
|
167
|
+
|
|
168
|
+
change() : size(0), added(0), released(0) { }
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
typedef std::map<std::string, change>changeset;
|
|
172
|
+
|
|
173
|
+
static void manageChange(changeset & changes, const HeapGraphNode * node, bool added)
|
|
174
|
+
{
|
|
175
|
+
std::string type;
|
|
176
|
+
|
|
177
|
+
switch(node->GetType()) {
|
|
178
|
+
case HeapGraphNode::kArray:
|
|
179
|
+
type.append("Array");
|
|
180
|
+
break;
|
|
181
|
+
case HeapGraphNode::kString:
|
|
182
|
+
type.append("String");
|
|
183
|
+
break;
|
|
184
|
+
case HeapGraphNode::kObject:
|
|
185
|
+
type.append(handleToStr(node->GetName()));
|
|
186
|
+
break;
|
|
187
|
+
case HeapGraphNode::kCode:
|
|
188
|
+
type.append("Code");
|
|
189
|
+
break;
|
|
190
|
+
case HeapGraphNode::kClosure:
|
|
191
|
+
type.append("Closure");
|
|
192
|
+
break;
|
|
193
|
+
case HeapGraphNode::kRegExp:
|
|
194
|
+
type.append("RegExp");
|
|
195
|
+
break;
|
|
196
|
+
case HeapGraphNode::kHeapNumber:
|
|
197
|
+
type.append("Number");
|
|
198
|
+
break;
|
|
199
|
+
case HeapGraphNode::kNative:
|
|
200
|
+
type.append("Native");
|
|
201
|
+
break;
|
|
202
|
+
case HeapGraphNode::kHidden:
|
|
203
|
+
default:
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (changes.find(type) == changes.end()) {
|
|
208
|
+
changes[type] = change();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
changeset::iterator i = changes.find(type);
|
|
212
|
+
|
|
213
|
+
#if (NODE_MODULE_VERSION >= 0x002D)
|
|
214
|
+
i->second.size += node->GetShallowSize() * (added ? 1 : -1);
|
|
215
|
+
#else
|
|
216
|
+
i->second.size += node->GetSelfSize() * (added ? 1 : -1);
|
|
217
|
+
#endif
|
|
218
|
+
if (added) i->second.added++;
|
|
219
|
+
else i->second.released++;
|
|
220
|
+
|
|
221
|
+
// XXX: example
|
|
222
|
+
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static Handle<Value> changesetToObject(changeset & changes)
|
|
227
|
+
{
|
|
228
|
+
Nan::EscapableHandleScope scope;
|
|
229
|
+
Local<Array> a = Nan::New<v8::Array>();
|
|
230
|
+
|
|
231
|
+
for (changeset::iterator i = changes.begin(); i != changes.end(); i++) {
|
|
232
|
+
Local<Object> d = Nan::New<v8::Object>();
|
|
233
|
+
d->Set(Nan::New("what").ToLocalChecked(), Nan::New(i->first.c_str()).ToLocalChecked());
|
|
234
|
+
d->Set(Nan::New("size_bytes").ToLocalChecked(), Nan::New<v8::Number>(i->second.size));
|
|
235
|
+
d->Set(Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(i->second.size).c_str()).ToLocalChecked());
|
|
236
|
+
d->Set(Nan::New("+").ToLocalChecked(), Nan::New<v8::Number>(i->second.added));
|
|
237
|
+
d->Set(Nan::New("-").ToLocalChecked(), Nan::New<v8::Number>(i->second.released));
|
|
238
|
+
a->Set(a->Length(), d);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return scope.Escape(a);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
static v8::Local<Value>
|
|
246
|
+
compare(const v8::HeapSnapshot * before, const v8::HeapSnapshot * after)
|
|
247
|
+
{
|
|
248
|
+
Nan::EscapableHandleScope scope;
|
|
249
|
+
int s, diffBytes;
|
|
250
|
+
|
|
251
|
+
Local<Object> o = Nan::New<v8::Object>();
|
|
252
|
+
|
|
253
|
+
// first let's append summary information
|
|
254
|
+
Local<Object> b = Nan::New<v8::Object>();
|
|
255
|
+
b->Set(Nan::New("nodes").ToLocalChecked(), Nan::New(before->GetNodesCount()));
|
|
256
|
+
//b->Set(Nan::New("time"), s_startTime);
|
|
257
|
+
o->Set(Nan::New("before").ToLocalChecked(), b);
|
|
258
|
+
|
|
259
|
+
Local<Object> a = Nan::New<v8::Object>();
|
|
260
|
+
a->Set(Nan::New("nodes").ToLocalChecked(), Nan::New(after->GetNodesCount()));
|
|
261
|
+
//a->Set(Nan::New("time"), time(NULL));
|
|
262
|
+
o->Set(Nan::New("after").ToLocalChecked(), a);
|
|
263
|
+
|
|
264
|
+
// now let's get allocations by name
|
|
265
|
+
set<uint64_t> beforeIDs, afterIDs;
|
|
266
|
+
s = 0;
|
|
267
|
+
buildIDSet(&beforeIDs, before->GetRoot(), s);
|
|
268
|
+
b->Set(Nan::New("size_bytes").ToLocalChecked(), Nan::New(s));
|
|
269
|
+
b->Set(Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(s).c_str()).ToLocalChecked());
|
|
270
|
+
|
|
271
|
+
diffBytes = s;
|
|
272
|
+
s = 0;
|
|
273
|
+
buildIDSet(&afterIDs, after->GetRoot(), s);
|
|
274
|
+
a->Set(Nan::New("size_bytes").ToLocalChecked(), Nan::New(s));
|
|
275
|
+
a->Set(Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(s).c_str()).ToLocalChecked());
|
|
276
|
+
|
|
277
|
+
diffBytes = s - diffBytes;
|
|
278
|
+
|
|
279
|
+
Local<Object> c = Nan::New<v8::Object>();
|
|
280
|
+
c->Set(Nan::New("size_bytes").ToLocalChecked(), Nan::New(diffBytes));
|
|
281
|
+
c->Set(Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(diffBytes).c_str()).ToLocalChecked());
|
|
282
|
+
o->Set(Nan::New("change").ToLocalChecked(), c);
|
|
283
|
+
|
|
284
|
+
// before - after will reveal nodes released (memory freed)
|
|
285
|
+
vector<uint64_t> changedIDs;
|
|
286
|
+
setDiff(beforeIDs, afterIDs, changedIDs);
|
|
287
|
+
c->Set(Nan::New("freed_nodes").ToLocalChecked(), Nan::New<v8::Number>(changedIDs.size()));
|
|
288
|
+
|
|
289
|
+
// here's where we'll collect all the summary information
|
|
290
|
+
changeset changes;
|
|
291
|
+
|
|
292
|
+
// for each of these nodes, let's aggregate the change information
|
|
293
|
+
for (unsigned long i = 0; i < changedIDs.size(); i++) {
|
|
294
|
+
const HeapGraphNode * n = before->GetNodeById(changedIDs[i]);
|
|
295
|
+
manageChange(changes, n, false);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
changedIDs.clear();
|
|
299
|
+
|
|
300
|
+
// after - before will reveal nodes added (memory allocated)
|
|
301
|
+
setDiff(afterIDs, beforeIDs, changedIDs);
|
|
302
|
+
|
|
303
|
+
c->Set(Nan::New("allocated_nodes").ToLocalChecked(), Nan::New<v8::Number>(changedIDs.size()));
|
|
304
|
+
|
|
305
|
+
for (unsigned long i = 0; i < changedIDs.size(); i++) {
|
|
306
|
+
const HeapGraphNode * n = after->GetNodeById(changedIDs[i]);
|
|
307
|
+
manageChange(changes, n, true);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
c->Set(Nan::New("details").ToLocalChecked(), changesetToObject(changes));
|
|
311
|
+
|
|
312
|
+
return scope.Escape(o);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
NAN_METHOD(heapdiff::HeapDiff::End)
|
|
316
|
+
{
|
|
317
|
+
// take another snapshot and compare them
|
|
318
|
+
Nan::HandleScope scope;
|
|
319
|
+
|
|
320
|
+
HeapDiff *t = Unwrap<HeapDiff>( info.This() );
|
|
321
|
+
|
|
322
|
+
// How shall we deal with double .end()ing? The only reasonable
|
|
323
|
+
// approach seems to be an exception, cause nothing else makes
|
|
324
|
+
// sense.
|
|
325
|
+
if (t->ended) {
|
|
326
|
+
return Nan::ThrowError("attempt to end() a HeapDiff that was already ended");
|
|
327
|
+
}
|
|
328
|
+
t->ended = true;
|
|
329
|
+
|
|
330
|
+
s_inProgress = true;
|
|
331
|
+
#if (NODE_MODULE_VERSION >= 0x002D)
|
|
332
|
+
t->after = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(NULL);
|
|
333
|
+
#else
|
|
334
|
+
#if (NODE_MODULE_VERSION > 0x000B)
|
|
335
|
+
t->after = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(Nan::New<v8::String>("").ToLocalChecked(), NULL);
|
|
336
|
+
#else
|
|
337
|
+
t->after = v8::HeapProfiler::TakeSnapshot(Nan::New<v8::String>("").ToLocalChecked(), HeapSnapshot::kFull, NULL);
|
|
338
|
+
#endif
|
|
339
|
+
#endif
|
|
340
|
+
s_inProgress = false;
|
|
341
|
+
|
|
342
|
+
v8::Local<Value> comparison = compare(t->before, t->after);
|
|
343
|
+
// free early, free often. I mean, after all, this process we're in is
|
|
344
|
+
// probably having memory problems. We want to help her.
|
|
345
|
+
((HeapSnapshot *) t->before)->Delete();
|
|
346
|
+
t->before = NULL;
|
|
347
|
+
((HeapSnapshot *) t->after)->Delete();
|
|
348
|
+
t->after = NULL;
|
|
349
|
+
|
|
350
|
+
info.GetReturnValue().Set(comparison);
|
|
351
|
+
}
|
package/src/heapdiff.hh
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 2012|lloyd|http://wtfpl.org
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#ifndef __HEADDIFF_H
|
|
6
|
+
#define __HEADDIFF_H
|
|
7
|
+
|
|
8
|
+
#include <v8.h>
|
|
9
|
+
#include <v8-profiler.h>
|
|
10
|
+
#include <node.h>
|
|
11
|
+
#include <nan.h>
|
|
12
|
+
|
|
13
|
+
namespace heapdiff
|
|
14
|
+
{
|
|
15
|
+
class HeapDiff : public Nan::ObjectWrap
|
|
16
|
+
{
|
|
17
|
+
public:
|
|
18
|
+
static void Initialize ( v8::Handle<v8::Object> target );
|
|
19
|
+
|
|
20
|
+
static NAN_METHOD(New);
|
|
21
|
+
static NAN_METHOD(End);
|
|
22
|
+
static bool InProgress();
|
|
23
|
+
|
|
24
|
+
protected:
|
|
25
|
+
HeapDiff();
|
|
26
|
+
~HeapDiff();
|
|
27
|
+
private:
|
|
28
|
+
const v8::HeapSnapshot * before;
|
|
29
|
+
const v8::HeapSnapshot * after;
|
|
30
|
+
bool ended;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
#endif
|
package/src/init.cc
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 2012|lloyd|do what the fuck you want to
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include <v8.h>
|
|
6
|
+
#include <node.h>
|
|
7
|
+
|
|
8
|
+
#include "heapdiff.hh"
|
|
9
|
+
#include "memwatch.hh"
|
|
10
|
+
|
|
11
|
+
extern "C" {
|
|
12
|
+
void init (v8::Handle<v8::Object> target)
|
|
13
|
+
{
|
|
14
|
+
v8::Isolate * isolate = target->GetIsolate();
|
|
15
|
+
|
|
16
|
+
Nan::HandleScope scope;
|
|
17
|
+
heapdiff::HeapDiff::Initialize(target);
|
|
18
|
+
|
|
19
|
+
Nan::SetMethod(target, "upon_gc", memwatch::upon_gc);
|
|
20
|
+
Nan::SetMethod(target, "gc", memwatch::trigger_gc);
|
|
21
|
+
|
|
22
|
+
isolate->AddGCEpilogueCallback(memwatch::after_gc);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
NODE_MODULE(memwatch, init);
|
|
26
|
+
};
|
package/src/memwatch.cc
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 2012|lloyd|http://wtfpl.org
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "platformcompat.hh"
|
|
6
|
+
#include "memwatch.hh"
|
|
7
|
+
#include "heapdiff.hh"
|
|
8
|
+
#include "util.hh"
|
|
9
|
+
|
|
10
|
+
#include <node.h>
|
|
11
|
+
#include <node_version.h>
|
|
12
|
+
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <cstring>
|
|
15
|
+
#include <sstream>
|
|
16
|
+
|
|
17
|
+
#include <math.h> // for pow
|
|
18
|
+
#include <time.h> // for time
|
|
19
|
+
|
|
20
|
+
using namespace v8;
|
|
21
|
+
using namespace node;
|
|
22
|
+
|
|
23
|
+
Handle<Object> g_context;
|
|
24
|
+
Nan::Callback *g_cb;
|
|
25
|
+
|
|
26
|
+
struct Baton {
|
|
27
|
+
uv_work_t req;
|
|
28
|
+
size_t heapUsage;
|
|
29
|
+
GCType type;
|
|
30
|
+
GCCallbackFlags flags;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
static const unsigned int RECENT_PERIOD = 10;
|
|
34
|
+
static const unsigned int ANCIENT_PERIOD = 120;
|
|
35
|
+
|
|
36
|
+
static struct
|
|
37
|
+
{
|
|
38
|
+
// counts of different types of gc events
|
|
39
|
+
unsigned int gc_full;
|
|
40
|
+
unsigned int gc_inc;
|
|
41
|
+
unsigned int gc_compact;
|
|
42
|
+
|
|
43
|
+
// last base heap size as measured *right* after GC
|
|
44
|
+
unsigned int last_base;
|
|
45
|
+
|
|
46
|
+
// the estimated "base memory" usage of the javascript heap
|
|
47
|
+
// over the RECENT_PERIOD number of GC runs
|
|
48
|
+
unsigned int base_recent;
|
|
49
|
+
|
|
50
|
+
// the estimated "base memory" usage of the javascript heap
|
|
51
|
+
// over the ANCIENT_PERIOD number of GC runs
|
|
52
|
+
unsigned int base_ancient;
|
|
53
|
+
|
|
54
|
+
// the most extreme values we've seen for base heap size
|
|
55
|
+
unsigned int base_max;
|
|
56
|
+
unsigned int base_min;
|
|
57
|
+
|
|
58
|
+
// leak detection!
|
|
59
|
+
|
|
60
|
+
// the period from which this leak analysis starts
|
|
61
|
+
time_t leak_time_start;
|
|
62
|
+
// the base memory for the detection period
|
|
63
|
+
time_t leak_base_start;
|
|
64
|
+
// the number of consecutive compactions for which we've grown
|
|
65
|
+
unsigned int consecutive_growth;
|
|
66
|
+
} s_stats;
|
|
67
|
+
|
|
68
|
+
static Local<Value> getLeakReport(size_t heapUsage)
|
|
69
|
+
{
|
|
70
|
+
Nan::EscapableHandleScope scope;
|
|
71
|
+
|
|
72
|
+
size_t growth = heapUsage - s_stats.leak_base_start;
|
|
73
|
+
int now = time(NULL);
|
|
74
|
+
int delta = now - s_stats.leak_time_start;
|
|
75
|
+
|
|
76
|
+
Local<Object> leakReport = Nan::New<v8::Object>();
|
|
77
|
+
//leakReport->Set(Nan::New("start").ToLocalChecked(), NODE_UNIXTIME_V8(s_stats.leak_time_start));
|
|
78
|
+
//leakReport->Set(Nan::New("end").ToLocalChecked(), NODE_UNIXTIME_V8(now));
|
|
79
|
+
leakReport->Set(Nan::New("growth").ToLocalChecked(), Nan::New<v8::Number>(growth));
|
|
80
|
+
|
|
81
|
+
std::stringstream ss;
|
|
82
|
+
ss << "heap growth over 5 consecutive GCs ("
|
|
83
|
+
<< mw_util::niceDelta(delta) << ") - "
|
|
84
|
+
<< mw_util::niceSize(growth / ((double) delta / (60.0 * 60.0))) << "/hr";
|
|
85
|
+
|
|
86
|
+
leakReport->Set(Nan::New("reason").ToLocalChecked(), Nan::New(ss.str().c_str()).ToLocalChecked());
|
|
87
|
+
|
|
88
|
+
return scope.Escape(leakReport);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static void AsyncMemwatchAfter(uv_work_t* request) {
|
|
92
|
+
Nan::HandleScope scope;
|
|
93
|
+
|
|
94
|
+
Baton * b = (Baton *) request->data;
|
|
95
|
+
|
|
96
|
+
// do the math in C++, permanent
|
|
97
|
+
// record the type of GC event that occured
|
|
98
|
+
if (b->type == kGCTypeMarkSweepCompact) s_stats.gc_full++;
|
|
99
|
+
else s_stats.gc_inc++;
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
#if NODE_VERSION_AT_LEAST(0,8,0)
|
|
103
|
+
b->type == kGCTypeMarkSweepCompact
|
|
104
|
+
#else
|
|
105
|
+
b->flags == kGCCallbackFlagCompacted
|
|
106
|
+
#endif
|
|
107
|
+
) {
|
|
108
|
+
// leak detection code. has the heap usage grown?
|
|
109
|
+
if (s_stats.last_base < b->heapUsage) {
|
|
110
|
+
if (s_stats.consecutive_growth == 0) {
|
|
111
|
+
s_stats.leak_time_start = time(NULL);
|
|
112
|
+
s_stats.leak_base_start = b->heapUsage;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
s_stats.consecutive_growth++;
|
|
116
|
+
|
|
117
|
+
// consecutive growth over 5 GCs suggests a leak
|
|
118
|
+
if (s_stats.consecutive_growth >= 5) {
|
|
119
|
+
// reset to zero
|
|
120
|
+
s_stats.consecutive_growth = 0;
|
|
121
|
+
|
|
122
|
+
// emit a leak report!
|
|
123
|
+
Local<Value> argv[3];
|
|
124
|
+
argv[0] = Nan::New<v8::Boolean>(false);
|
|
125
|
+
// the type of event to emit
|
|
126
|
+
argv[1] = Nan::New("leak").ToLocalChecked();
|
|
127
|
+
argv[2] = getLeakReport(b->heapUsage);
|
|
128
|
+
|
|
129
|
+
Nan::AsyncResource async("nan:Callback:Call");
|
|
130
|
+
g_cb->Call(3, argv, &async);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
s_stats.consecutive_growth = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// update last_base
|
|
137
|
+
s_stats.last_base = b->heapUsage;
|
|
138
|
+
|
|
139
|
+
// update compaction count
|
|
140
|
+
s_stats.gc_compact++;
|
|
141
|
+
|
|
142
|
+
// the first ten compactions we'll use a different algorithm to
|
|
143
|
+
// dampen out wider memory fluctuation at startup
|
|
144
|
+
if (s_stats.gc_compact < RECENT_PERIOD) {
|
|
145
|
+
double decay = pow(s_stats.gc_compact / RECENT_PERIOD, 2.5);
|
|
146
|
+
decay *= s_stats.gc_compact;
|
|
147
|
+
if (ISINF(decay) || ISNAN(decay)) decay = 0;
|
|
148
|
+
s_stats.base_recent = ((s_stats.base_recent * decay) +
|
|
149
|
+
s_stats.last_base) / (decay + 1);
|
|
150
|
+
|
|
151
|
+
decay = pow(s_stats.gc_compact / RECENT_PERIOD, 2.4);
|
|
152
|
+
decay *= s_stats.gc_compact;
|
|
153
|
+
s_stats.base_ancient = ((s_stats.base_ancient * decay) +
|
|
154
|
+
s_stats.last_base) / (1 + decay);
|
|
155
|
+
|
|
156
|
+
} else {
|
|
157
|
+
s_stats.base_recent = ((s_stats.base_recent * (RECENT_PERIOD - 1)) +
|
|
158
|
+
s_stats.last_base) / RECENT_PERIOD;
|
|
159
|
+
double decay = FMIN(ANCIENT_PERIOD, s_stats.gc_compact);
|
|
160
|
+
s_stats.base_ancient = ((s_stats.base_ancient * (decay - 1)) +
|
|
161
|
+
s_stats.last_base) / decay;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// only record min/max after 3 gcs to let initial instability settle
|
|
165
|
+
if (s_stats.gc_compact >= 3) {
|
|
166
|
+
if (!s_stats.base_min || s_stats.base_min > s_stats.last_base) {
|
|
167
|
+
s_stats.base_min = s_stats.last_base;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!s_stats.base_max || s_stats.base_max < s_stats.last_base) {
|
|
171
|
+
s_stats.base_max = s_stats.last_base;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// if there are any listeners, it's time to emit!
|
|
176
|
+
if (!g_cb->IsEmpty()) {
|
|
177
|
+
Local<Value> argv[3];
|
|
178
|
+
// magic argument to indicate to the callback all we want to know is whether there are
|
|
179
|
+
// listeners (here we don't)
|
|
180
|
+
argv[0] = Nan::New<v8::Boolean>(true);
|
|
181
|
+
|
|
182
|
+
//Handle<Value> haveListeners = g_cb->call(1, argv);
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
double ut= 0.0;
|
|
186
|
+
if (s_stats.base_ancient) {
|
|
187
|
+
ut = (double) ROUND(((double) (s_stats.base_recent - s_stats.base_ancient) /
|
|
188
|
+
(double) s_stats.base_ancient) * 1000.0) / 10.0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ok, there are listeners, we actually must serialize and emit this stats event
|
|
192
|
+
Local<Object> stats = Nan::New<v8::Object>();
|
|
193
|
+
stats->Set(Nan::New("num_full_gc").ToLocalChecked(), Nan::New(s_stats.gc_full));
|
|
194
|
+
stats->Set(Nan::New("num_inc_gc").ToLocalChecked(), Nan::New(s_stats.gc_inc));
|
|
195
|
+
stats->Set(Nan::New("heap_compactions").ToLocalChecked(), Nan::New(s_stats.gc_compact));
|
|
196
|
+
stats->Set(Nan::New("usage_trend").ToLocalChecked(), Nan::New(ut));
|
|
197
|
+
stats->Set(Nan::New("estimated_base").ToLocalChecked(), Nan::New(s_stats.base_recent));
|
|
198
|
+
stats->Set(Nan::New("current_base").ToLocalChecked(), Nan::New(s_stats.last_base));
|
|
199
|
+
stats->Set(Nan::New("min").ToLocalChecked(), Nan::New(s_stats.base_min));
|
|
200
|
+
stats->Set(Nan::New("max").ToLocalChecked(), Nan::New(s_stats.base_max));
|
|
201
|
+
argv[0] = Nan::New<v8::Boolean>(false);
|
|
202
|
+
// the type of event to emit
|
|
203
|
+
argv[1] = Nan::New("stats").ToLocalChecked();
|
|
204
|
+
argv[2] = stats;
|
|
205
|
+
|
|
206
|
+
Nan::AsyncResource async("nan:Callback:Call");
|
|
207
|
+
g_cb->Call(3, argv, &async);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
delete b;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
static void noop_work_func(uv_work_t *) { }
|
|
215
|
+
|
|
216
|
+
void memwatch::after_gc(Isolate* isolate, GCType type, GCCallbackFlags flags)
|
|
217
|
+
{
|
|
218
|
+
if (heapdiff::HeapDiff::InProgress()) return;
|
|
219
|
+
|
|
220
|
+
Nan::HandleScope scope;
|
|
221
|
+
|
|
222
|
+
Baton * baton = new Baton;
|
|
223
|
+
v8::HeapStatistics hs;
|
|
224
|
+
|
|
225
|
+
Nan::GetHeapStatistics(&hs);
|
|
226
|
+
|
|
227
|
+
baton->heapUsage = hs.used_heap_size();
|
|
228
|
+
baton->type = type;
|
|
229
|
+
baton->flags = flags;
|
|
230
|
+
baton->req.data = (void *) baton;
|
|
231
|
+
|
|
232
|
+
// schedule our work to run in a moment, once gc has fully completed.
|
|
233
|
+
//
|
|
234
|
+
// here we pass a noop work function to work around a flaw in libuv,
|
|
235
|
+
// uv_queue_work on unix works fine, but will will crash on
|
|
236
|
+
// windows. see: https://github.com/joyent/libuv/pull/629
|
|
237
|
+
uv_queue_work(uv_default_loop(), &(baton->req),
|
|
238
|
+
noop_work_func, (uv_after_work_cb)AsyncMemwatchAfter);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
NAN_METHOD(memwatch::upon_gc) {
|
|
242
|
+
Nan::HandleScope scope;
|
|
243
|
+
if (info.Length() >= 1 && info[0]->IsFunction()) {
|
|
244
|
+
g_cb = new Nan::Callback(info[0].As<v8::Function>());
|
|
245
|
+
}
|
|
246
|
+
info.GetReturnValue().Set(Nan::Undefined());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
NAN_METHOD(memwatch::trigger_gc) {
|
|
250
|
+
Nan::HandleScope scope;
|
|
251
|
+
int deadline_in_ms = 500;
|
|
252
|
+
if (info.Length() >= 1 && info[0]->IsNumber()) {
|
|
253
|
+
deadline_in_ms = (int)(info[0]->Int32Value());
|
|
254
|
+
}
|
|
255
|
+
#if (NODE_MODULE_VERSION >= 0x002D)
|
|
256
|
+
Nan::IdleNotification(deadline_in_ms);
|
|
257
|
+
Nan::LowMemoryNotification();
|
|
258
|
+
#else
|
|
259
|
+
while(!Nan::IdleNotification(deadline_in_ms)) {};
|
|
260
|
+
Nan::LowMemoryNotification();
|
|
261
|
+
#endif
|
|
262
|
+
info.GetReturnValue().Set(Nan::Undefined());
|
|
263
|
+
}
|
package/src/memwatch.hh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 2012|lloyd|http://wtfpl.org
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#ifndef __MEMWATCH_HH
|
|
6
|
+
#define __MEMWATCH_HH
|
|
7
|
+
|
|
8
|
+
#include <node.h>
|
|
9
|
+
#include <nan.h>
|
|
10
|
+
|
|
11
|
+
namespace memwatch
|
|
12
|
+
{
|
|
13
|
+
NAN_METHOD(upon_gc);
|
|
14
|
+
NAN_METHOD(trigger_gc);
|
|
15
|
+
void after_gc(v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
#endif
|