GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
#include "diagnosticfilename-inl.h" |
||
2 |
#include "env-inl.h" |
||
3 |
#include "memory_tracker-inl.h" |
||
4 |
#include "stream_base-inl.h" |
||
5 |
#include "util-inl.h" |
||
6 |
|||
7 |
using v8::Array; |
||
8 |
using v8::Boolean; |
||
9 |
using v8::Context; |
||
10 |
using v8::EmbedderGraph; |
||
11 |
using v8::EscapableHandleScope; |
||
12 |
using v8::FunctionCallbackInfo; |
||
13 |
using v8::FunctionTemplate; |
||
14 |
using v8::Global; |
||
15 |
using v8::HandleScope; |
||
16 |
using v8::HeapSnapshot; |
||
17 |
using v8::Isolate; |
||
18 |
using v8::JSON; |
||
19 |
using v8::Local; |
||
20 |
using v8::MaybeLocal; |
||
21 |
using v8::Number; |
||
22 |
using v8::Object; |
||
23 |
using v8::ObjectTemplate; |
||
24 |
using v8::String; |
||
25 |
using v8::Value; |
||
26 |
|||
27 |
namespace node { |
||
28 |
namespace heap { |
||
29 |
|||
30 |
✗✓ | 14478 |
class JSGraphJSNode : public EmbedderGraph::Node { |
31 |
public: |
||
32 |
4826 |
const char* Name() override { return "<JS Node>"; } |
|
33 |
4826 |
size_t SizeInBytes() override { return 0; } |
|
34 |
4826 |
bool IsEmbedderNode() override { return false; } |
|
35 |
14478 |
Local<Value> JSValue() { return PersistentToLocal::Strong(persistent_); } |
|
36 |
|||
37 |
9652 |
int IdentityHash() { |
|
38 |
9652 |
Local<Value> v = JSValue(); |
|
39 |
✓✓ | 12380 |
if (v->IsObject()) return v.As<Object>()->GetIdentityHash(); |
40 |
✓✗ | 24864 |
if (v->IsName()) return v.As<v8::Name>()->GetIdentityHash(); |
41 |
if (v->IsInt32()) return v.As<v8::Int32>()->Value(); |
||
42 |
return 0; |
||
43 |
} |
||
44 |
|||
45 |
4826 |
JSGraphJSNode(Isolate* isolate, Local<Value> val) |
|
46 |
4826 |
: persistent_(isolate, val) { |
|
47 |
✗✓ | 4826 |
CHECK(!val.IsEmpty()); |
48 |
4826 |
} |
|
49 |
|||
50 |
struct Hash { |
||
51 |
9652 |
inline size_t operator()(JSGraphJSNode* n) const { |
|
52 |
9652 |
return static_cast<size_t>(n->IdentityHash()); |
|
53 |
} |
||
54 |
}; |
||
55 |
|||
56 |
struct Equal { |
||
57 |
inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const { |
||
58 |
return a->JSValue()->SameValue(b->JSValue()); |
||
59 |
} |
||
60 |
}; |
||
61 |
|||
62 |
private: |
||
63 |
Global<Value> persistent_; |
||
64 |
}; |
||
65 |
|||
66 |
✗✓ | 16 |
class JSGraph : public EmbedderGraph { |
67 |
public: |
||
68 |
16 |
explicit JSGraph(Isolate* isolate) : isolate_(isolate) {} |
|
69 |
|||
70 |
4826 |
Node* V8Node(const Local<Value>& value) override { |
|
71 |
4826 |
std::unique_ptr<JSGraphJSNode> n { new JSGraphJSNode(isolate_, value) }; |
|
72 |
4826 |
auto it = engine_nodes_.find(n.get()); |
|
73 |
✗✓ | 4826 |
if (it != engine_nodes_.end()) |
74 |
return *it; |
||
75 |
4826 |
engine_nodes_.insert(n.get()); |
|
76 |
4826 |
return AddNode(std::unique_ptr<Node>(n.release())); |
|
77 |
} |
||
78 |
|||
79 |
6750 |
Node* AddNode(std::unique_ptr<Node> node) override { |
|
80 |
6750 |
Node* n = node.get(); |
|
81 |
6750 |
nodes_.emplace(std::move(node)); |
|
82 |
6750 |
return n; |
|
83 |
} |
||
84 |
|||
85 |
6862 |
void AddEdge(Node* from, Node* to, const char* name = nullptr) override { |
|
86 |
6862 |
edges_[from].insert(std::make_pair(name, to)); |
|
87 |
6862 |
} |
|
88 |
|||
89 |
16 |
MaybeLocal<Array> CreateObject() const { |
|
90 |
16 |
EscapableHandleScope handle_scope(isolate_); |
|
91 |
16 |
Local<Context> context = isolate_->GetCurrentContext(); |
|
92 |
|||
93 |
32 |
std::unordered_map<Node*, Local<Object>> info_objects; |
|
94 |
16 |
Local<Array> nodes = Array::New(isolate_, nodes_.size()); |
|
95 |
16 |
Local<String> edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges"); |
|
96 |
16 |
Local<String> is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot"); |
|
97 |
16 |
Local<String> name_string = FIXED_ONE_BYTE_STRING(isolate_, "name"); |
|
98 |
16 |
Local<String> size_string = FIXED_ONE_BYTE_STRING(isolate_, "size"); |
|
99 |
16 |
Local<String> value_string = FIXED_ONE_BYTE_STRING(isolate_, "value"); |
|
100 |
16 |
Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps"); |
|
101 |
16 |
Local<String> to_string = FIXED_ONE_BYTE_STRING(isolate_, "to"); |
|
102 |
|||
103 |
✓✓ | 6766 |
for (const std::unique_ptr<Node>& n : nodes_) |
104 |
6750 |
info_objects[n.get()] = Object::New(isolate_); |
|
105 |
|||
106 |
{ |
||
107 |
16 |
HandleScope handle_scope(isolate_); |
|
108 |
16 |
size_t i = 0; |
|
109 |
✓✗✓✓ |
6766 |
for (const std::unique_ptr<Node>& n : nodes_) { |
110 |
6750 |
Local<Object> obj = info_objects[n.get()]; |
|
111 |
Local<Value> value; |
||
112 |
6750 |
std::string name_str; |
|
113 |
6750 |
const char* prefix = n->NamePrefix(); |
|
114 |
✓✓ | 6750 |
if (prefix == nullptr) { |
115 |
4826 |
name_str = n->Name(); |
|
116 |
} else { |
||
117 |
1924 |
name_str = n->NamePrefix(); |
|
118 |
1924 |
name_str += " "; |
|
119 |
1924 |
name_str += n->Name(); |
|
120 |
} |
||
121 |
✗✓ | 13500 |
if (!String::NewFromUtf8( |
122 |
6750 |
isolate_, name_str.c_str(), v8::NewStringType::kNormal) |
|
123 |
✓✗✗✓ |
40500 |
.ToLocal(&value) || |
124 |
✓✗✓✗ ✓✗ |
40500 |
obj->Set(context, name_string, value).IsNothing() || |
125 |
obj->Set(context, |
||
126 |
is_root_string, |
||
127 |
✓✗✓✗ |
20250 |
Boolean::New(isolate_, n->IsRootNode())) |
128 |
✓✗✓✗ |
47250 |
.IsNothing() || |
129 |
obj->Set(context, |
||
130 |
size_string, |
||
131 |
✓✗✓✗ |
20250 |
Number::New(isolate_, n->SizeInBytes())) |
132 |
✓✗✗✓ ✓✗ |
67500 |
.IsNothing() || |
133 |
✓✗✓✗ ✓✗ |
40500 |
obj->Set(context, edges_string, Array::New(isolate_)).IsNothing()) { |
134 |
return MaybeLocal<Array>(); |
||
135 |
} |
||
136 |
✗✓ | 20250 |
if (nodes->Set(context, i++, obj).IsNothing()) |
137 |
return MaybeLocal<Array>(); |
||
138 |
✓✓ | 6750 |
if (!n->IsEmbedderNode()) { |
139 |
4826 |
value = static_cast<JSGraphJSNode*>(n.get())->JSValue(); |
|
140 |
✗✓ | 9652 |
if (obj->Set(context, value_string, value).IsNothing()) |
141 |
return MaybeLocal<Array>(); |
||
142 |
} |
||
143 |
6766 |
} |
|
144 |
} |
||
145 |
|||
146 |
✓✓ | 6766 |
for (const std::unique_ptr<Node>& n : nodes_) { |
147 |
6750 |
Node* wraps = n->WrapperNode(); |
|
148 |
✓✗ | 6750 |
if (wraps == nullptr) continue; |
149 |
Local<Object> from = info_objects[n.get()]; |
||
150 |
Local<Object> to = info_objects[wraps]; |
||
151 |
if (from->Set(context, wraps_string, to).IsNothing()) |
||
152 |
return MaybeLocal<Array>(); |
||
153 |
} |
||
154 |
|||
155 |
✓✓ | 567 |
for (const auto& edge_info : edges_) { |
156 |
551 |
Node* source = edge_info.first; |
|
157 |
Local<Value> edges; |
||
158 |
✓✗✗✓ ✓✗✓✗ ✗✓ |
2755 |
if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) || |
159 |
551 |
!edges->IsArray()) { |
|
160 |
return MaybeLocal<Array>(); |
||
161 |
} |
||
162 |
|||
163 |
551 |
size_t i = 0; |
|
164 |
551 |
size_t j = 0; |
|
165 |
✓✓ | 7413 |
for (const auto& edge : edge_info.second) { |
166 |
6862 |
Local<Object> to_object = info_objects[edge.second]; |
|
167 |
6862 |
Local<Object> edge_obj = Object::New(isolate_); |
|
168 |
Local<Value> edge_name_value; |
||
169 |
6862 |
const char* edge_name = edge.first; |
|
170 |
✓✓ | 6862 |
if (edge_name != nullptr) { |
171 |
✗✓ | 9198 |
if (!String::NewFromUtf8( |
172 |
4599 |
isolate_, edge_name, v8::NewStringType::kNormal) |
|
173 |
13797 |
.ToLocal(&edge_name_value)) { |
|
174 |
return MaybeLocal<Array>(); |
||
175 |
} |
||
176 |
} else { |
||
177 |
4526 |
edge_name_value = Number::New(isolate_, j++); |
|
178 |
} |
||
179 |
✓✗✓✗ ✓✗✗✓ |
48034 |
if (edge_obj->Set(context, name_string, edge_name_value).IsNothing() || |
180 |
✓✗✗✓ ✓✗✓✗ ✓✗ |
68620 |
edge_obj->Set(context, to_string, to_object).IsNothing() || |
181 |
✓✗✓✗ ✓✗ |
34310 |
edges.As<Array>()->Set(context, i++, edge_obj).IsNothing()) { |
182 |
return MaybeLocal<Array>(); |
||
183 |
} |
||
184 |
} |
||
185 |
} |
||
186 |
|||
187 |
16 |
return handle_scope.Escape(nodes); |
|
188 |
} |
||
189 |
|||
190 |
private: |
||
191 |
Isolate* isolate_; |
||
192 |
std::unordered_set<std::unique_ptr<Node>> nodes_; |
||
193 |
std::unordered_set<JSGraphJSNode*, JSGraphJSNode::Hash, JSGraphJSNode::Equal> |
||
194 |
engine_nodes_; |
||
195 |
std::unordered_map<Node*, std::set<std::pair<const char*, Node*>>> edges_; |
||
196 |
}; |
||
197 |
|||
198 |
16 |
void BuildEmbedderGraph(const FunctionCallbackInfo<Value>& args) { |
|
199 |
16 |
Environment* env = Environment::GetCurrent(args); |
|
200 |
16 |
JSGraph graph(env->isolate()); |
|
201 |
16 |
Environment::BuildEmbedderGraph(env->isolate(), &graph, env); |
|
202 |
Local<Array> ret; |
||
203 |
✓✗ | 32 |
if (graph.CreateObject().ToLocal(&ret)) |
204 |
32 |
args.GetReturnValue().Set(ret); |
|
205 |
16 |
} |
|
206 |
|||
207 |
namespace { |
||
208 |
✗✓ | 4 |
class FileOutputStream : public v8::OutputStream { |
209 |
public: |
||
210 |
4 |
explicit FileOutputStream(FILE* stream) : stream_(stream) {} |
|
211 |
|||
212 |
8 |
int GetChunkSize() override { |
|
213 |
8 |
return 65536; // big chunks == faster |
|
214 |
} |
||
215 |
|||
216 |
4 |
void EndOfStream() override {} |
|
217 |
|||
218 |
186 |
WriteResult WriteAsciiChunk(char* data, int size) override { |
|
219 |
186 |
const size_t len = static_cast<size_t>(size); |
|
220 |
186 |
size_t off = 0; |
|
221 |
|||
222 |
✓✓✓✗ ✓✗✓✓ |
558 |
while (off < len && !feof(stream_) && !ferror(stream_)) |
223 |
186 |
off += fwrite(data + off, 1, len - off, stream_); |
|
224 |
|||
225 |
186 |
return off == len ? kContinue : kAbort; |
|
226 |
} |
||
227 |
|||
228 |
private: |
||
229 |
FILE* stream_; |
||
230 |
}; |
||
231 |
|||
232 |
class HeapSnapshotStream : public AsyncWrap, |
||
233 |
public StreamBase, |
||
234 |
public v8::OutputStream { |
||
235 |
public: |
||
236 |
28 |
HeapSnapshotStream( |
|
237 |
Environment* env, |
||
238 |
const HeapSnapshot* snapshot, |
||
239 |
v8::Local<v8::Object> obj) : |
||
240 |
AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT), |
||
241 |
StreamBase(env), |
||
242 |
28 |
snapshot_(snapshot) { |
|
243 |
28 |
MakeWeak(); |
|
244 |
28 |
StreamBase::AttachToObject(GetObject()); |
|
245 |
28 |
} |
|
246 |
|||
247 |
81 |
~HeapSnapshotStream() override { |
|
248 |
27 |
Cleanup(); |
|
249 |
✗✓ | 54 |
} |
250 |
|||
251 |
54 |
int GetChunkSize() override { |
|
252 |
54 |
return 65536; // big chunks == faster |
|
253 |
} |
||
254 |
|||
255 |
27 |
void EndOfStream() override { |
|
256 |
27 |
EmitRead(UV_EOF); |
|
257 |
27 |
Cleanup(); |
|
258 |
27 |
} |
|
259 |
|||
260 |
1352 |
WriteResult WriteAsciiChunk(char* data, int size) override { |
|
261 |
1352 |
int len = size; |
|
262 |
✓✓ | 4056 |
while (len != 0) { |
263 |
1352 |
uv_buf_t buf = EmitAlloc(size); |
|
264 |
1352 |
ssize_t avail = len; |
|
265 |
✗✓ | 1352 |
if (static_cast<ssize_t>(buf.len) < avail) |
266 |
avail = buf.len; |
||
267 |
1352 |
memcpy(buf.base, data, avail); |
|
268 |
1352 |
data += avail; |
|
269 |
1352 |
len -= avail; |
|
270 |
1352 |
EmitRead(size, buf); |
|
271 |
} |
||
272 |
1352 |
return kContinue; |
|
273 |
} |
||
274 |
|||
275 |
27 |
int ReadStart() override { |
|
276 |
✗✓ | 27 |
CHECK_NE(snapshot_, nullptr); |
277 |
27 |
snapshot_->Serialize(this, HeapSnapshot::kJSON); |
|
278 |
27 |
return 0; |
|
279 |
} |
||
280 |
|||
281 |
1352 |
int ReadStop() override { |
|
282 |
1352 |
return 0; |
|
283 |
} |
||
284 |
|||
285 |
int DoShutdown(ShutdownWrap* req_wrap) override { |
||
286 |
UNREACHABLE(); |
||
287 |
} |
||
288 |
|||
289 |
int DoWrite(WriteWrap* w, |
||
290 |
uv_buf_t* bufs, |
||
291 |
size_t count, |
||
292 |
uv_stream_t* send_handle) override { |
||
293 |
UNREACHABLE(); |
||
294 |
} |
||
295 |
|||
296 |
1379 |
bool IsAlive() override { return snapshot_ != nullptr; } |
|
297 |
bool IsClosing() override { return snapshot_ == nullptr; } |
||
298 |
2786 |
AsyncWrap* GetAsyncWrap() override { return this; } |
|
299 |
|||
300 |
39 |
void MemoryInfo(MemoryTracker* tracker) const override { |
|
301 |
✓✓ | 39 |
if (snapshot_ != nullptr) { |
302 |
tracker->TrackFieldWithSize( |
||
303 |
7 |
"snapshot", sizeof(*snapshot_), "HeapSnapshot"); |
|
304 |
} |
||
305 |
39 |
} |
|
306 |
|||
307 |
39 |
SET_MEMORY_INFO_NAME(HeapSnapshotStream) |
|
308 |
39 |
SET_SELF_SIZE(HeapSnapshotStream) |
|
309 |
|||
310 |
private: |
||
311 |
54 |
void Cleanup() { |
|
312 |
✓✓ | 54 |
if (snapshot_ != nullptr) { |
313 |
28 |
const_cast<HeapSnapshot*>(snapshot_)->Delete(); |
|
314 |
28 |
snapshot_ = nullptr; |
|
315 |
} |
||
316 |
54 |
} |
|
317 |
|||
318 |
|||
319 |
const HeapSnapshot* snapshot_; |
||
320 |
}; |
||
321 |
|||
322 |
4 |
inline void TakeSnapshot(Isolate* isolate, v8::OutputStream* out) { |
|
323 |
const HeapSnapshot* const snapshot = |
||
324 |
4 |
isolate->GetHeapProfiler()->TakeHeapSnapshot(); |
|
325 |
4 |
snapshot->Serialize(out, HeapSnapshot::kJSON); |
|
326 |
4 |
const_cast<HeapSnapshot*>(snapshot)->Delete(); |
|
327 |
4 |
} |
|
328 |
|||
329 |
4 |
inline bool WriteSnapshot(Isolate* isolate, const char* filename) { |
|
330 |
4 |
FILE* fp = fopen(filename, "w"); |
|
331 |
✗✓ | 4 |
if (fp == nullptr) |
332 |
return false; |
||
333 |
4 |
FileOutputStream stream(fp); |
|
334 |
4 |
TakeSnapshot(isolate, &stream); |
|
335 |
4 |
fclose(fp); |
|
336 |
4 |
return true; |
|
337 |
} |
||
338 |
|||
339 |
} // namespace |
||
340 |
|||
341 |
28 |
void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) { |
|
342 |
28 |
Environment* env = Environment::GetCurrent(args); |
|
343 |
28 |
HandleScope scope(env->isolate()); |
|
344 |
const HeapSnapshot* const snapshot = |
||
345 |
28 |
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(); |
|
346 |
✗✓ | 28 |
CHECK_NOT_NULL(snapshot); |
347 |
Local<Object> obj; |
||
348 |
✗✓ | 56 |
if (!env->streambaseoutputstream_constructor_template() |
349 |
84 |
->NewInstance(env->context()) |
|
350 |
84 |
.ToLocal(&obj)) { |
|
351 |
28 |
return; |
|
352 |
} |
||
353 |
28 |
HeapSnapshotStream* out = new HeapSnapshotStream(env, snapshot, obj); |
|
354 |
✓✗ | 84 |
args.GetReturnValue().Set(out->object()); |
355 |
} |
||
356 |
|||
357 |
4 |
void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) { |
|
358 |
4 |
Environment* env = Environment::GetCurrent(args); |
|
359 |
4 |
Isolate* isolate = args.GetIsolate(); |
|
360 |
|||
361 |
4 |
Local<Value> filename_v = args[0]; |
|
362 |
|||
363 |
✓✓ | 8 |
if (filename_v->IsUndefined()) { |
364 |
3 |
DiagnosticFilename name(env, "Heap", "heapsnapshot"); |
|
365 |
✗✓ | 3 |
if (!WriteSnapshot(isolate, *name)) |
366 |
return; |
||
367 |
✓✗ | 9 |
if (String::NewFromUtf8(isolate, *name, v8::NewStringType::kNormal) |
368 |
6 |
.ToLocal(&filename_v)) { |
|
369 |
6 |
args.GetReturnValue().Set(filename_v); |
|
370 |
} |
||
371 |
3 |
return; |
|
372 |
} |
||
373 |
|||
374 |
1 |
BufferValue path(isolate, filename_v); |
|
375 |
✗✓ | 1 |
CHECK_NOT_NULL(*path); |
376 |
✗✓ | 1 |
if (!WriteSnapshot(isolate, *path)) |
377 |
return; |
||
378 |
2 |
return args.GetReturnValue().Set(filename_v); |
|
379 |
} |
||
380 |
|||
381 |
69 |
void Initialize(Local<Object> target, |
|
382 |
Local<Value> unused, |
||
383 |
Local<Context> context, |
||
384 |
void* priv) { |
||
385 |
69 |
Environment* env = Environment::GetCurrent(context); |
|
386 |
|||
387 |
env->SetMethodNoSideEffect(target, |
||
388 |
"buildEmbedderGraph", |
||
389 |
69 |
BuildEmbedderGraph); |
|
390 |
env->SetMethodNoSideEffect(target, |
||
391 |
"triggerHeapSnapshot", |
||
392 |
69 |
TriggerHeapSnapshot); |
|
393 |
env->SetMethodNoSideEffect(target, |
||
394 |
"createHeapSnapshotStream", |
||
395 |
69 |
CreateHeapSnapshotStream); |
|
396 |
|||
397 |
// Create FunctionTemplate for HeapSnapshotStream |
||
398 |
69 |
Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate()); |
|
399 |
138 |
os->Inherit(AsyncWrap::GetConstructorTemplate(env)); |
|
400 |
69 |
Local<ObjectTemplate> ost = os->InstanceTemplate(); |
|
401 |
69 |
ost->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); |
|
402 |
138 |
os->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream")); |
|
403 |
69 |
StreamBase::AddMethods(env, os); |
|
404 |
69 |
env->set_streambaseoutputstream_constructor_template(ost); |
|
405 |
69 |
} |
|
406 |
|||
407 |
} // namespace heap |
||
408 |
} // namespace node |
||
409 |
|||
410 |
4955 |
NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize) |
Generated by: GCOVR (Version 3.4) |