diff --git a/packages/opencensus-core/src/trace/model/span-base.ts b/packages/opencensus-core/src/trace/model/span-base.ts index bb72efccb..bd9a71ef2 100644 --- a/packages/opencensus-core/src/trace/model/span-base.ts +++ b/packages/opencensus-core/src/trace/model/span-base.ts @@ -65,6 +65,9 @@ export abstract class SpanBase implements types.Span { /** The number of dropped attributes. */ droppedAttributesCount = 0; + /** The number of dropped links. */ + droppedLinksCount = 0; + /** Constructs a new SpanBaseModel instance. */ constructor() { this.className = this.constructor.name; @@ -180,6 +183,11 @@ export abstract class SpanBase implements types.Span { addLink( traceId: string, spanId: string, type: string, attributes?: types.Attributes) { + if (this.links.length >= this.activeTraceParams.numberOfLinksPerSpan) { + this.links.shift(); + this.droppedLinksCount++; + } + this.links.push({ 'traceId': traceId, 'spanId': spanId, diff --git a/packages/opencensus-core/src/trace/model/types.ts b/packages/opencensus-core/src/trace/model/types.ts index d2d60d2f1..efb51b345 100644 --- a/packages/opencensus-core/src/trace/model/types.ts +++ b/packages/opencensus-core/src/trace/model/types.ts @@ -320,11 +320,14 @@ export interface Span { /** Gives the TraceContext of the span. */ readonly spanContext: SpanContext; + /** Trace Parameters */ + activeTraceParams: configTypes.TraceParams; + /** The number of dropped attributes. */ droppedAttributesCount: number; - /** Trace Parameters */ - activeTraceParams: configTypes.TraceParams; + /** The number of dropped links. */ + droppedLinksCount: number; /** * Adds an atribute to the span. diff --git a/packages/opencensus-core/test/test-root-span.ts b/packages/opencensus-core/test/test-root-span.ts index 9d5657110..fe2ff92d4 100644 --- a/packages/opencensus-core/test/test-root-span.ts +++ b/packages/opencensus-core/test/test-root-span.ts @@ -230,6 +230,7 @@ describe('RootSpan', () => { rootSpan.addLink(rootSpan.traceId, span.id, LINK_TYPE); assert.ok(rootSpan.links.length > 0); + assert.equal(rootSpan.droppedLinksCount, 0); assert.ok(instanceOfLink(rootSpan.links[0])); }); }); diff --git a/packages/opencensus-core/test/test-span.ts b/packages/opencensus-core/test/test-span.ts index 04162cfbd..868bca087 100644 --- a/packages/opencensus-core/test/test-span.ts +++ b/packages/opencensus-core/test/test-span.ts @@ -26,6 +26,10 @@ import {Annotation, Attributes, Link} from '../src/trace/model/types'; // rootspan const tracer = new CoreTracer(); +tracer.activeTraceParams = { + numberOfAttributesPerSpan: 32, + numberOfLinksPerSpan: 32 +}; describe('Span', () => { /** @@ -186,6 +190,20 @@ describe('Span', () => { span.attributes['testKey' + attType], 'testValue' + attType); }); }); + + it('should drop extra attributes', () => { + const rootSpan = new RootSpan(tracer); + rootSpan.start(); + + const span = new Span(rootSpan); + span.start(); + for (let i = 0; i < 40; i++) { + span.addAttribute('attr' + i, 100); + } + + assert.equal(Object.keys(span.attributes).length, 32); + assert.equal(span.droppedAttributesCount, 8); + }); }); /** @@ -232,8 +250,24 @@ describe('Span', () => { span.addLink(span.traceId, rootSpan.id, LINK_TYPE); assert.ok(span.links.length > 0); + assert.equal(span.droppedLinksCount, 0); assert.ok(instanceOfLink(span.links[0])); }); + + it('should drop extra links', () => { + const rootSpan = new RootSpan(tracer); + rootSpan.start(); + const span = new Span(rootSpan); + span.start(); + + const LINK_TYPE = 'PARENT_LINKED_SPAN'; + for (let i = 0; i < 35; i++) { + span.addLink(span.traceId, rootSpan.id, LINK_TYPE); + } + + assert.equal(span.links.length, 32); + assert.equal(span.droppedLinksCount, 3); + }); }); /** diff --git a/packages/opencensus-exporter-ocagent/src/adapters.ts b/packages/opencensus-exporter-ocagent/src/adapters.ts index f55b630f4..8dec96ac0 100644 --- a/packages/opencensus-exporter-ocagent/src/adapters.ts +++ b/packages/opencensus-exporter-ocagent/src/adapters.ts @@ -15,7 +15,6 @@ */ import {Annotation, Attributes, Link, MessageEvent, RootSpan, Span} from '@opencensus/core'; - import {google, opencensus} from './types'; /** @@ -246,10 +245,10 @@ const adaptLink = (link: Link): opencensus.proto.trace.v1.Span.Link => { * @param links Link[] * @returns opencensus.proto.trace.v1.Span.Links */ -const adaptLinks = - (links: Link[] = []): opencensus.proto.trace.v1.Span.Links => { - return {link: links.map(adaptLink), droppedLinksCount: null}; - }; +const adaptLinks = (links: Link[] = [], droppedLinksCount: number): + opencensus.proto.trace.v1.Span.Links => { + return {link: links.map(adaptLink), droppedLinksCount}; +}; /** * Adapts a boolean to a `google.protobuf.BoolValue` type. @@ -276,7 +275,7 @@ export const adaptSpan = (span: Span): opencensus.proto.trace.v1.Span => { attributes: adaptAttributes(span.attributes, span.droppedAttributesCount), stackTrace: null, // Unsupported by nodejs timeEvents: adaptTimeEvents(span.annotations, span.messageEvents), - links: adaptLinks(span.links), + links: adaptLinks(span.links, span.droppedLinksCount), status: span.status, sameProcessAsParentSpan: adaptBoolean(!span.remoteParent), childSpanCount: null, diff --git a/packages/opencensus-exporter-ocagent/test/test-ocagent.ts b/packages/opencensus-exporter-ocagent/test/test-ocagent.ts index b944d1a44..e3ccb4d3a 100644 --- a/packages/opencensus-exporter-ocagent/test/test-ocagent.ts +++ b/packages/opencensus-exporter-ocagent/test/test-ocagent.ts @@ -175,7 +175,7 @@ describe('OpenCensus Agent Exporter', () => { tracing = nodeTracing.start({ exporter: ocAgentExporter, samplingRate: INITIAL_SAMPLER_PROBABILITY, - traceParams: {numberOfAttributesPerSpan: 4} + traceParams: {numberOfAttributesPerSpan: 4, numberOfLinksPerSpan: 3} }); }); @@ -345,7 +345,7 @@ describe('OpenCensus Agent Exporter', () => { rootSpan.addAnnotation( 'my_annotation', {myString: 'bar', myNumber: 123, myBoolean: true}); - // Metric Event + // Message Event const timeStamp = 123456789; rootSpan.addMessageEvent('MessageEventTypeSent', 'ffff', timeStamp); rootSpan.addMessageEvent('MessageEventTypeRecv', 'ffff', timeStamp); @@ -354,6 +354,8 @@ describe('OpenCensus Agent Exporter', () => { rootSpan.addMessageEvent(null as any, 'ffff', timeStamp); // Links + rootSpan.addLink('aaaaa', 'aaa', 'CHILD_LINKED_SPAN'); + rootSpan.addLink('bbbbb', 'bbbbb', 'CHILD_LINKED_SPAN'); rootSpan.addLink('ffff', 'ffff', 'CHILD_LINKED_SPAN', { 'child_link_attribute_string': 'foo1', 'child_link_attribute_number': 123, @@ -476,7 +478,7 @@ describe('OpenCensus Agent Exporter', () => { // Links const buff = Buffer.from([255, 255]); assert.deepEqual(span.links, { - droppedLinksCount: 0, + droppedLinksCount: 2, link: [ { type: 'CHILD_LINKED_SPAN', @@ -517,4 +519,175 @@ describe('OpenCensus Agent Exporter', () => { rootSpan.end(); }); }); + + it('should adapt a span correctly without overflowing trace param limits', + (done) => { + const rootSpanOptions: TraceOptions = { + name: 'root', + kind: 'SERVER', + spanContext: { + traceId: hexId(), + spanId: hexId(), + traceState: 'foo=bar,baz=buzz', + options: 0x1 + } + }; + + tracing.tracer.startRootSpan(rootSpanOptions, (rootSpan: RootSpan) => { + // Status + rootSpan.setStatus(CanonicalCode.OK); + + // Attribute + rootSpan.addAttribute('my_first_attribute', 'foo'); + rootSpan.addAttribute('my_second_attribute', 'foo2'); + + // Annotation + rootSpan.addAnnotation( + 'my_annotation', + {myString: 'bar', myNumber: 123, myBoolean: true}); + + // Message Event + const timeStamp = 123456789; + rootSpan.addMessageEvent('MessageEventTypeSent', 'ffff', timeStamp); + rootSpan.addMessageEvent('MessageEventTypeRecv', 'ffff', timeStamp); + + // Links + rootSpan.addLink('ffff', 'ffff', 'CHILD_LINKED_SPAN', { + 'child_link_attribute_string': 'foo1', + 'child_link_attribute_number': 123, + 'child_link_attribute_boolean': true, + }); + rootSpan.addLink('ffff', 'ffff', 'PARENT_LINKED_SPAN'); + + server.on( + MockAgentEvent.ExportStreamMessageReceived, + (message: opencensus.proto.agent.trace.v1 + .ExportTraceServiceRequest) => { + assert.equal(message.spans.length, 1); + const span = message.spans[0]; + // Name / Context + if (!span.name) { + assert.fail('span.name is null or undefined'); + return; + } + assert.equal(span.name.value, 'root'); + assert.equal(span.kind, 'SERVER'); + + if (!span.tracestate) { + assert.fail('span.tracestate is null or undefined'); + return; + } + assert.deepEqual( + span.tracestate.entries, + [{key: 'foo', value: 'bar'}, {key: 'baz', value: 'buzz'}]); + + if (!span.status) { + assert.fail('span.status is null or undefined'); + } else { + assert.deepEqual(span.status, {code: 0, message: ''}); + } + + // Attributes + if (!span.attributes) { + assert.fail('span.attributes is null or undefined'); + return; + } + assert.deepEqual(span.attributes.attributeMap, { + my_first_attribute: { + value: 'stringValue', + stringValue: {value: 'foo', truncatedByteCount: 0} + }, + my_second_attribute: { + value: 'stringValue', + stringValue: {value: 'foo2', truncatedByteCount: 0} + } + }); + assert.equal(span.attributes.droppedAttributesCount, 0); + + // Time Events + assert.deepEqual(span.timeEvents, { + droppedAnnotationsCount: 0, + droppedMessageEventsCount: 0, + timeEvent: [ + { + value: 'annotation', + time: null, + annotation: { + description: + {value: 'my_annotation', truncatedByteCount: 0}, + attributes: { + attributeMap: { + myString: { + value: 'stringValue', + stringValue: + {value: 'bar', truncatedByteCount: 0} + }, + myNumber: {value: 'intValue', intValue: '123'}, + myBoolean: {value: 'boolValue', boolValue: true} + }, + droppedAttributesCount: 0 + } + } + }, + { + messageEvent: { + compressedSize: '0', + id: '65535', + type: 'SENT', + uncompressedSize: '0' + }, + time: {seconds: '123456', nanos: 789000000}, + value: 'messageEvent' + }, + { + value: 'messageEvent', + messageEvent: { + compressedSize: '0', + id: '65535', + type: 'RECEIVED', + uncompressedSize: '0' + }, + time: {seconds: '123456', nanos: 789000000}, + } + ] + }); + + // Links + const buff = Buffer.from([255, 255]); + assert.deepEqual(span.links, { + droppedLinksCount: 0, + link: [ + { + type: 'CHILD_LINKED_SPAN', + traceId: buff, + spanId: buff, + attributes: { + droppedAttributesCount: 0, + attributeMap: { + child_link_attribute_string: { + value: 'stringValue', + stringValue: {value: 'foo1', truncatedByteCount: 0} + }, + child_link_attribute_number: + {value: 'intValue', intValue: '123'}, + child_link_attribute_boolean: + {value: 'boolValue', boolValue: true} + } + } + }, + { + type: 'PARENT_LINKED_SPAN', + traceId: buff, + spanId: buff, + attributes: null + } + ] + }); + + done(); + }); + + rootSpan.end(); + }); + }); });