Skip to content

Commit

Permalink
feat: warn more on potential memory leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
kjin committed Sep 25, 2018
1 parent d5fe71b commit 01c51ed
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 22 deletions.
51 changes: 30 additions & 21 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,6 @@ export type CLSMechanism =

/** Available configuration options. */
export interface Config {
/**
* The trace context propagation mechanism to use. The following options are
* available:
* - 'async-hooks' uses an implementation of CLS on top of the Node core
* `async_hooks` module in Node 8+. This option should not be used if the
* Node binary version requirements are not met.
* - 'async-listener' uses an implementation of CLS on top of the
* `continuation-local-storage` module.
* - 'auto' behaves like 'async-hooks' on Node 8+, and 'async-listener'
* otherwise.
* - 'none' disables CLS completely.
* - 'singular' allows one root span to exist at a time. This option is meant
* to be used internally by Google Cloud Functions, or in any other
* environment where it is guaranteed that only one request is being served
* at a time.
* The 'auto' mechanism is used by default if this configuration option is
* not explicitly set.
*/
clsMechanism?: CLSMechanism;

/**
* Log levels: 0=disabled, 1=error, 2=warn, 3=info, 4=debug
* The value of GCLOUD_TRACE_LOGLEVEL takes precedence over this value.
Expand All @@ -70,6 +50,34 @@ export interface Config {
*/
rootSpanNameOverride?: string|((name: string) => string);

/**
* The trace context propagation mechanism to use. The following options are
* available:
* - 'async-hooks' uses an implementation of CLS on top of the Node core
* `async_hooks` module in Node 8+. This option should not be used if the
* Node binary version requirements are not met.
* - 'async-listener' uses an implementation of CLS on top of the
* `continuation-local-storage` module.
* - 'auto' behaves like 'async-hooks' on Node 8+, and 'async-listener'
* otherwise.
* - 'none' disables CLS completely.
* - 'singular' allows one root span to exist at a time. This option is meant
* to be used internally by Google Cloud Functions, or in any other
* environment where it is guaranteed that only one request is being served
* at a time.
* The 'auto' mechanism is used by default if this configuration option is
* not explicitly set.
*/
clsMechanism?: CLSMechanism;

/**
* The number of local spans per trace to allow before emitting a warning.
* An excessive number of spans per trace may suggest a memory leak.
* This value should be 1-2x the estimated maximum number of RPCs made on
* behalf of a single incoming request.
*/
spansPerTraceSoftLimit?: number;

/**
* The maximum number of characters reported on a label value. This value
* cannot exceed 16383, the maximum value accepted by the service.
Expand Down Expand Up @@ -197,11 +205,12 @@ export interface Config {
* user-provided value will be used to extend the default value.
*/
export const defaultConfig = {
clsMechanism: 'auto' as CLSMechanism,
logLevel: 1,
enabled: true,
enhancedDatabaseReporting: false,
rootSpanNameOverride: (name: string) => name,
clsMechanism: 'auto' as CLSMechanism,
spansPerTraceSoftLimit: 25,
maximumLabelValueSize: 512,
plugins: {
// enable all by default
Expand Down
12 changes: 12 additions & 0 deletions src/trace-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface StackdriverTracerConfig extends
enhancedDatabaseReporting: boolean;
ignoreContextHeader: boolean;
rootSpanNameOverride: (path: string) => string;
spansPerTraceSoftLimit: number;
}

interface IncomingTraceContext {
Expand Down Expand Up @@ -241,6 +242,17 @@ export class StackdriverTracer implements Tracer {
rootSpan.span.name}] was already closed.`);
return UNCORRELATED_CHILD_SPAN;
}
if (rootSpan.trace.spans.length === this.config!.spansPerTraceSoftLimit) {
// As in the previous case, a root span with a large number of child
// spans suggests a memory leak stemming from context confusion. This
// is likely due to userspace task queues or Promise implementations.
this.logger!.warn(`TraceApi#createChildSpan: [${
this.pluginName}] Adding child span [${
options.name}] will cause the trace with root span [${
rootSpan.span.name}] to contain more than ${
this.config!
.spansPerTraceSoftLimit} spans. This is likely a memory leak.`);
}
// Create a new child span and return it.
const childContext = rootSpan.createChildSpan({
name: options.name,
Expand Down
3 changes: 2 additions & 1 deletion test/test-trace-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ describe('Trace Interface', () => {
enhancedDatabaseReporting: false,
ignoreContextHeader: false,
rootSpanNameOverride: (name: string) => name,
samplingRate: 0
samplingRate: 0,
spansPerTraceSoftLimit: 25
},
config),
logger);
Expand Down

0 comments on commit 01c51ed

Please sign in to comment.