Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2017-08-08 01:53:24 +0300
committerMyles Borins <mylesborins@google.com>2017-09-12 04:18:33 +0300
commitf75faddb1f7ffb6b713f8fced7dc499eb6632df8 (patch)
tree4030ecd4f16f19304a31eee0ad0bb5d403fdde41 /test
parent8cee5d66bdadffd0a51cab70956da011937a9868 (diff)
perf_hooks: implementation of the perf timing API
An initial implementation of the Performance Timing API for Node.js. This is the same Performance Timing API implemented by modern browsers with a number of Node.js specific properties. The User Timing mark() and measure() APIs are implemented, garbage collection timing, and node startup milestone timing. ```js const { performance } = require('perf_hooks'); performance.mark('A'); setTimeout(() => { performance.mark('B'); performance.measure('A to B', 'A', 'B'); const entry = performance.getEntriesByName('A to B', 'measure')[0]; console.log(entry.duration); }, 10000); ``` The implementation is at the native layer and makes use of uv_hrtime(). This should enable *eventual* integration with things like Tracing and Inspection. The implementation is extensible and should allow us to add new performance entry types as we go (e.g. for measuring i/o perf, etc). Documentation and a test are provided. PR-URL: https://github.com/nodejs/node/pull/14680 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Diffstat (limited to 'test')
-rw-r--r--test/parallel/test-performance-function.js93
-rwxr-xr-xtest/parallel/test-performance-gc.js51
-rw-r--r--test/parallel/test-performance.js124
-rw-r--r--test/parallel/test-performanceobserver.js139
4 files changed, 407 insertions, 0 deletions
diff --git a/test/parallel/test-performance-function.js b/test/parallel/test-performance-function.js
new file mode 100644
index 00000000000..8dd4d3004ba
--- /dev/null
+++ b/test/parallel/test-performance-function.js
@@ -0,0 +1,93 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+
+const {
+ performance,
+ PerformanceObserver
+} = require('perf_hooks');
+
+{
+ // Intentional non-op. Do not wrap in common.mustCall();
+ const n = performance.timerify(() => {});
+ n();
+ const entries = performance.getEntriesByType('function');
+ assert.strictEqual(entries.length, 0);
+
+ const obs = new PerformanceObserver(common.mustCall((list) => {
+ const entries = list.getEntries();
+ const entry = entries[0];
+ assert(entry);
+ assert.strictEqual(entry.name, 'performance.timerify');
+ assert.strictEqual(entry.entryType, 'function');
+ assert.strictEqual(typeof entry.duration, 'number');
+ assert.strictEqual(typeof entry.startTime, 'number');
+ obs.disconnect();
+ performance.clearFunctions();
+ }));
+ obs.observe({ entryTypes: ['function'] });
+ n();
+}
+
+{
+ // If the error throws, the error should just be bubbled up and the
+ // performance timeline entry will not be reported.
+ const obs = new PerformanceObserver(common.mustNotCall());
+ obs.observe({ entryTypes: ['function'] });
+ const n = performance.timerify(() => {
+ throw new Error('test');
+ });
+ assert.throws(() => n(), /^Error: test$/);
+ const entries = performance.getEntriesByType('function');
+ assert.strictEqual(entries.length, 0);
+ obs.disconnect();
+}
+
+{
+ class N {}
+ const n = performance.timerify(N);
+ new n();
+ const entries = performance.getEntriesByType('function');
+ assert.strictEqual(entries.length, 0);
+
+ const obs = new PerformanceObserver(common.mustCall((list) => {
+ const entries = list.getEntries();
+ const entry = entries[0];
+ assert.strictEqual(entry[0], 1);
+ assert.strictEqual(entry[1], 'abc');
+ assert(entry);
+ assert.strictEqual(entry.name, 'N');
+ assert.strictEqual(entry.entryType, 'function');
+ assert.strictEqual(typeof entry.duration, 'number');
+ assert.strictEqual(typeof entry.startTime, 'number');
+ obs.disconnect();
+ performance.clearFunctions();
+ }));
+ obs.observe({ entryTypes: ['function'] });
+
+ new n(1, 'abc');
+}
+
+{
+ [1, {}, [], null, undefined, Infinity].forEach((i) => {
+ assert.throws(() => performance.timerify(i),
+ common.expectsError({
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message: 'The "fn" argument must be of type Function'
+ }));
+ });
+}
+
+// Function can only be wrapped once, also check length and name
+{
+ const m = (a, b = 1) => {};
+ const n = performance.timerify(m);
+ const o = performance.timerify(m);
+ const p = performance.timerify(n);
+ assert.strictEqual(n, o);
+ assert.strictEqual(n, p);
+ assert.strictEqual(n.length, m.length);
+ assert.strictEqual(n.name, 'timerified m');
+}
diff --git a/test/parallel/test-performance-gc.js b/test/parallel/test-performance-gc.js
new file mode 100755
index 00000000000..1ff4c9ae629
--- /dev/null
+++ b/test/parallel/test-performance-gc.js
@@ -0,0 +1,51 @@
+// Flags: --expose-gc
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const {
+ performance,
+ PerformanceObserver
+} = require('perf_hooks');
+
+const {
+ NODE_PERFORMANCE_GC_MAJOR,
+ NODE_PERFORMANCE_GC_MINOR,
+ NODE_PERFORMANCE_GC_INCREMENTAL,
+ NODE_PERFORMANCE_GC_WEAKCB
+} = process.binding('performance').constants;
+
+const kinds = [
+ NODE_PERFORMANCE_GC_MAJOR,
+ NODE_PERFORMANCE_GC_MINOR,
+ NODE_PERFORMANCE_GC_INCREMENTAL,
+ NODE_PERFORMANCE_GC_WEAKCB
+];
+
+// No observers for GC events, no entries should appear
+{
+ global.gc();
+ const entries = performance.getEntriesByType('gc');
+ assert.strictEqual(entries.length, 0);
+}
+
+// Adding an observer should force at least one gc to appear
+{
+ const obs = new PerformanceObserver(common.mustCallAtLeast((list) => {
+ const entry = list.getEntries()[0];
+ assert(entry);
+ assert.strictEqual(entry.name, 'gc');
+ assert.strictEqual(entry.entryType, 'gc');
+ assert(kinds.includes(entry.kind));
+ assert.strictEqual(typeof entry.startTime, 'number');
+ assert.strictEqual(typeof entry.duration, 'number');
+
+ performance.clearGC();
+
+ const entries = performance.getEntriesByType('gc');
+ assert.strictEqual(entries.length, 0);
+ obs.disconnect();
+ }));
+ obs.observe({ entryTypes: ['gc'] });
+ global.gc();
+}
diff --git a/test/parallel/test-performance.js b/test/parallel/test-performance.js
new file mode 100644
index 00000000000..62fb13ed289
--- /dev/null
+++ b/test/parallel/test-performance.js
@@ -0,0 +1,124 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { performance } = require('perf_hooks');
+
+assert(performance);
+assert(performance.nodeTiming);
+assert.strictEqual(typeof performance.timeOrigin, 'number');
+
+{
+ const entries = performance.getEntries();
+ assert(Array.isArray(entries));
+ assert.strictEqual(entries.length, 1);
+ assert.strictEqual(entries[0], performance.nodeTiming);
+}
+
+{
+ const entries = performance.getEntriesByName('node');
+ assert(Array.isArray(entries));
+ assert.strictEqual(entries.length, 1);
+ assert.strictEqual(entries[0], performance.nodeTiming);
+}
+
+{
+ let n;
+ let entries = performance.getEntries();
+ for (n = 0; n < entries.length; n++) {
+ const entry = entries[n];
+ assert.notStrictEqual(entry.name, 'A');
+ assert.notStrictEqual(entry.entryType, 'mark');
+ }
+ performance.mark('A');
+ entries = performance.getEntries();
+ const markA = entries[1];
+ assert.strictEqual(markA.name, 'A');
+ assert.strictEqual(markA.entryType, 'mark');
+ performance.clearMarks('A');
+ entries = performance.getEntries();
+ for (n = 0; n < entries.length; n++) {
+ const entry = entries[n];
+ assert.notStrictEqual(entry.name, 'A');
+ assert.notStrictEqual(entry.entryType, 'mark');
+ }
+}
+
+{
+ let entries = performance.getEntries();
+ for (let n = 0; n < entries.length; n++) {
+ const entry = entries[n];
+ assert.notStrictEqual(entry.name, 'B');
+ assert.notStrictEqual(entry.entryType, 'mark');
+ }
+ performance.mark('B');
+ entries = performance.getEntries();
+ const markB = entries[1];
+ assert.strictEqual(markB.name, 'B');
+ assert.strictEqual(markB.entryType, 'mark');
+ performance.clearMarks();
+ entries = performance.getEntries();
+ assert.strictEqual(entries.length, 1);
+}
+
+{
+ performance.mark('A');
+ [undefined, null, 'foo', 'initialize', 1].forEach((i) => {
+ assert.doesNotThrow(() => performance.measure('test', i, 'A'));
+ });
+
+ [undefined, null, 'foo', 1].forEach((i) => {
+ assert.throws(() => performance.measure('test', 'A', i),
+ common.expectsError({
+ code: 'ERR_INVALID_PERFORMANCE_MARK',
+ type: Error,
+ message: `The "${i}" performance mark has not been set`
+ }));
+ });
+
+ performance.clearMeasures();
+ performance.clearMarks();
+
+ const entries = performance.getEntries();
+ assert.strictEqual(entries.length, 1);
+}
+
+{
+ performance.mark('A');
+ setImmediate(() => {
+ performance.mark('B');
+ performance.measure('foo', 'A', 'B');
+ const entry = performance.getEntriesByName('foo')[0];
+ const markA = performance.getEntriesByName('A', 'mark')[0];
+ const markB = performance.getEntriesByName('B', 'mark')[0];
+ assert.strictEqual(entry.name, 'foo');
+ assert.strictEqual(entry.entryType, 'measure');
+ assert.strictEqual(entry.startTime, markA.startTime);
+ assert.strictEqual(entry.duration.toPrecision(3),
+ (markB.startTime - markA.startTime).toPrecision(3));
+ });
+}
+
+assert.strictEqual(performance.nodeTiming.name, 'node');
+assert.strictEqual(performance.nodeTiming.entryType, 'node');
+
+[
+ 'startTime',
+ 'duration',
+ 'nodeStart',
+ 'v8Start',
+ 'bootstrapComplete',
+ 'environment',
+ 'loopStart',
+ 'loopExit',
+ 'thirdPartyMainStart',
+ 'thirdPartyMainEnd',
+ 'clusterSetupStart',
+ 'clusterSetupEnd',
+ 'moduleLoadStart',
+ 'moduleLoadEnd',
+ 'preloadModuleLoadStart',
+ 'preloadModuleLoadEnd'
+].forEach((i) => {
+ assert.strictEqual(typeof performance.nodeTiming[i], 'number');
+});
diff --git a/test/parallel/test-performanceobserver.js b/test/parallel/test-performanceobserver.js
new file mode 100644
index 00000000000..081362939ce
--- /dev/null
+++ b/test/parallel/test-performanceobserver.js
@@ -0,0 +1,139 @@
+'use strict';
+
+const common = require('../common');
+const Countdown = require('../common/countdown');
+const assert = require('assert');
+const {
+ observerCounts: counts
+} = process.binding('performance');
+const {
+ performance,
+ PerformanceObserver,
+ constants
+} = require('perf_hooks');
+
+const {
+ NODE_PERFORMANCE_ENTRY_TYPE_NODE,
+ NODE_PERFORMANCE_ENTRY_TYPE_MARK,
+ NODE_PERFORMANCE_ENTRY_TYPE_MEASURE,
+ NODE_PERFORMANCE_ENTRY_TYPE_GC,
+ NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION,
+} = constants;
+
+assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_NODE], 0);
+assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 1);
+assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MEASURE], 1);
+assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_GC], 0);
+assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION], 0);
+
+{
+ [1, null, undefined, {}, [], Infinity].forEach((i) => {
+ assert.throws(() => new PerformanceObserver(i),
+ common.expectsError({
+ code: 'ERR_INVALID_CALLBACK',
+ type: TypeError,
+ message: 'callback must be a function'
+ }));
+ });
+ const observer = new PerformanceObserver(common.mustNotCall());
+
+ [1, null, undefined].forEach((i) => {
+ //observer.observe(i);
+ assert.throws(() => observer.observe(i),
+ common.expectsError({
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message: 'The "options" argument must be of type Object'
+ }));
+ });
+
+ [1, undefined, null, {}, Infinity].forEach((i) => {
+ assert.throws(() => observer.observe({ entryTypes: i }),
+ common.expectsError({
+ code: 'ERR_INVALID_OPT_VALUE',
+ type: TypeError,
+ message: 'The value "[object Object]" is invalid for ' +
+ 'option "entryTypes"'
+ }));
+ });
+}
+
+// Test Non-Buffered
+{
+ const observer =
+ new PerformanceObserver(common.mustCall(callback, 3));
+
+ const countdown =
+ new Countdown(3, common.mustCall(() => {
+ observer.disconnect();
+ assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 1);
+ }));
+
+ function callback(list, obs) {
+ assert.strictEqual(obs, observer);
+ const entries = list.getEntries();
+ assert.strictEqual(entries.length, 1);
+ countdown.dec();
+ }
+ assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 1);
+ assert.doesNotThrow(() => observer.observe({ entryTypes: ['mark'] }));
+ assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 2);
+ performance.mark('test1');
+ performance.mark('test2');
+ performance.mark('test3');
+}
+
+
+// Test Buffered
+{
+ const observer =
+ new PerformanceObserver(common.mustCall(callback, 1));
+ assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 1);
+
+ function callback(list, obs) {
+ assert.strictEqual(obs, observer);
+ const entries = list.getEntries();
+ assert.strictEqual(entries.length, 3);
+ observer.disconnect();
+ assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 1);
+
+ {
+ const entriesByName = list.getEntriesByName('test1');
+ assert.strictEqual(entriesByName.length, 1);
+ assert.strictEqual(entriesByName[0].name, 'test1');
+ assert.strictEqual(entriesByName[0].entryType, 'mark');
+ }
+
+ {
+ const entriesByName = list.getEntriesByName('test1', 'mark');
+ assert.strictEqual(entriesByName.length, 1);
+ assert.strictEqual(entriesByName[0].name, 'test1');
+ assert.strictEqual(entriesByName[0].entryType, 'mark');
+ }
+
+ {
+ const entriesByName = list.getEntriesByName('test1', 'measure');
+ assert.strictEqual(entriesByName.length, 0);
+ }
+
+ {
+ const entriesByType = list.getEntriesByType('measure');
+ assert.strictEqual(entriesByType.length, 1);
+ assert.strictEqual(entriesByType[0].name, 'test3');
+ assert.strictEqual(entriesByType[0].entryType, 'measure');
+ }
+ }
+
+ assert.doesNotThrow(() => {
+ observer.observe({ entryTypes: ['mark', 'measure'], buffered: true });
+ });
+ // Do this twice to make sure it doesn't throw
+ assert.doesNotThrow(() => {
+ observer.observe({ entryTypes: ['mark', 'measure'], buffered: true });
+ });
+ // Even tho we called twice, count should be 1
+ assert.strictEqual(counts[NODE_PERFORMANCE_ENTRY_TYPE_MARK], 2);
+ performance.mark('test1');
+ performance.mark('test2');
+ performance.measure('test3', 'test1', 'test2');
+}