UNPKG

8.68 kBJavaScriptView Raw
1/*
2 * Copyright (c) 2012 Dmitri Melikyan
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to permit
9 * persons to whom the Software is furnished to do so, subject to the
10 * following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
18 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24
25var os = require('os');
26
27
28var nt;
29var v8tools;
30var active = false;
31
32exports.init = function() {
33 nt = global.nodetime;
34
35 try {
36 v8tools = require('v8tools');
37 }
38 catch(err) {
39 nt.error(err);
40 }
41}
42
43
44/* CPU profiler */
45
46exports.startCpuProfiler = function(seconds) {
47 if(!v8tools || active) return;
48 active = true;
49
50 seconds || (seconds = 10);
51
52 v8tools.startV8Profiler();
53 nt.message("V8 CPU profiler started");
54
55 // stop v8 profiler automatically after 10 seconds
56 setTimeout(function() {
57 exports.stopCpuProfiler();
58 }, seconds * 1000);
59};
60
61
62exports.stopCpuProfiler = function() {
63 if(!v8tools || !active) return;
64
65 var nodes = {};
66 var root = undefined;
67 var rootSamplesCount = undefined;
68
69 v8tools.stopV8Profiler(function(parentCallUid, callUid, totalSamplesCount, functionName, scriptResourceName, lineNumber) {
70 if(rootSamplesCount === undefined)
71 rootSamplesCount = totalSamplesCount;
72
73 var cpuUsage = ((totalSamplesCount * 100) / rootSamplesCount || 1);
74 var obj = {
75 _totalSamplesCount: totalSamplesCount,
76 _functionName: functionName,
77 _scriptResourceName: scriptResourceName,
78 _lineNumber: lineNumber,
79 _cpuUsage: cpuUsage,
80 _id: nt.nextId++,
81 _target: [],
82 _label: cpuUsage.toFixed(2) + "% - " + functionName
83 };
84
85 if(scriptResourceName && lineNumber)
86 obj._label += " (" + scriptResourceName + ":" + lineNumber + ")";
87
88 nodes[callUid] = obj;
89 if(root === undefined) {
90 root = obj;
91 }
92
93 if(parentCallUid) {
94 var parentNode = nodes[parentCallUid];
95 if(parentNode) parentNode._target.push(obj);
96 }
97 });
98 nt.message("V8 CPU profiler stopped");
99
100 if(root) {
101 var profile = {};
102 profile._id = nt.nextId++;
103 profile._label = os.hostname() + ' [' + process.pid + ']';
104 profile._ts = nt.millis();
105 profile._ns = 'cpu-profiles';
106 profile.root = root;
107
108 nt.agent.send({cmd: 'updateData', args: profile});
109 }
110
111 active = false;
112};
113
114
115
116/* Heap profiler */
117
118function edgeTypeToString(type) {
119 switch(type) {
120 case 0:
121 return 'variable';
122 case 1:
123 return 'element';
124 case 2:
125 return 'property';
126 case 3:
127 return 'internal';
128 case 4:
129 return 'hidden';
130 case 5:
131 return 'shortcut';
132 case 6:
133 return 'weak';
134 default:
135 return 'other';
136 }
137}
138
139function nodeTypeToString(type) {
140 switch(type) {
141 case 0:
142 return 'hidden';
143 case 1:
144 return 'array';
145 case 2:
146 return 'string';
147 case 3:
148 return 'object';
149 case 4:
150 return 'compiled code';
151 case 5:
152 return 'function clojure';
153 case 6:
154 return 'regexp';
155 case 7:
156 return 'heap number';
157 case 7:
158 return 'native object';
159 default:
160 return 'other';
161 }
162}
163
164
165
166function calculateRetainedSize(depth, walked, node) {
167 if(depth++ > 1000) return 0;
168 walked[node.nodeUid] = true;
169
170 node.retainedSize += node.selfSize;
171
172 node.children.forEach(function(childNode) {
173 if(walked[childNode.nodeUid] || childNode.retainersCount > 1) return;
174
175 if(!childNode.retainedSize) {
176 calculateRetainedSize(depth + 1, walked, childNode);
177 }
178
179 node.retainedSize += childNode.retainedSize;
180 });
181}
182
183
184function genKey(node) {
185 if(node.retainerType == 0 || node.retainerType == 2) {
186 return edgeTypeToString(node.retainerType) + ':' + node.retainerName;
187 }
188 else {
189 return edgeTypeToString(node.retainerType);
190 }
191}
192
193
194function genGroupLabel(node) {
195 switch(node.retainerType) {
196 case 0:
197 return 'Variable: ' + node.retainerName;
198 case 1:
199 return 'Array elements';
200 case 2:
201 return 'Property: ' + node.retainerName;
202 case 4:
203 return 'Hidden links';
204 case 6:
205 return 'Weak references';
206 default:
207 return 'Other';
208 }
209}
210
211function truncate(obj) {
212 if(!obj) return undefined;
213
214 if(typeof(obj) === 'string') {
215 if(obj.length > 25) {
216 return obj.substring(0, 25) + '...';
217 }
218 else {
219 return obj;
220 }
221 }
222 else if(typeof(obj) === 'number') {
223 return obj;
224 }
225}
226
227
228function genNodeLabel(node) {
229 var name = truncate(node.name);
230 return nodeTypeToString(node.type) + (name ? (": " + name) : "");
231}
232
233
234exports.takeHeapSnapshot = function() {
235 if(!v8tools || active) return;
236 active = true;
237
238 nt.message("V8 heap profiler starting...");
239
240 var seen = {};
241 var groups = {};
242 var totalSize = 0;
243 var totalCount = 0;
244
245 var nodes = {};
246 v8tools.takeHeapSnapshot(function(parentNodeUid, nodeUid, name, type, selfSize, retainerName, retainerType) {
247 if(retainerType === 5) return;
248
249 var node = nodes[nodeUid];
250 if(!node) {
251 node = nodes[nodeUid] = {
252 nodeUid: nodeUid,
253 name: name,
254 type: type,
255 selfSize: selfSize,
256 retainerName: retainerName,
257 retainerType: retainerType,
258 retainedSize: 0,
259 retainersCount: 0,
260 parents: {},
261 children: []
262 }
263 }
264
265 var parentNode = nodes[parentNodeUid];
266 if(parentNode) {
267 parentNode.children.push(node);
268 if(!node.parents[parentNodeUid]) {
269 node.parents[parentNodeUid] = true;
270 node.retainersCount++;
271 }
272 }
273 });
274
275
276 for(var prop in nodes) {
277 var node = nodes[prop];
278 if(node.retainerType && node.retainerType !== 3) {
279 if(!node.retainedSize) calculateRetainedSize(0, {}, node);
280 if(node.retainersCount > 1) totalSize += node.selfSize;
281
282 var key = genKey(node);
283 var obj = groups[key];
284 if(!obj) {
285 obj = groups[key] = {
286 _id: nt.nextId++,
287 _label: genGroupLabel(node),
288 size: 0,
289 count: 0,
290 instances: []
291 };
292 }
293
294 obj.size += node.retainedSize;
295 obj.count++;
296
297 obj.instances.push({
298 _id: nt.nextId++,
299 _label: genNodeLabel(node),
300 _retainedSize: node.retainedSize,
301 'Id': node.nodeUid,
302 'Name': truncate(node.name),
303 'Type': nodeTypeToString(node.type),
304 'Self size (KB)': (node.selfSize / 1024).toFixed(3),
305 'Retained size (KB)': (node.retainedSize / 1024).toFixed(3)
306 });
307 }
308
309 totalSize += node.selfSize;
310 totalCount++;
311 }
312
313 // sort groups
314 var groupsOrdered = [];
315 for(var key in groups) {
316 groupsOrdered.push(groups[key]);
317 }
318 groupsOrdered = groupsOrdered.sort(function(a, b) {
319 return b.size - a.size;
320 });
321 groupsOrdered = groupsOrdered.slice(0, 100);
322
323
324 // prepare for rendering
325 for(var key in groups) {
326 var obj = groups[key];
327
328 obj.instances = obj.instances.sort(function(a, b) {
329 return b._retainedSize - a._retainedSize;
330 });
331 obj.instances = obj.instances.slice(0, 10);
332
333 obj['Size (KB)'] = (obj.size / 1024).toFixed(3);
334 if(totalSize > 0) obj['Size (%)'] = Math.round((obj.size / totalSize) * 100);
335 obj._label = obj['Size (%)'] + "% - " + obj._label;
336
337 obj['Count'] = obj.count;
338 if(totalCount > 0) obj['Count (%)'] = Math.round((obj.count / totalCount) * 100);
339
340 obj['Largest instances'] = obj.instances;
341
342 delete obj.size;
343 delete obj.count;
344 delete obj.instances;
345 }
346 nt.message("V8 heap profiler stopped");
347
348 var snapshot = {};
349 snapshot._id = nt.nextId++;
350 snapshot._label = os.hostname() + ' [' + process.pid + ']';
351 snapshot._ts = nt.millis();
352 snapshot._ns = 'heap-snapshots';
353 snapshot['Retainers'] = groupsOrdered;
354
355 nt.agent.send({cmd: 'updateData', args: snapshot});
356
357 active = false;
358
359 //console.log(require('util').inspect(groupsOrdered, true, 20, true));
360};
361
362