Skip to content

Commit 9975311

Browse files
vmarchaudtargos
authored andcommitted
perf_hooks: add HttpRequest statistics monitoring #28445
```js const { PerformanceObserver, performance } = require('perf_hooks'); const http = require('http'); const obs = new PerformanceObserver((items) => { const entry = items.getEntries()[0]; console.log(entry.name, entry.duration); }); obs.observe({ entryTypes: ['http'] }); const server = http.Server(function(req, res) { server.close(); res.writeHead(200); res.end('hello world\n'); }); server.listen(0, function() { const req = http.request({ port: this.address().port, path: '/', method: 'POST' }).end(); }); ``` PR-URL: #28486 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent 0aca527 commit 9975311

File tree

7 files changed

+119
-5
lines changed

7 files changed

+119
-5
lines changed

doc/api/perf_hooks.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ added: v8.5.0
183183
* {string}
184184

185185
The type of the performance entry. Currently it may be one of: `'node'`,
186-
`'mark'`, `'measure'`, `'gc'`, `'function'`, or `'http2'`.
186+
`'mark'`, `'measure'`, `'gc'`, `'function'`, `'http2'` or `'http'`.
187187

188188
### performanceEntry.kind
189189
<!-- YAML

lib/_http_server.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ const {
3939
prepareError,
4040
} = require('_http_common');
4141
const { OutgoingMessage } = require('_http_outgoing');
42-
const { outHeadersKey, ondrain, nowDate } = require('internal/http');
42+
const {
43+
outHeadersKey,
44+
ondrain,
45+
nowDate,
46+
emitStatistics
47+
} = require('internal/http');
4348
const {
4449
defaultTriggerAsyncIdScope,
4550
getOrSetAsyncId
@@ -57,8 +62,11 @@ const {
5762
DTRACE_HTTP_SERVER_RESPONSE
5863
} = require('internal/dtrace');
5964
const { getOptionValue } = require('internal/options');
65+
const { observerCounts, constants } = internalBinding('performance');
66+
const { NODE_PERFORMANCE_ENTRY_TYPE_HTTP } = constants;
6067

6168
const kServerResponse = Symbol('ServerResponse');
69+
const kServerResponseStatistics = Symbol('ServerResponseStatistics');
6270
const kDefaultHttpServerTimeout =
6371
getOptionValue('--http-server-default-timeout');
6472

@@ -150,12 +158,22 @@ function ServerResponse(req) {
150158
this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te);
151159
this.shouldKeepAlive = false;
152160
}
161+
162+
const httpObserverCount = observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_HTTP];
163+
if (httpObserverCount > 0) {
164+
this[kServerResponseStatistics] = {
165+
startTime: process.hrtime()
166+
};
167+
}
153168
}
154169
Object.setPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype);
155170
Object.setPrototypeOf(ServerResponse, OutgoingMessage);
156171

157172
ServerResponse.prototype._finish = function _finish() {
158173
DTRACE_HTTP_SERVER_RESPONSE(this.connection);
174+
if (this[kServerResponseStatistics] !== undefined) {
175+
emitStatistics(this[kServerResponseStatistics]);
176+
}
159177
OutgoingMessage.prototype._finish.call(this);
160178
};
161179

lib/internal/http.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const { setUnrefTimeout } = require('internal/timers');
4+
const { PerformanceEntry, notify } = internalBinding('performance');
45

56
var nowCache;
67
var utcCache;
@@ -31,9 +32,26 @@ function ondrain() {
3132
if (this._httpMessage) this._httpMessage.emit('drain');
3233
}
3334

35+
class HttpRequestTiming extends PerformanceEntry {
36+
constructor(statistics) {
37+
super();
38+
this.name = 'HttpRequest';
39+
this.entryType = 'http';
40+
const startTime = statistics.startTime;
41+
const diff = process.hrtime(startTime);
42+
this.duration = diff[0] * 1000 + diff[1] / 1e6;
43+
this.startTime = startTime[0] * 1000 + startTime[1] / 1e6;
44+
}
45+
}
46+
47+
function emitStatistics(statistics) {
48+
notify('http', new HttpRequestTiming(statistics));
49+
}
50+
3451
module.exports = {
3552
outHeadersKey: Symbol('outHeadersKey'),
3653
ondrain,
3754
nowDate,
38-
utcDate
55+
utcDate,
56+
emitStatistics
3957
};

lib/perf_hooks.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
NODE_PERFORMANCE_ENTRY_TYPE_GC,
2626
NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION,
2727
NODE_PERFORMANCE_ENTRY_TYPE_HTTP2,
28+
NODE_PERFORMANCE_ENTRY_TYPE_HTTP,
2829

2930
NODE_PERFORMANCE_MILESTONE_NODE_START,
3031
NODE_PERFORMANCE_MILESTONE_V8_START,
@@ -70,7 +71,8 @@ const observerableTypes = [
7071
'measure',
7172
'gc',
7273
'function',
73-
'http2'
74+
'http2',
75+
'http'
7476
];
7577

7678
const IDX_STREAM_STATS_ID = 0;
@@ -508,6 +510,7 @@ function mapTypes(i) {
508510
case 'gc': return NODE_PERFORMANCE_ENTRY_TYPE_GC;
509511
case 'function': return NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION;
510512
case 'http2': return NODE_PERFORMANCE_ENTRY_TYPE_HTTP2;
513+
case 'http': return NODE_PERFORMANCE_ENTRY_TYPE_HTTP;
511514
}
512515
}
513516

src/node_perf.cc

+16
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,21 @@ void Timerify(const FunctionCallbackInfo<Value>& args) {
366366
args.GetReturnValue().Set(wrap);
367367
}
368368

369+
// Notify a custom PerformanceEntry to observers
370+
void Notify(const FunctionCallbackInfo<Value>& args) {
371+
Environment* env = Environment::GetCurrent(args);
372+
Utf8Value type(env->isolate(), args[0]);
373+
Local<Value> entry = args[1];
374+
PerformanceEntryType entry_type = ToPerformanceEntryTypeEnum(*type);
375+
AliasedUint32Array& observers = env->performance_state()->observers;
376+
if (entry_type != NODE_PERFORMANCE_ENTRY_TYPE_INVALID &&
377+
observers[entry_type]) {
378+
USE(env->performance_entry_callback()->
379+
Call(env->context(), Undefined(env->isolate()), 1, &entry));
380+
}
381+
}
382+
383+
369384
// Event Loop Timing Histogram
370385
namespace {
371386
static void ELDHistogramMin(const FunctionCallbackInfo<Value>& args) {
@@ -562,6 +577,7 @@ void Initialize(Local<Object> target,
562577
env->SetMethod(target, "timerify", Timerify);
563578
env->SetMethod(
564579
target, "setupGarbageCollectionTracking", SetupGarbageCollectionTracking);
580+
env->SetMethod(target, "notify", Notify);
565581

566582
Local<Object> constants = Object::New(isolate);
567583

src/node_perf_common.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ extern uint64_t performance_v8_start;
3535
V(MEASURE, "measure") \
3636
V(GC, "gc") \
3737
V(FUNCTION, "function") \
38-
V(HTTP2, "http2")
38+
V(HTTP2, "http2") \
39+
V(HTTP, "http")
3940

4041
enum PerformanceMilestone {
4142
#define V(name, _) NODE_PERFORMANCE_MILESTONE_##name,

test/parallel/test-http-perf_hooks.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
const { PerformanceObserver } = require('perf_hooks');
8+
9+
const obs = new PerformanceObserver(common.mustCall((items) => {
10+
const entry = items.getEntries()[0];
11+
assert.strictEqual(entry.entryType, 'http');
12+
assert.strictEqual(typeof entry.startTime, 'number');
13+
assert.strictEqual(typeof entry.duration, 'number');
14+
}, 2));
15+
16+
obs.observe({ entryTypes: ['http'] });
17+
18+
const expected = 'Post Body For Test';
19+
const makeRequest = (options) => {
20+
return new Promise((resolve, reject) => {
21+
http.request(options, common.mustCall((res) => {
22+
resolve();
23+
})).on('error', reject).end(options.data);
24+
});
25+
};
26+
27+
const server = http.Server(common.mustCall((req, res) => {
28+
let result = '';
29+
30+
req.setEncoding('utf8');
31+
req.on('data', function(chunk) {
32+
result += chunk;
33+
});
34+
35+
req.on('end', common.mustCall(function() {
36+
assert.strictEqual(result, expected);
37+
res.writeHead(200);
38+
res.end('hello world\n');
39+
}));
40+
}, 2));
41+
42+
server.listen(0, common.mustCall(async () => {
43+
await Promise.all([
44+
makeRequest({
45+
port: server.address().port,
46+
path: '/',
47+
method: 'POST',
48+
data: expected
49+
}),
50+
makeRequest({
51+
port: server.address().port,
52+
path: '/',
53+
method: 'POST',
54+
data: expected
55+
})
56+
]);
57+
server.close();
58+
}));

0 commit comments

Comments
 (0)