Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit a5c2971

Browse files
authored
Add support for recording HTTP stats (#370)
* Add support for recording HTTP stats * fix review comment * enable noUnusedLocals
1 parent 1bb958a commit a5c2971

File tree

7 files changed

+157
-71
lines changed

7 files changed

+157
-71
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ All notable changes to this project will be documented in this file.
88
- Add support for ```tags```, ```status``` and ```annotation``` in Zipkin exporter.
99
- Add support for Binary propagation format.
1010
- Add support for object(```SpanOptions```) as an argument for ```startChildSpan``` function, similar to ```startRootSpan```.
11-
- Add proto files to exporter-ocagent package. Fixes issue [#174](https://github.com/census-instrumentation/opencensus-node/issues/174).
11+
- Add proto files to exporter-ocagent package. Fixes issue [#174](https://github.com/census-instrumentation/opencensus-node/issues/174).
1212
- Remove `ConfigStream` behavior from exporter-ocagent. This was unstable and is not currently supported by any other language instrumentation.
1313
- Change default exporter-ocagent port to `55678` to match the default OC Agent port.
14+
- Add support for recording HTTP stats.
1415

1516
## 0.0.9 - 2019-02-12
1617
- Add Metrics API.

packages/opencensus-instrumentation-grpc/src/index.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,4 @@
1515
*/
1616

1717
export * from './grpc';
18-
19-
import {registerAllGrpcViews, registerClientGrpcBasicViews, registerServerGrpcBasicViews} from './grpc-stats/stats-common';
20-
21-
export {
22-
registerAllGrpcViews,
23-
registerClientGrpcBasicViews,
24-
registerServerGrpcBasicViews
25-
};
18+
export {registerAllGrpcViews, registerClientGrpcBasicViews, registerServerGrpcBasicViews} from './grpc-stats/stats-common';

packages/opencensus-instrumentation-http/src/http-metrics.ts renamed to packages/opencensus-instrumentation-http/src/http-stats.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {AggregationType, globalStats, MeasureUnit, Stats, View} from '@opencensus/core';
17+
import {AggregationType, globalStats, Measure, MeasureUnit, Stats, View} from '@opencensus/core';
1818

1919
/**
2020
* {@link Measure} for the client-side total bytes sent in request body (not
2121
* including headers). This is uncompressed bytes.
2222
*/
23-
export const HTTP_CLIENT_SENT_BYTES = globalStats.createMeasureInt64(
23+
export const HTTP_CLIENT_SENT_BYTES: Measure = globalStats.createMeasureInt64(
2424
'opencensus.io/http/client/sent_bytes', MeasureUnit.BYTE,
2525
'Client-side total bytes sent in request body (uncompressed)');
2626

@@ -31,25 +31,27 @@ export const HTTP_CLIENT_SENT_BYTES = globalStats.createMeasureInt64(
3131
* the Content-Length header. This is uncompressed bytes. Responses with no
3232
* body should record 0 for this value.
3333
*/
34-
export const HTTP_CLIENT_RECEIVED_BYTES = globalStats.createMeasureInt64(
35-
'opencensus.io/http/client/received_bytes', MeasureUnit.BYTE,
36-
'Client-side total bytes received in response bodies (uncompressed)');
34+
export const HTTP_CLIENT_RECEIVED_BYTES: Measure =
35+
globalStats.createMeasureInt64(
36+
'opencensus.io/http/client/received_bytes', MeasureUnit.BYTE,
37+
'Client-side total bytes received in response bodies (uncompressed)');
3738

3839
/**
3940
* {@link Measure} for the client-side time between first byte of request
4041
* headers sent to last byte of response received, or terminal error.
4142
*/
42-
export const HTTP_CLIENT_ROUNDTRIP_LATENCY = globalStats.createMeasureDouble(
43+
export const HTTP_CLIENT_ROUNDTRIP_LATENCY: Measure = globalStats.createMeasureDouble(
4344
'opencensus.io/http/client/roundtrip_latency', MeasureUnit.MS,
4445
'Client-side time between first byte of request headers sent to last byte of response received, or terminal error');
4546

4647
/**
4748
* {@link Measure} for the server-side total bytes received in request body
4849
* (not including headers). This is uncompressed bytes.
4950
*/
50-
export const HTTP_SERVER_RECEIVED_BYTES = globalStats.createMeasureInt64(
51-
'opencensus.io/http/server/received_bytes', MeasureUnit.BYTE,
52-
'Server-side total bytes received in request body (uncompressed)');
51+
export const HTTP_SERVER_RECEIVED_BYTES: Measure =
52+
globalStats.createMeasureInt64(
53+
'opencensus.io/http/server/received_bytes', MeasureUnit.BYTE,
54+
'Server-side total bytes received in request body (uncompressed)');
5355

5456
/**
5557
* {@link Measure} for the server-side total bytes sent in response bodies
@@ -58,15 +60,15 @@ export const HTTP_SERVER_RECEIVED_BYTES = globalStats.createMeasureInt64(
5860
* Content-Length header. This is uncompressed bytes. Responses with no
5961
* body should record 0 for this value.
6062
*/
61-
export const HTTP_SERVER_SENT_BYTES = globalStats.createMeasureInt64(
63+
export const HTTP_SERVER_SENT_BYTES: Measure = globalStats.createMeasureInt64(
6264
'opencensus.io/http/server/sent_bytes', MeasureUnit.BYTE,
6365
'Server-side total bytes sent in response bodies (uncompressed)');
6466

6567
/**
6668
* {@link Measure} for the server-side time between first byte of request
6769
* headers received to last byte of response sent, or terminal error.
6870
*/
69-
export const HTTP_SERVER_LATENCY = globalStats.createMeasureDouble(
71+
export const HTTP_SERVER_LATENCY: Measure = globalStats.createMeasureDouble(
7072
'opencensus.io/http/server/server_latency', MeasureUnit.MS,
7173
'Server-side time between first byte of request headers received to last byte of response sent, or terminal error');
7274

packages/opencensus-instrumentation-http/src/http.ts

+72-43
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {BasePlugin, CanonicalCode, Func, HeaderGetter, HeaderSetter, MessageEventType, RootSpan, Span, SpanKind, TraceOptions} from '@opencensus/core';
18-
import * as httpModule from 'http';
17+
import {BasePlugin, CanonicalCode, Func, HeaderGetter, HeaderSetter, MessageEventType, Span, SpanKind, TagMap, TraceOptions} from '@opencensus/core';
18+
import {ClientRequest, ClientResponse, IncomingMessage, request, RequestOptions, ServerResponse} from 'http';
1919
import * as semver from 'semver';
2020
import * as shimmer from 'shimmer';
2121
import * as url from 'url';
2222
import * as uuid from 'uuid';
23-
import {HttpPluginConfig, IgnoreMatcher} from './types';
23+
import * as stats from './http-stats';
24+
import {IgnoreMatcher} from './types';
2425

25-
export type HttpGetCallback = (res: httpModule.IncomingMessage) => void;
26-
export type HttpModule = typeof httpModule;
27-
export type RequestFunction = typeof httpModule.request;
26+
export type HttpGetCallback = (res: IncomingMessage) => void;
27+
export type RequestFunction = typeof request;
28+
29+
function isOpenCensusRequest(options: RequestOptions) {
30+
return options && options.headers &&
31+
!!options.headers['x-opencensus-outgoing-request'];
32+
}
2833

2934
/** Http instrumentation plugin for Opencensus */
3035
export class HttpPlugin extends BasePlugin {
@@ -47,10 +52,7 @@ export class HttpPlugin extends BasePlugin {
4752
super(moduleName);
4853
}
4954

50-
51-
/**
52-
* Patches HTTP incoming and outcoming request functions.
53-
*/
55+
/** Patches HTTP incoming and outcoming request functions. */
5456
protected applyPatch() {
5557
this.logger.debug('applying patch to %s@%s', this.moduleName, this.version);
5658

@@ -72,9 +74,8 @@ export class HttpPlugin extends BasePlugin {
7274
// https://nodejs.org/dist/latest/docs/api/http.html#http_http_get_options_callback
7375
// https://github.com/googleapis/cloud-trace-nodejs/blob/master/src/plugins/plugin-http.ts#L198
7476
return function getTrace(
75-
options: httpModule.RequestOptions|string,
76-
callback: HttpGetCallback) {
77-
const req = httpModule.request(options, callback);
77+
options: RequestOptions|string, callback: HttpGetCallback) {
78+
const req = request(options, callback);
7879
req.end();
7980
return req;
8081
};
@@ -95,7 +96,6 @@ export class HttpPlugin extends BasePlugin {
9596
return this.moduleExports;
9697
}
9798

98-
9999
/** Unpatches all HTTP patched function. */
100100
protected applyUnpatch(): void {
101101
shimmer.unwrap(this.moduleExports, 'request');
@@ -149,7 +149,6 @@ export class HttpPlugin extends BasePlugin {
149149
}
150150
}
151151

152-
153152
/**
154153
* Creates spans for incoming requests, restoring spans' context if applied.
155154
*/
@@ -164,10 +163,11 @@ export class HttpPlugin extends BasePlugin {
164163
if (event !== 'request') {
165164
return original.apply(this, arguments);
166165
}
167-
168-
const request: httpModule.IncomingMessage = args[0];
169-
const response: httpModule.ServerResponse = args[1];
166+
const startTime = Date.now();
167+
const request: IncomingMessage = args[0];
168+
const response: ServerResponse = args[1];
170169
const path = request.url ? url.parse(request.url).pathname || '' : '';
170+
const method = request.method || 'GET';
171171
plugin.logger.debug('%s plugin incomingRequest', plugin.moduleName);
172172

173173
if (plugin.isIgnored(
@@ -201,14 +201,15 @@ export class HttpPlugin extends BasePlugin {
201201
// https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/master/src/plugins/plugin-connect.ts#L75)
202202
const originalEnd = response.end;
203203

204-
response.end = function(this: httpModule.ServerResponse) {
204+
response.end = function(this: ServerResponse) {
205205
response.end = originalEnd;
206206
const returned = response.end.apply(this, arguments);
207207

208208
const requestUrl = request.url ? url.parse(request.url) : null;
209209
const host = headers.host || 'localhost';
210210
const userAgent =
211211
(headers['user-agent'] || headers['User-Agent']) as string;
212+
const tags = new TagMap();
212213

213214
rootSpan.addAttribute(
214215
HttpPlugin.ATTRIBUTE_HTTP_HOST,
@@ -217,21 +218,18 @@ export class HttpPlugin extends BasePlugin {
217218
'$1',
218219
));
219220

220-
rootSpan.addAttribute(
221-
HttpPlugin.ATTRIBUTE_HTTP_METHOD, request.method || 'GET');
222-
221+
rootSpan.addAttribute(HttpPlugin.ATTRIBUTE_HTTP_METHOD, method);
223222
if (requestUrl) {
224223
rootSpan.addAttribute(
225224
HttpPlugin.ATTRIBUTE_HTTP_PATH, requestUrl.pathname || '');
226225
rootSpan.addAttribute(
227226
HttpPlugin.ATTRIBUTE_HTTP_ROUTE, requestUrl.path || '');
227+
tags.set(stats.HTTP_SERVER_ROUTE, {value: requestUrl.path || ''});
228228
}
229-
230229
if (userAgent) {
231230
rootSpan.addAttribute(
232231
HttpPlugin.ATTRIBUTE_HTTP_USER_AGENT, userAgent);
233232
}
234-
235233
rootSpan.addAttribute(
236234
HttpPlugin.ATTRIBUTE_HTTP_STATUS_CODE,
237235
response.statusCode.toString());
@@ -243,6 +241,12 @@ export class HttpPlugin extends BasePlugin {
243241
rootSpan.addMessageEvent(
244242
MessageEventType.RECEIVED, uuid.v4().split('-').join(''));
245243

244+
tags.set(stats.HTTP_SERVER_METHOD, {value: method});
245+
tags.set(
246+
stats.HTTP_SERVER_STATUS,
247+
{value: response.statusCode.toString()});
248+
HttpPlugin.recordStats(rootSpan.kind, tags, Date.now() - startTime);
249+
246250
rootSpan.end();
247251
return returned;
248252
};
@@ -253,20 +257,17 @@ export class HttpPlugin extends BasePlugin {
253257
};
254258
}
255259

256-
257260
/**
258261
* Creates spans for outgoing requests, sending spans' context for distributed
259262
* tracing.
260263
*/
261264
protected getPatchOutgoingRequestFunction() {
262-
return (original: Func<httpModule.ClientRequest>): Func<
263-
httpModule.ClientRequest> => {
265+
return (original: Func<ClientRequest>): Func<ClientRequest> => {
264266
const plugin = this;
265267
return function outgoingRequest(
266-
options: httpModule.RequestOptions|string,
267-
callback): httpModule.ClientRequest {
268+
options: RequestOptions|string, callback): ClientRequest {
268269
if (!options) {
269-
return original.apply(this, arguments);
270+
return original.apply(this, [options, callback]);
270271
}
271272

272273
// Makes sure the url is an url object
@@ -280,11 +281,10 @@ export class HttpPlugin extends BasePlugin {
280281
origin = `${parsedUrl.protocol || 'http:'}//${parsedUrl.host}`;
281282
} else {
282283
// Do not trace ourselves
283-
if (options.headers &&
284-
options.headers['x-opencensus-outgoing-request']) {
284+
if (isOpenCensusRequest(options)) {
285285
plugin.logger.debug(
286286
'header with "x-opencensus-outgoing-request" - do not trace');
287-
return original.apply(this, arguments);
287+
return original.apply(this, [options, callback]);
288288
}
289289

290290
try {
@@ -294,12 +294,12 @@ export class HttpPlugin extends BasePlugin {
294294
}
295295
method = options.method || 'GET';
296296
origin = `${options.protocol || 'http:'}//${options.host}`;
297-
} catch (e) {
297+
} catch (ignore) {
298298
}
299299
}
300300

301-
const request: httpModule.ClientRequest =
302-
original.apply(this, arguments);
301+
const request: ClientRequest =
302+
original.apply(this, [options, callback]);
303303

304304
if (plugin.isIgnored(
305305
origin + pathname, request,
@@ -315,7 +315,6 @@ export class HttpPlugin extends BasePlugin {
315315
kind: SpanKind.CLIENT,
316316
};
317317

318-
319318
// Checks if this outgoing request is part of an operation by checking
320319
// if there is a current root span, if so, we create a child span. In
321320
// case there is no root span, this means that the outgoing request is
@@ -343,10 +342,10 @@ export class HttpPlugin extends BasePlugin {
343342
* @param options The arguments to the original function.
344343
*/
345344
private getMakeRequestTraceFunction(
346-
// tslint:disable-next-line:no-any
347-
request: httpModule.ClientRequest, options: httpModule.RequestOptions,
348-
plugin: HttpPlugin): Func<httpModule.ClientRequest> {
349-
return (span: Span): httpModule.ClientRequest => {
345+
request: ClientRequest, options: RequestOptions,
346+
plugin: HttpPlugin): Func<ClientRequest> {
347+
return (span: Span): ClientRequest => {
348+
const startTime = Date.now();
350349
plugin.logger.debug('makeRequestTrace');
351350

352351
if (!span) {
@@ -365,17 +364,19 @@ export class HttpPlugin extends BasePlugin {
365364
propagation.inject(setter, span.spanContext);
366365
}
367366

368-
request.on('response', (response: httpModule.ClientResponse) => {
367+
request.on('response', (response: ClientResponse) => {
369368
plugin.tracer.wrapEmitter(response);
370369
plugin.logger.debug('outgoingRequest on response()');
371-
372370
response.on('end', () => {
373371
plugin.logger.debug('outgoingRequest on end()');
374372
const method = response.method ? response.method : 'GET';
375373
const headers = options.headers;
376374
const userAgent =
377375
headers ? (headers['user-agent'] || headers['User-Agent']) : null;
378376

377+
const tags = new TagMap();
378+
tags.set(stats.HTTP_CLIENT_METHOD, {value: method});
379+
379380
if (options.hostname) {
380381
span.addAttribute(HttpPlugin.ATTRIBUTE_HTTP_HOST, options.hostname);
381382
}
@@ -394,12 +395,16 @@ export class HttpPlugin extends BasePlugin {
394395
HttpPlugin.ATTRIBUTE_HTTP_STATUS_CODE,
395396
response.statusCode.toString());
396397
span.setStatus(HttpPlugin.parseResponseStatus(response.statusCode));
398+
tags.set(
399+
stats.HTTP_CLIENT_STATUS,
400+
{value: response.statusCode.toString()});
397401
}
398402

399403
// Message Event ID is not defined
400404
span.addMessageEvent(
401405
MessageEventType.SENT, uuid.v4().split('-').join(''));
402406

407+
HttpPlugin.recordStats(span.kind, tags, Date.now() - startTime);
403408
span.end();
404409
});
405410

@@ -457,6 +462,30 @@ export class HttpPlugin extends BasePlugin {
457462
}
458463
}
459464
}
465+
466+
/** Method to record stats for client and server. */
467+
static recordStats(kind: SpanKind, tags: TagMap, ms: number) {
468+
if (!plugin.stats) {
469+
return;
470+
}
471+
472+
try {
473+
const measureList = [];
474+
switch (kind) {
475+
case SpanKind.CLIENT:
476+
measureList.push(
477+
{measure: stats.HTTP_CLIENT_ROUNDTRIP_LATENCY, value: ms});
478+
break;
479+
case SpanKind.SERVER:
480+
measureList.push({measure: stats.HTTP_SERVER_LATENCY, value: ms});
481+
break;
482+
default:
483+
break;
484+
}
485+
plugin.stats.record(measureList, tags);
486+
} catch (ignore) {
487+
}
488+
}
460489
}
461490

462491
const plugin = new HttpPlugin('http');

packages/opencensus-instrumentation-http/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
*/
1616

1717
export * from './http';
18+
export {registerAllClientViews, registerAllServerViews, registerAllViews} from './http-stats';

0 commit comments

Comments
 (0)