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

Commit 647a791

Browse files
committed
Add support for recording HTTP stats
1 parent a887a57 commit 647a791

File tree

5 files changed

+147
-41
lines changed

5 files changed

+147
-41
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
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-http/src/http-metrics.ts renamed to packages/opencensus-instrumentation-http/src/http-stats.ts

Lines changed: 13 additions & 11 deletions
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

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {BasePlugin, CanonicalCode, Func, HeaderGetter, HeaderSetter, MessageEventType, RootSpan, Span, SpanKind, TraceOptions} from '@opencensus/core';
17+
import {BasePlugin, CanonicalCode, Func, HeaderGetter, HeaderSetter, MessageEventType, Span, SpanKind, TagMap, TraceOptions} from '@opencensus/core';
1818
import * as httpModule 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

2526
export type HttpGetCallback = (res: httpModule.IncomingMessage) => void;
2627
export type HttpModule = typeof httpModule;
2728
export type RequestFunction = typeof httpModule.request;
2829

30+
// tslint:disable-next-line:no-any
31+
function isOpenCensusRequest(options: any) {
32+
return options && options.headers &&
33+
!!options.headers['x-opencensus-outgoing-request'];
34+
}
35+
2936
/** Http instrumentation plugin for Opencensus */
3037
export class HttpPlugin extends BasePlugin {
3138
/**
@@ -149,7 +156,6 @@ export class HttpPlugin extends BasePlugin {
149156
}
150157
}
151158

152-
153159
/**
154160
* Creates spans for incoming requests, restoring spans' context if applied.
155161
*/
@@ -164,10 +170,11 @@ export class HttpPlugin extends BasePlugin {
164170
if (event !== 'request') {
165171
return original.apply(this, arguments);
166172
}
167-
173+
const startTime = Date.now();
168174
const request: httpModule.IncomingMessage = args[0];
169175
const response: httpModule.ServerResponse = args[1];
170176
const path = request.url ? url.parse(request.url).pathname || '' : '';
177+
const method = request.method || 'GET';
171178
plugin.logger.debug('%s plugin incomingRequest', plugin.moduleName);
172179

173180
if (plugin.isIgnored(
@@ -209,6 +216,7 @@ export class HttpPlugin extends BasePlugin {
209216
const host = headers.host || 'localhost';
210217
const userAgent =
211218
(headers['user-agent'] || headers['User-Agent']) as string;
219+
const tags = new TagMap();
212220

213221
rootSpan.addAttribute(
214222
HttpPlugin.ATTRIBUTE_HTTP_HOST,
@@ -217,21 +225,18 @@ export class HttpPlugin extends BasePlugin {
217225
'$1',
218226
));
219227

220-
rootSpan.addAttribute(
221-
HttpPlugin.ATTRIBUTE_HTTP_METHOD, request.method || 'GET');
222-
228+
rootSpan.addAttribute(HttpPlugin.ATTRIBUTE_HTTP_METHOD, method);
223229
if (requestUrl) {
224230
rootSpan.addAttribute(
225231
HttpPlugin.ATTRIBUTE_HTTP_PATH, requestUrl.pathname || '');
226232
rootSpan.addAttribute(
227233
HttpPlugin.ATTRIBUTE_HTTP_ROUTE, requestUrl.path || '');
234+
tags.set(stats.HTTP_SERVER_ROUTE, {value: requestUrl.path || ''});
228235
}
229-
230236
if (userAgent) {
231237
rootSpan.addAttribute(
232238
HttpPlugin.ATTRIBUTE_HTTP_USER_AGENT, userAgent);
233239
}
234-
235240
rootSpan.addAttribute(
236241
HttpPlugin.ATTRIBUTE_HTTP_STATUS_CODE,
237242
response.statusCode.toString());
@@ -243,6 +248,12 @@ export class HttpPlugin extends BasePlugin {
243248
rootSpan.addMessageEvent(
244249
MessageEventType.RECEIVED, uuid.v4().split('-').join(''));
245250

251+
tags.set(stats.HTTP_SERVER_METHOD, {value: method});
252+
tags.set(
253+
stats.HTTP_SERVER_STATUS,
254+
{value: response.statusCode.toString()});
255+
HttpPlugin.recordStats(rootSpan.kind, tags, Date.now() - startTime);
256+
246257
rootSpan.end();
247258
return returned;
248259
};
@@ -266,7 +277,14 @@ export class HttpPlugin extends BasePlugin {
266277
options: httpModule.RequestOptions|string,
267278
callback): httpModule.ClientRequest {
268279
if (!options) {
269-
return original.apply(this, arguments);
280+
return original.apply(this, [options, callback]);
281+
}
282+
283+
// Do not trace ourselves
284+
if (isOpenCensusRequest(options)) {
285+
plugin.logger.debug(
286+
'header with "x-opencensus-outgoing-request" - do not trace');
287+
return original.apply(this, [options, callback]);
270288
}
271289

272290
// Makes sure the url is an url object
@@ -279,27 +297,19 @@ export class HttpPlugin extends BasePlugin {
279297
pathname = parsedUrl.pathname || '';
280298
origin = `${parsedUrl.protocol || 'http:'}//${parsedUrl.host}`;
281299
} else {
282-
// Do not trace ourselves
283-
if (options.headers &&
284-
options.headers['x-opencensus-outgoing-request']) {
285-
plugin.logger.debug(
286-
'header with "x-opencensus-outgoing-request" - do not trace');
287-
return original.apply(this, arguments);
288-
}
289-
290300
try {
291301
pathname = (options as url.URL).pathname;
292302
if (!pathname) {
293303
pathname = options.path ? url.parse(options.path).pathname : '';
294304
}
295305
method = options.method || 'GET';
296306
origin = `${options.protocol || 'http:'}//${options.host}`;
297-
} catch (e) {
307+
} catch (ignore) {
298308
}
299309
}
300310

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

304314
if (plugin.isIgnored(
305315
origin + pathname, request,
@@ -343,10 +353,10 @@ export class HttpPlugin extends BasePlugin {
343353
* @param options The arguments to the original function.
344354
*/
345355
private getMakeRequestTraceFunction(
346-
// tslint:disable-next-line:no-any
347356
request: httpModule.ClientRequest, options: httpModule.RequestOptions,
348357
plugin: HttpPlugin): Func<httpModule.ClientRequest> {
349358
return (span: Span): httpModule.ClientRequest => {
359+
const startTime = Date.now();
350360
plugin.logger.debug('makeRequestTrace');
351361

352362
if (!span) {
@@ -368,14 +378,16 @@ export class HttpPlugin extends BasePlugin {
368378
request.on('response', (response: httpModule.ClientResponse) => {
369379
plugin.tracer.wrapEmitter(response);
370380
plugin.logger.debug('outgoingRequest on response()');
371-
372381
response.on('end', () => {
373382
plugin.logger.debug('outgoingRequest on end()');
374383
const method = response.method ? response.method : 'GET';
375384
const headers = options.headers;
376385
const userAgent =
377386
headers ? (headers['user-agent'] || headers['User-Agent']) : null;
378387

388+
const tags = new TagMap();
389+
tags.set(stats.HTTP_CLIENT_METHOD, {value: method});
390+
379391
if (options.hostname) {
380392
span.addAttribute(HttpPlugin.ATTRIBUTE_HTTP_HOST, options.hostname);
381393
}
@@ -394,12 +406,16 @@ export class HttpPlugin extends BasePlugin {
394406
HttpPlugin.ATTRIBUTE_HTTP_STATUS_CODE,
395407
response.statusCode.toString());
396408
span.setStatus(HttpPlugin.parseResponseStatus(response.statusCode));
409+
tags.set(
410+
stats.HTTP_CLIENT_STATUS,
411+
{value: response.statusCode.toString()});
397412
}
398413

399414
// Message Event ID is not defined
400415
span.addMessageEvent(
401416
MessageEventType.SENT, uuid.v4().split('-').join(''));
402417

418+
HttpPlugin.recordStats(span.kind, tags, Date.now() - startTime);
403419
span.end();
404420
});
405421

@@ -457,6 +473,30 @@ export class HttpPlugin extends BasePlugin {
457473
}
458474
}
459475
}
476+
477+
/** Method to record stats for client and server. */
478+
static recordStats(kind: SpanKind, tags: TagMap, ms: number) {
479+
if (!plugin.stats) {
480+
return;
481+
}
482+
483+
try {
484+
const measureList = [];
485+
switch (kind) {
486+
case SpanKind.CLIENT:
487+
measureList.push(
488+
{measure: stats.HTTP_CLIENT_ROUNDTRIP_LATENCY, value: ms});
489+
break;
490+
case SpanKind.SERVER:
491+
measureList.push({measure: stats.HTTP_SERVER_LATENCY, value: ms});
492+
break;
493+
default:
494+
break;
495+
}
496+
plugin.stats.record(measureList, tags);
497+
} catch (ignore) {
498+
}
499+
}
460500
}
461501

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@
1515
*/
1616

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

0 commit comments

Comments
 (0)