Skip to content

Commit c50ced0

Browse files
authored
Merge pull request #3 from odedglas/monitor
2 parents 7f1eb0f + 8975b49 commit c50ced0

File tree

9 files changed

+149
-14
lines changed

9 files changed

+149
-14
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ Returns the current execution context identified with the current asyncId.
7474

7575
Runs a given function under a dedicated AsyncResource, exposing given initial context to the process and it's child processes.
7676

77+
### monitor(): ExecutionMapUsage
78+
79+
Returns an monitoring report over the current execution map resources
80+
7781
### API Usage
7882

7983

@@ -107,6 +111,7 @@ Promise.resolve().then(() => {
107111
}, 1000);
108112

109113
console.log(Context.get()); // outputs: {"value": true}
114+
console.log(Context.monitor) // Will result with monitor result
110115
});
111116
});
112117
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-execution-context",
3-
"version": "1.1.4",
3+
"version": "1.1.5",
44
"description": "Provides execution context wrapper for node JS, can be used to create execution wrapper for handling requests and more",
55
"author": "Oded Goldglas <odedglas@gmail.com>",
66
"license": "ISC",

src/hooks/constants.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/**
22
* The excluded async process from our context map.
33
* Connections async processes tend not to be destroyed, which potentially will cause memory leak issues.
4-
* We can skip those types assuming the execution context won't be used for process types.
4+
* We can skip those types assuming the execution context won't be used for these process types.
55
* @type {Set<String>}
66
*/
77
exports.EXCLUDED_ASYNC_TYPES = new Set([
88
'DNSCHANNEL',
99
'TLSWRAP',
1010
'TCPWRAP',
1111
'HTTPPARSER',
12-
'ZLIB'
12+
'ZLIB',
13+
'UDPSENDWRAP',
14+
'UDPWRAP'
1315
]);

src/hooks/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ const init = (executionContextMap) => (asyncId, type, triggerAsyncId) => {
3333

3434
// Setting child process entry as ref to parent context
3535
executionContextMap.set(asyncId, {
36-
ref
36+
ref,
37+
type,
38+
created: Date.now()
3739
});
3840

39-
const { context = {}, children = [] } = executionContextMap.get(ref);
41+
const { context = {}, children = [], ...meta } = executionContextMap.get(ref);
4042

4143
// Adding current async as child to parent context in order to control cleanup better
4244
executionContextMap.set(ref, {
45+
...meta,
4346
context,
4447
children: [...children, asyncId]
4548
});
@@ -53,7 +56,7 @@ const init = (executionContextMap) => (asyncId, type, triggerAsyncId) => {
5356
* @param {Number} ref - The parent process ref asyncId
5457
*/
5558
const onChildProcessDestroy = (executionContextMap, asyncId, ref) => {
56-
const { children: parentChildren, context } = executionContextMap.get(ref);
59+
const { children: parentChildren, context, ...meta } = executionContextMap.get(ref);
5760
const children = parentChildren.filter((id) => id !== asyncId);
5861

5962
// Parent context will be released upon last child removal
@@ -64,6 +67,7 @@ const onChildProcessDestroy = (executionContextMap, asyncId, ref) => {
6467
}
6568

6669
executionContextMap.set(ref, {
70+
...meta,
6771
context,
6872
children
6973
});

src/hooks/spec.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ describe('Context / Hooks', () => {
6060
it('Register sub process as ref entry under root process', () => {
6161
expect(executionMap.size).toEqual(2);
6262

63-
expect(executionMap.get(childAsyncId)).toEqual({
63+
expect(executionMap.get(childAsyncId)).toMatchObject({
6464
ref: triggerAsyncId
6565
});
6666
});
@@ -76,7 +76,7 @@ describe('Context / Hooks', () => {
7676
const nestedChildAsyncId = await spawn(childAsyncId);
7777

7878
expect(executionMap.size).toEqual(3);
79-
expect(executionMap.get(nestedChildAsyncId)).toEqual({
79+
expect(executionMap.get(nestedChildAsyncId)).toMatchObject({
8080
ref: triggerAsyncId
8181
});
8282

@@ -152,11 +152,11 @@ describe('Context / Hooks', () => {
152152
it('Triggers parent cleanup when all child process died', (done) => {
153153
children.forEach(destroy);
154154

155-
process.nextTick(() => {
155+
setTimeout(() => {
156156
expect(executionMap.size).toEqual(0);
157157

158158
done();
159-
});
159+
}, 200)
160160
});
161161
});
162162
});

src/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const asyncHooks = require('async_hooks');
22
const ExecutionContextResource = require('./lib/ExecutionContextResource')
3-
const { isProduction } = require('./lib');
3+
const { isProduction, monitorMap } = require('./lib');
44
const { create: createHooks } = require('./hooks');
55
const { ExecutionContextErrors } = require('./constants');
66

@@ -48,7 +48,9 @@ const createExecutionContext = () => {
4848
if (executionContextMap.has(asyncId)) handleError(ExecutionContextErrors.CONTEXT_ALREADY_DECLARED);
4949

5050
executionContextMap.set(asyncId, {
51+
asyncId,
5152
context: { ...initialContext, executionId: asyncId },
53+
created: Date.now(),
5254
children: []
5355
});
5456
},
@@ -105,6 +107,14 @@ const createExecutionContext = () => {
105107

106108
fn();
107109
});
110+
},
111+
112+
/**
113+
* Monitors current execution map usage
114+
* @return {ExecutionMapUsage}
115+
*/
116+
monitor: () => {
117+
return monitorMap(executionContextMap);
108118
}
109119
};
110120

src/lib/index.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,45 @@ const PRODUCTION = 'production';
66

77
const env = process.env.NODE_ENV || PRODUCTION;
88

9+
/**
10+
* Calculates a duration between a given moment and now
11+
* @param {Number} now - The current time
12+
* @param {Number} created - The created time to calculate it's duration
13+
* @return {Number}
14+
*/
15+
const getDuration = (now, created) => now - created
16+
917
module.exports = {
1018
env,
19+
1120
isProduction: (environment = env) => environment === PRODUCTION,
12-
isUndefined: (thing) => [null, undefined].includes(thing)
21+
22+
isUndefined: (thing) => [null, undefined].includes(thing),
23+
24+
/**
25+
* Returns a monitoring report over the "executionContext" memory usage.
26+
* @param {ExecutionContextMap} executionContextMap The execution map to monitor
27+
* @return {ExecutionMapUsage}
28+
*/
29+
monitorMap: (executionContextMap) => {
30+
const now = Date.now();
31+
const entries = [...executionContextMap.values()]
32+
.filter(({ children }) => !!children)
33+
.map(({ asyncId, created, children, context = {} }) => ({
34+
asyncId,
35+
created,
36+
contextSize: JSON.stringify(context).length,
37+
duration: getDuration(now, created),
38+
children: children.map((childId) => {
39+
const { type, created } = executionContextMap.get(childId);
40+
41+
return { asyncId: childId, type, created, duration: getDuration(now, created) }
42+
})
43+
}));
44+
45+
return {
46+
size: executionContextMap.size,
47+
entries
48+
}
49+
}
1350
};

src/lib/spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const lib = require ('.');
2+
const Context = require('../');
23

34
describe('Lib', () => {
45
describe('isProduction', () => {
@@ -32,4 +33,55 @@ describe('Lib', () => {
3233
expect(lib.isUndefined(val)).toBeTruthy();
3334
});
3435
});
36+
37+
describe('monitorMap', () => {
38+
describe('When no context is open', () => {
39+
let report;
40+
beforeEach(() => {
41+
report = Context.monitor();
42+
});
43+
it('Returns empty usage', () => {
44+
expect(report.size).toEqual(0);
45+
});
46+
47+
it('Return empty array entries', () => {
48+
expect(Array.isArray(report.entries)).toBeTruthy();
49+
expect(report.entries).toHaveLength(0);
50+
});
51+
});
52+
53+
describe('When context is created', () => {
54+
const contextAware = (fn) => {
55+
Context.create({ value: true });
56+
fn();
57+
};
58+
59+
const spwan = () => new Promise((resolve) => setTimeout(resolve, 100));
60+
61+
describe('When a single process is present', () => {
62+
it('Reports with empty usage', () => {
63+
contextAware(() => {
64+
const report = Context.monitor();
65+
66+
expect(report.size).toEqual(1);
67+
expect(report.entries).toHaveLength(1);
68+
expect(report.entries[0].children).toHaveLength(0);
69+
});
70+
});
71+
});
72+
73+
describe('When sub process is present', () => {
74+
it('Reports root context entries', (done) => {
75+
contextAware(() => {
76+
spwan();
77+
const report = Context.monitor();
78+
79+
expect(report.size > 0).toBeTruthy();
80+
expect(report.entries.length > 0).toBeTruthy();
81+
done()
82+
});
83+
});
84+
});
85+
});
86+
});
3587
});

src/types.d.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ interface HookCallbacks {
3131
destroy?(asyncId: number): void;
3232
}
3333

34+
interface ExecutionMapUsageBaseEntry {
35+
asyncId: number;
36+
created: number;
37+
duration: number;
38+
}
39+
40+
interface ExecutionMapUsageChildEntry extends ExecutionMapUsageBaseEntry {
41+
type: string;
42+
}
43+
44+
interface ExecutionMapUsageEntry extends ExecutionMapUsageBaseEntry {
45+
asyncId: number;
46+
children: ExecutionMapUsageChildEntry[];
47+
}
48+
49+
interface ExecutionMapUsage {
50+
size: number;
51+
entries: ExecutionMapUsageEntry[];
52+
}
53+
3454
interface ExecutionContextAPI {
3555

3656
/**
@@ -48,12 +68,17 @@ interface ExecutionContextAPI {
4868
/**
4969
* Gets the current async process execution context.
5070
*/
51-
get(): object
71+
get(): object;
5272

5373
/**
5474
* Runs a given function within an async resource context
5575
* @param fn
5676
* @param initialContext
5777
*/
58-
run(fn: Function, initialContext: object): void
78+
run(fn: Function, initialContext: object): void;
79+
80+
/**
81+
* Monitors the current execution map usage
82+
*/
83+
monitor(): ExecutionMapUsage;
5984
}

0 commit comments

Comments
 (0)