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 | var os = require('os');
|
26 |
|
27 |
|
28 | var nt;
|
29 | var v8tools;
|
30 | var active = false;
|
31 |
|
32 | exports.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 |
|
45 |
|
46 | exports.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 |
|
56 | setTimeout(function() {
|
57 | exports.stopCpuProfiler();
|
58 | }, seconds * 1000);
|
59 | };
|
60 |
|
61 |
|
62 | exports.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 |
|
117 |
|
118 | function 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 |
|
139 | function 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 |
|
166 | function 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 |
|
184 | function 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 |
|
194 | function 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 |
|
211 | function 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 |
|
228 | function genNodeLabel(node) {
|
229 | var name = truncate(node.name);
|
230 | return nodeTypeToString(node.type) + (name ? (": " + name) : "");
|
231 | }
|
232 |
|
233 |
|
234 | exports.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 |
|
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 |
|
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 |
|
360 | };
|
361 |
|
362 |
|