v8-profiler-n-api
Version:
node bindings for the v8 profiler
331 lines (257 loc) • 8.21 kB
JavaScript
;
const Stream = require('stream').Stream;
const inherits = require('util').inherits;
const binding = require('./lib/binding');
const workerThreads = require('./lib/worker_threads');
function global_check() {
const version = process.versions.node.split('.').map(item => Number(item));
const greater_than_12_15_0 = version[0] > 12 || (version[0] === 12 && version[1] > 15);
return { greater_than_12_15_0 };
}
binding.setup({
debug: !!process.env.V8_PROFILER_DEBUG,
thread_id: workerThreads.threadId
}, global_check);
function nodes(snapshot) {
let n = snapshot.nodesCount, i, nodes = [];
for (i = 0; i < n; i++) {
nodes[i] = snapshot.getNode(i);
}
return nodes;
}
function nodesHist(snapshot) {
let objects = {};
nodes(snapshot).forEach(function (node) {
let key = node.type === 'Object' ? node.name : node.type;
objects[key] = objects[node.name] || 0;
objects[key]++;
});
return objects;
}
function Snapshot() { }
Snapshot.prototype.getHeader = function () {
return {
typeId: this.typeId,
uid: this.uid,
title: this.title
};
};
/**
* @param {Snapshot} other
* @returns {Object}
*/
Snapshot.prototype.compare = function (other) {
let selfHist = nodesHist(this),
otherHist = nodesHist(other),
keys = Object.keys(selfHist).concat(Object.keys(otherHist)),
diff = {};
keys.forEach(function (key) {
if (key in diff) { return; }
let selfCount = selfHist[key] || 0,
otherCount = otherHist[key] || 0;
diff[key] = otherCount - selfCount;
});
return diff;
};
function ExportStream() {
Stream.Transform.call(this);
this._transform = function noTransform(chunk, encoding, done) {
done(null, chunk);
};
}
inherits(ExportStream, Stream.Transform);
/**
* @param {Stream.Writable|function} dataReceiver
* @returns {Stream|undefined}
*/
Snapshot.prototype.export = function (dataReceiver) {
dataReceiver = dataReceiver || new ExportStream();
let toStream = dataReceiver instanceof Stream,
chunks = toStream ? null : [];
function onChunk(chunk, len) {
if (toStream) { dataReceiver.write(chunk); }
else { chunks.push(chunk); }
}
function onDone() {
if (toStream) { dataReceiver.end(); }
else { dataReceiver(null, chunks.join('')); }
}
this.serialize(onChunk, onDone);
return toStream ? dataReceiver : undefined;
};
function CpuProfile() { }
CpuProfile.prototype.getHeader = function () {
return {
typeId: this.typeId,
uid: this.uid,
title: this.title
};
};
CpuProfile.prototype.export = function (dataReceiver) {
dataReceiver = dataReceiver || new ExportStream();
let toStream = dataReceiver instanceof Stream;
let error, result;
try {
result = JSON.stringify(this);
} catch (err) {
error = err;
}
process.nextTick(function () {
if (toStream) {
if (error) {
dataReceiver.emit('error', error);
}
dataReceiver.end(result);
} else {
dataReceiver(error, result);
}
});
return toStream ? dataReceiver : undefined;
};
function SamplingHeapProfile() { }
SamplingHeapProfile.prototype.export = function (dataReceiver) {
dataReceiver = dataReceiver || new ExportStream();
let toStream = dataReceiver instanceof Stream;
let error, result;
try {
result = JSON.stringify(this);
} catch (err) {
error = err;
}
process.nextTick(function () {
if (toStream) {
if (error) {
dataReceiver.emit('error', error);
}
dataReceiver.end(result);
} else {
dataReceiver(error, result);
}
});
return toStream ? dataReceiver : undefined;
};
let startTime, endTime;
let activeProfiles = [];
let profiler = {
/*HEAP PROFILER API*/
get snapshots() { return binding.heap.snapshots; },
takeSnapshot: function (name, control) {
if (typeof name === 'function') {
control = name;
name = '';
}
if (typeof control !== 'function') {
control = function noop() { };
}
name = name ? '' + name : '';
let snapshot = binding.heap.takeSnapshot(name, control);
snapshot.__proto__ = Snapshot.prototype;
snapshot.title = name ? name : snapshot.title;
return snapshot;
},
deleteAllSnapshots: function () {
Object.keys(binding.heap.snapshots).forEach(function (key) {
binding.heap.snapshots[key].delete();
});
},
startTrackingHeapObjects: function () {
binding.heap.startTrackingHeapObjects();
},
stopTrackingHeapObjects: function () {
binding.heap.stopTrackingHeapObjects();
},
getHeapStats: function (iterator, callback) {
if (typeof iterator !== 'function') { iterator = function noop() { }; }
if (typeof callback !== 'function') { callback = function noop() { }; }
return binding.heap.getHeapStats(iterator, callback);
},
getObjectByHeapObjectId: function (id) {
id = parseInt(id, 10);
if (isNaN(id)) { return; }
return binding.heap.getObjectByHeapObjectId(id);
},
getHeapObjectId: function (value) {
if (!arguments.length) { return; }
return binding.heap.getHeapObjectId(value);
},
/*CPU PROFILER API*/
get profiles() { return binding.cpu.profiles; },
setGenerateType: function (type) {
const types = [0, 1];
if (types.indexOf(type) > -1) {
binding.cpu.setGenerateType(type);
} else {
console.error(`type should in [${types.join(', ')}], got ${type}.`);
}
},
setProfilesCleanupLimit: function (limit) {
if (!isNaN(limit)) {
binding.cpu.setProfilesCleanupLimit(Number(limit));
} else {
console.error(`limit should be number.`);
}
},
startProfiling: function (name, recsamples, mode) {
if (activeProfiles.length === 0 && typeof process._startProfilerIdleNotifier === 'function') {
process._startProfilerIdleNotifier();
}
if (typeof name === 'boolean') {
recsamples = name;
name = '';
}
if ([0, 1].indexOf(mode) === -1) {
mode = 0;
}
recsamples = recsamples === undefined ? true : Boolean(recsamples);
name = name && '' + name || '';
if (activeProfiles.indexOf(name) < 0) { activeProfiles.push(name); }
startTime = Date.now();
binding.cpu.startProfiling(name, recsamples, mode);
},
stopProfiling: function (name) {
let index = activeProfiles.indexOf(name);
if (name && index < 0) { return; }
let profile = binding.cpu.stopProfiling(name);
endTime = Date.now();
profile.__proto__ = CpuProfile.prototype;
if (!profile.startTime) { profile.startTime = startTime; }
if (!profile.endTime) { profile.endTime = endTime; }
if (name) { activeProfiles.splice(index, 1); }
else { activeProfiles.length = activeProfiles.length - 1; }
if (activeProfiles.length === 0 && typeof process._stopProfilerIdleNotifier === 'function') {
process._stopProfilerIdleNotifier();
}
return profile;
},
setSamplingInterval: function (num) {
if (activeProfiles.length) {
throw new Error('setSamplingInterval must be called when there are no profiles being recorded.');
}
num = parseInt(num, 10) || 1000;
binding.cpu.setSamplingInterval(num);
},
deleteAllProfiles: function () {
Object.keys(binding.cpu.profiles).forEach(function (key) {
binding.cpu.profiles[key].delete();
});
},
collectSample: function () {
if (process.versions.modules >= 48) {
binding.cpu.collectSample();
}
},
/*SAMPLING HEAP PROFILER API*/
startSamplingHeapProfiling: function (interval, depth) {
if (process.versions.modules < 48) { throw new Error('Sampling heap profiler needs node version >= node-v6.0.0!'); }
if (arguments.length === 2) { binding.samplingHeap.startSamplingHeapProfiling(interval, depth); }
else { binding.samplingHeap.startSamplingHeapProfiling(); }
},
stopSamplingHeapProfiling: function () {
if (process.versions.modules < 48) { throw new Error('Sampling heap profiler needs node version >= node-v6.0.0!'); }
let profile = binding.samplingHeap.stopSamplingHeapProfiling();
profile.__proto__ = SamplingHeapProfile.prototype;
return profile;
}
};
module.exports = profiler;
process.profiler = profiler;