diff --git a/async-interceptor.js b/async-interceptor.js index 646ae87..e507b6f 100644 --- a/async-interceptor.js +++ b/async-interceptor.js @@ -7,27 +7,26 @@ export const AsyncResourceMap = new Map(); export const AsyncInterceptor = async_hooks.createHook({ init(asyncId, type) { - logResourceCreation(asyncId, type); + captureResource(asyncId, type); }, }); -function logResourceCreation(asyncId, type) { - const stack = (new Error()).stack.split('\n').slice(2).filter(line => { - return !['AsyncHook.init', 'node:internal/async_hooks'].some(fn => line.includes(fn)); - }).join('\n'); +function captureResource(asyncId, type) { + let stack = stackTrace(); + + stack = `${type}\n${stack}`; if (AsyncResourceMap.size > GC_LIMIT) { - console.log('Meteor Perf: Garbage collecting async resources'); + console.log('Meteor Perf: Reached AsyncResourceMap limit, garbage collecting'); garbageCollectAsyncResources(); } if (!AsyncResourceMap.has(stack)) { - AsyncResourceMap.set(stack, { count: 0, types: new Set() }); + AsyncResourceMap.set(stack, { count: 0 }); } const resourceInfo = AsyncResourceMap.get(stack); resourceInfo.count++; - resourceInfo.types.add(type); } function garbageCollectAsyncResources() { @@ -36,4 +35,12 @@ function garbageCollectAsyncResources() { AsyncResourceMap.delete(stack); } }); -} \ No newline at end of file +} + +export function stackTrace () { + return (new Error()).stack.split('\n').slice(3).filter(line => { + return !['AsyncHook.init', 'node:internal/async_hooks'].some(fn => line.includes(fn)); + }).join('\n') +} + +setInterval(garbageCollectAsyncResources, 10000); \ No newline at end of file diff --git a/package.js b/package.js index 2645726..de5cea2 100644 --- a/package.js +++ b/package.js @@ -20,7 +20,8 @@ Package.onTest(function(api) { Npm.depends({ 'chai': '4.3.4', 'sinon': '10.0.0', - 'sinon-chai': '3.6.0' + 'sinon-chai': '3.6.0', + 'benchmarkify': '4.0.0' }); api.mainModule('tests/index.js', 'server'); diff --git a/tests/async-interceptor.test.js b/tests/async-interceptor.test.js new file mode 100644 index 0000000..366d5ee --- /dev/null +++ b/tests/async-interceptor.test.js @@ -0,0 +1,38 @@ +import { expect } from 'chai'; +import { AsyncInterceptor, AsyncResourceMap, stackTrace } from '../async-interceptor'; +import Benchmarkify from "benchmarkify"; + +const benchmark = new Benchmarkify("Meteor Perf", { chartImage: true }).printHeader(); + +benchmark.createSuite("Stack Trace", { time: 1000 }) + .add("default error stack trace", () => { + return new Error().stack; + }) + .ref("clean stack trace", () => { + return (new Error()).stack.split('\n').slice(2).filter(line => { + return !['AsyncHook.init', 'node:internal/async_hooks'].some(fn => line.includes(fn)); + }).join('\n'); + }); + + +await benchmark.run(); + +describe('Async Interceptor', () => { + it('get stack trace', async () => { + const stack = stackTrace(); + + expect(stack).to.be.a('string'); + }); + + it('should capture async operations', async () => { + AsyncInterceptor.enable(); + + await new Promise((resolve) => { + setTimeout(resolve, 100); + }); + + AsyncInterceptor.disable(); + + expect(AsyncResourceMap.size).to.be.greaterThan(0); + }) +}) \ No newline at end of file diff --git a/tests/index.js b/tests/index.js index 6d67538..1895563 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1 +1,2 @@ -import './observer.test'; \ No newline at end of file +import './observer.test'; +import './async-interceptor.test'; \ No newline at end of file diff --git a/tests/observer.test.js b/tests/observer.test.js index 7c1c054..4b6d26e 100644 --- a/tests/observer.test.js +++ b/tests/observer.test.js @@ -1,5 +1,4 @@ import { StatDict } from '../observer-monitor'; -import { MeteorPerf } from '../index'; import { expect } from 'chai'; const coll = new Mongo.Collection('test') @@ -11,10 +10,7 @@ describe('Observer', () => { await coll.removeAsync({}, { multi: true }) observer = await coll.find({}).observeChanges({ - added: (...args) => { - console.trace() - console.log('added', args); - }, + added: (...args) => console.log('added', args), changed: (...args) => console.log('changed', args), removed: (...args) => console.log('removed', args), }) @@ -26,14 +22,7 @@ describe('Observer', () => { }); it('should detect observer creation', async () => { - console.log(await coll.insertAsync({ foo: 'bar' })) - console.log(await coll.insertAsync({ foo: 'bar' })) - console.log(await coll.insertAsync({ foo: 'bar' })) - console.log(await coll.insertAsync({ foo: 'bar' })) - - Meteor._sleepForMs(5000) - - console.log({ StatDict, MeteorPerf}) + await coll.insertAsync({ foo: 'bar' }) expect(StatDict.size).to.equal(1); expect(StatDict.get('test::{}')).to.have.property('key', 'test::{}');