-
Notifications
You must be signed in to change notification settings - Fork 376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: collect bootstrap timing metrics #275
Open
yvasiyarov
wants to merge
5
commits into
siimon:master
Choose a base branch
from
yvasiyarov:bootstrap
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
'use strict'; | ||
const Gauge = require('../gauge'); | ||
|
||
let perf_hooks; | ||
|
||
try { | ||
// eslint-disable-next-line | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
// node version is too old | ||
} | ||
|
||
// Constants ordered accordingly with order of events | ||
const NODEJS_NODE_START = 'nodejs_node_start'; | ||
const NODEJS_V8_START = 'nodejs_v8_start'; | ||
const NODEJS_ENVIRONMENT_INITIALIZED = 'nodejs_environment_initialized'; | ||
const NODEJS_BOOTSTRAP_COMPLETE = 'nodejs_bootstrap_complete'; | ||
const NODEJS_LOOP_START = 'nodejs_loop_start'; | ||
|
||
module.exports = (registry, config = {}) => { | ||
if (!perf_hooks) { | ||
return () => {}; | ||
} | ||
|
||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const nodeStart = new Gauge({ | ||
name: namePrefix + NODEJS_NODE_START, | ||
help: 'Node process start time(in seconds).', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const v8Start = new Gauge({ | ||
name: namePrefix + NODEJS_V8_START, | ||
help: 'V8 start time (in seconds).', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const environmentInitialized = new Gauge({ | ||
name: namePrefix + NODEJS_ENVIRONMENT_INITIALIZED, | ||
help: 'Node.js environment initialization complete time (in seconds).', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const bootstrapComplete = new Gauge({ | ||
name: namePrefix + NODEJS_BOOTSTRAP_COMPLETE, | ||
help: 'Node.js bootstrap complete time (in seconds).', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const loopStart = new Gauge({ | ||
name: namePrefix + NODEJS_LOOP_START, | ||
help: 'Node.js event loop start time (in seconds).', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
|
||
return () => { | ||
const entry = perf_hooks.performance.nodeTiming; | ||
const now = Date.now(); | ||
|
||
if (entry.nodeStart !== -1) { | ||
if (config.timestamps) { | ||
nodeStart.set({}, entry.nodeStart, now); | ||
} else { | ||
nodeStart.set({}, entry.nodeStart); | ||
} | ||
} | ||
|
||
if (entry.v8Start !== -1) { | ||
if (config.timestamps) { | ||
v8Start.set({}, entry.v8Start, now); | ||
} else { | ||
v8Start.set({}, entry.v8Start); | ||
} | ||
} | ||
|
||
if (entry.environment !== -1) { | ||
if (config.timestamps) { | ||
environmentInitialized.set({}, entry.environment, now); | ||
} else { | ||
environmentInitialized.set({}, entry.environment); | ||
} | ||
} | ||
|
||
if (entry.loopStart !== -1) { | ||
if (config.timestamps) { | ||
loopStart.set({}, entry.loopStart, now); | ||
} else { | ||
loopStart.set({}, entry.loopStart); | ||
} | ||
} | ||
|
||
if (entry.bootstrapComplete !== -1) { | ||
if (config.timestamps) { | ||
bootstrapComplete.set({}, entry.bootstrapComplete, now); | ||
} else { | ||
bootstrapComplete.set({}, entry.bootstrapComplete); | ||
} | ||
} | ||
}; | ||
}; | ||
|
||
module.exports.metricNames = [ | ||
NODEJS_NODE_START, | ||
NODEJS_V8_START, | ||
NODEJS_ENVIRONMENT_INITIALIZED, | ||
NODEJS_BOOTSTRAP_COMPLETE, | ||
NODEJS_LOOP_START | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
'use strict'; | ||
const Counter = require('../counter'); | ||
const Summary = require('../summary'); | ||
|
||
let perf_hooks; | ||
|
||
try { | ||
// eslint-disable-next-line | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
// node version is too old | ||
} | ||
|
||
const NODEJS_GC_RUNS = 'nodejs_gc_runs'; | ||
const NODEJS_GC_DURATION_SUMMARY = 'nodejs_gc_duration_summary'; | ||
|
||
function gcKindToString(gcKind) { | ||
let gcKindName = ''; | ||
switch (gcKind) { | ||
case perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR: | ||
gcKindName = 'major'; | ||
break; | ||
case perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR: | ||
gcKindName = 'minor'; | ||
break; | ||
case perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL: | ||
gcKindName = 'incremental'; | ||
break; | ||
case perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB: | ||
gcKindName = 'weakcb'; | ||
break; | ||
default: | ||
gcKindName = 'unknown'; | ||
break; | ||
} | ||
return gcKindName; | ||
} | ||
|
||
module.exports = (registry, config = {}) => { | ||
if (!perf_hooks) { | ||
return () => {}; | ||
} | ||
|
||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const gcCount = new Counter({ | ||
name: namePrefix + NODEJS_GC_RUNS, | ||
help: | ||
'Count of garbage collections. gc_type label is one of major, minor, incremental or weakcb.', | ||
labelNames: ['gc_type'], | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const gcSummary = new Summary({ | ||
name: namePrefix + NODEJS_GC_DURATION_SUMMARY, | ||
help: | ||
'Summary of garbage collections. gc_type label is one of major, minor, incremental or weakcb.', | ||
labelNames: ['gc_type'], | ||
maxAgeSeconds: 600, | ||
ageBuckets: 5, | ||
percentiles: [0.5, 0.75, 0.9, 0.99], | ||
registers: registry ? [registry] : undefined | ||
}); | ||
|
||
const obs = new perf_hooks.PerformanceObserver(list => { | ||
const entry = list.getEntries()[0]; | ||
const labels = { gc_type: gcKindToString(entry.kind) }; | ||
|
||
gcCount.inc(labels, 1); | ||
// Convert duration from milliseconds to seconds | ||
gcSummary.observe(labels, entry.duration / 1000); | ||
}); | ||
|
||
// We do not expect too many gc events per second, so we do not use buffering | ||
obs.observe({ entryTypes: ['gc'], buffered: false }); | ||
|
||
return () => {}; | ||
}; | ||
|
||
module.exports.metricNames = [NODEJS_GC_RUNS, NODEJS_GC_DURATION_SUMMARY]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
'use strict'; | ||
|
||
describe('bootstrapTime', () => { | ||
const register = require('../../index').register; | ||
const processHandles = require('../../lib/metrics/bootstrapTime'); | ||
|
||
beforeAll(() => { | ||
register.clear(); | ||
}); | ||
|
||
afterEach(() => { | ||
register.clear(); | ||
}); | ||
|
||
it('should add metric to the registry', () => { | ||
expect(register.getMetricsAsJSON()).toHaveLength(0); | ||
|
||
processHandles()(); | ||
|
||
const metrics = register.getMetricsAsJSON(); | ||
|
||
// Check if perf_hooks module is available | ||
let perf_hooks; | ||
try { | ||
// eslint-disable-next-line | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
// node version is too old | ||
} | ||
|
||
if (perf_hooks) { | ||
expect(metrics).toHaveLength(5); | ||
expect(metrics[0].help).toEqual('Node process start time(in seconds).'); | ||
expect(metrics[0].type).toEqual('gauge'); | ||
expect(metrics[0].name).toEqual('nodejs_node_start'); | ||
|
||
expect(metrics[1].help).toEqual('V8 start time (in seconds).'); | ||
expect(metrics[1].type).toEqual('gauge'); | ||
expect(metrics[1].name).toEqual('nodejs_v8_start'); | ||
|
||
expect(metrics[2].help).toEqual( | ||
'Node.js environment initialization complete time (in seconds).' | ||
); | ||
expect(metrics[2].type).toEqual('gauge'); | ||
expect(metrics[2].name).toEqual('nodejs_environment_initialized'); | ||
|
||
expect(metrics[3].help).toEqual( | ||
'Node.js bootstrap complete time (in seconds).' | ||
); | ||
expect(metrics[3].type).toEqual('gauge'); | ||
expect(metrics[3].name).toEqual('nodejs_bootstrap_complete'); | ||
|
||
expect(metrics[4].help).toEqual( | ||
'Node.js event loop start time (in seconds).' | ||
); | ||
expect(metrics[4].type).toEqual('gauge'); | ||
expect(metrics[4].name).toEqual('nodejs_loop_start'); | ||
} else { | ||
expect(metrics).toHaveLength(0); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
'use strict'; | ||
|
||
describe('gc', () => { | ||
const register = require('../../index').register; | ||
const processHandles = require('../../lib/metrics/gc'); | ||
|
||
beforeAll(() => { | ||
register.clear(); | ||
}); | ||
|
||
afterEach(() => { | ||
register.clear(); | ||
}); | ||
|
||
it('should add metric to the registry', () => { | ||
expect(register.getMetricsAsJSON()).toHaveLength(0); | ||
|
||
processHandles()(); | ||
|
||
const metrics = register.getMetricsAsJSON(); | ||
|
||
// Check if perf_hooks module is available | ||
let perf_hooks; | ||
try { | ||
// eslint-disable-next-line | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
// node version is too old | ||
} | ||
|
||
if (perf_hooks) { | ||
expect(metrics).toHaveLength(2); | ||
|
||
expect(metrics[0].help).toEqual( | ||
'Count of garbage collections. gc_type label is one of major, minor, incremental or weakcb.' | ||
); | ||
expect(metrics[0].type).toEqual('counter'); | ||
expect(metrics[0].name).toEqual('nodejs_gc_runs'); | ||
|
||
expect(metrics[1].help).toEqual( | ||
'Summary of garbage collections. gc_type label is one of major, minor, incremental or weakcb.' | ||
); | ||
expect(metrics[1].type).toEqual('summary'); | ||
expect(metrics[1].name).toEqual('nodejs_gc_duration_summary'); | ||
} else { | ||
expect(metrics).toHaveLength(0); | ||
} | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you disable the specific rule?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry, but its looks like I can not disable only specific rule.
For node 6.x I should use
//eslint-disable-next-line node/no-missing-require
But for newer versions of node I should use
//eslint-disable-next-line node/no-unsupported-features/node-builtins
But I can not specify both rules. So I rolled back this to just
//eslint-disable-next-line