diff --git a/packages/opencensus-web-core/src/trace/model/attribute-keys.ts b/packages/opencensus-web-core/src/trace/model/attribute-keys.ts index 85d5af5f..aef2b183 100644 --- a/packages/opencensus-web-core/src/trace/model/attribute-keys.ts +++ b/packages/opencensus-web-core/src/trace/model/attribute-keys.ts @@ -89,3 +89,8 @@ export const LONG_TASK_PREFIX = 'long_task.'; * long task. */ export const ATTRIBUTE_LONG_TASK_ATTRIBUTION = `${LONG_TASK_PREFIX}attribution`; + +/** + * Attribute for spans to be related back to the initial load trace. + */ +export const ATTRIBUTE_INITIAL_LOAD_TRACE_ID = 'initial_load_trace_id'; diff --git a/packages/opencensus-web-instrumentation-perf/src/initial-load-root-span.ts b/packages/opencensus-web-instrumentation-perf/src/initial-load-root-span.ts index be4021fd..ad2f74c0 100644 --- a/packages/opencensus-web-instrumentation-perf/src/initial-load-root-span.ts +++ b/packages/opencensus-web-instrumentation-perf/src/initial-load-root-span.ts @@ -20,6 +20,7 @@ import { ATTRIBUTE_HTTP_USER_AGENT, ATTRIBUTE_LONG_TASK_ATTRIBUTION, ATTRIBUTE_NAV_TYPE, + ATTRIBUTE_INITIAL_LOAD_TRACE_ID, parseUrl, randomSpanId, randomTraceId, @@ -92,6 +93,9 @@ export function getInitialLoadRootSpan( root.annotations = getNavigationAnnotations(perfEntries); root.attributes[ATTRIBUTE_HTTP_URL] = navigationUrl; root.attributes[ATTRIBUTE_HTTP_USER_AGENT] = navigator.userAgent; + // This is included to enable trace search by attribute to find an initial + // load trace and its interaction traces via a single attribute query. + root.attributes[ATTRIBUTE_INITIAL_LOAD_TRACE_ID] = root.traceId; if (navTiming) { root.endPerfTime = navTiming.loadEventEnd; diff --git a/packages/opencensus-web-instrumentation-perf/test/test-initial-load-root-span.ts b/packages/opencensus-web-instrumentation-perf/test/test-initial-load-root-span.ts index 2f527cb3..0273459b 100644 --- a/packages/opencensus-web-instrumentation-perf/test/test-initial-load-root-span.ts +++ b/packages/opencensus-web-instrumentation-perf/test/test-initial-load-root-span.ts @@ -128,6 +128,7 @@ const EXPECTED_ROOT_ATTRIBUTES: Attributes = { 'http.url': 'http://localhost:4200/', 'http.user_agent': USER_AGENT, 'nav.type': 'navigate', + initial_load_trace_id: '0000000000000000000000000000000b', }; const EXPECTED_ROOT_ANNOTATIONS: Annotation[] = [ { diff --git a/packages/opencensus-web-instrumentation-zone/src/on-page-interaction-stop-watch.ts b/packages/opencensus-web-instrumentation-zone/src/on-page-interaction-stop-watch.ts index 61fff91b..593623b2 100644 --- a/packages/opencensus-web-instrumentation-zone/src/on-page-interaction-stop-watch.ts +++ b/packages/opencensus-web-instrumentation-zone/src/on-page-interaction-stop-watch.ts @@ -19,7 +19,10 @@ import { ATTRIBUTE_HTTP_URL, ATTRIBUTE_HTTP_USER_AGENT, ATTRIBUTE_HTTP_PATH, + ATTRIBUTE_INITIAL_LOAD_TRACE_ID, + LinkType, } from '@opencensus/web-core'; +import { getInitialLoadSpanContext } from '@opencensus/web-initial-load'; /** A helper class for tracking on page interactions. */ export class OnPageInteractionStopwatch { @@ -44,7 +47,8 @@ export class OnPageInteractionStopwatch { } /** - * Stops the stopwatch, fills root span attributes and ends the span. + * Stops the stopwatch. Adds root span attributes and link to the initial + * load page, also, ends the span. * If has remaining tasks do not end the root span. */ stopAndRecord(): void { @@ -56,6 +60,18 @@ export class OnPageInteractionStopwatch { rootSpan.addAttribute(ATTRIBUTE_HTTP_URL, this.data.startLocationHref); rootSpan.addAttribute(ATTRIBUTE_HTTP_PATH, this.data.startLocationPath); rootSpan.addAttribute(ATTRIBUTE_HTTP_USER_AGENT, navigator.userAgent); + const initialLoadSpanContext = getInitialLoadSpanContext(); + // This is included to enable trace search by attribute to find an initial + // load trace and its interaction traces via a single attribute query. + rootSpan.addAttribute( + ATTRIBUTE_INITIAL_LOAD_TRACE_ID, + initialLoadSpanContext.traceId + ); + rootSpan.addLink( + initialLoadSpanContext.traceId, + initialLoadSpanContext.spanId, + LinkType.PARENT_LINKED_SPAN + ); rootSpan.end(); } } diff --git a/packages/opencensus-web-instrumentation-zone/test/test-interaction-tracker.ts b/packages/opencensus-web-instrumentation-zone/test/test-interaction-tracker.ts index 6345f06c..e6372846 100644 --- a/packages/opencensus-web-instrumentation-zone/test/test-interaction-tracker.ts +++ b/packages/opencensus-web-instrumentation-zone/test/test-interaction-tracker.ts @@ -21,6 +21,8 @@ import { ATTRIBUTE_HTTP_STATUS_CODE, ATTRIBUTE_HTTP_METHOD, WindowWithOcwGlobals, + LinkType, + Link, } from '@opencensus/web-core'; import { InteractionTracker, @@ -32,18 +34,27 @@ import { } from '../src/monkey-patching'; import { spanContextToTraceParent } from '@opencensus/web-propagation-tracecontext'; import { createFakePerfResourceEntry, spyPerfEntryByType } from './util'; -import { getInitialLoadSpanContext } from '@opencensus/web-initial-load'; describe('InteractionTracker', () => { doPatching(); InteractionTracker.startTracking(); let onEndSpanSpy: jasmine.Spy; const windowWithOcwGlobals = window as WindowWithOcwGlobals; - // Sample 100% of interactions for the testing. Necessary as the sampling - // decision is supposed to be done in the initial load page and the - // interaction tracker uses the same sampling decision. - windowWithOcwGlobals.ocSampleRate = 1.0; - getInitialLoadSpanContext(); + // Set the traceparent to fake the initial load Span Context. Also, Sample + // 100% of interactions for the testing. Necessary as this is supposed to be + // done by the initial load page. + const INITIAL_LOAD_TRACE_ID = '0af7651916cd43dd8448eb211c80319c'; + const INITIAL_LOAD_SPAN_ID = 'b7ad6b7169203331'; + windowWithOcwGlobals.traceparent = `00-${INITIAL_LOAD_TRACE_ID}-${INITIAL_LOAD_SPAN_ID}-01`; + + const EXPECTED_LINKS: Link[] = [ + { + traceId: INITIAL_LOAD_TRACE_ID, + spanId: INITIAL_LOAD_SPAN_ID, + type: LinkType.PARENT_LINKED_SPAN, + attributes: {}, + }, + ]; // Use Buffer time as we expect that these interactions take // a little extra time to complete due to the setTimeout that @@ -66,10 +77,14 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); // As there is another setTimeOut that completes the interaction, the - // span duraction is not precise, then only test if the interaction duration - // finishes within a range. + // span duraction is not precise, then only test if the interaction + // duration finishes within a range. expect(rootSpan.duration).toBeLessThan(TIME_BUFFER); done(); }); @@ -85,6 +100,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(SET_TIMEOUT_TIME); expect(rootSpan.duration).toBeLessThanOrEqual( @@ -105,6 +124,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeLessThanOrEqual(TIME_BUFFER); done(); @@ -125,6 +148,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(interactionTime); //The duration has to be less than set to the canceled timeout. @@ -152,6 +179,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(SET_TIMEOUT_TIME); expect(rootSpan.duration).toBeLessThanOrEqual( @@ -172,6 +203,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('button#test_element click'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(SET_TIMEOUT_TIME); expect(rootSpan.duration).toBeLessThanOrEqual( @@ -193,6 +228,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeLessThanOrEqual(TIME_BUFFER); done(); @@ -217,6 +256,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); // As this click is done at 'RESET_TRACING_ZONE_DELAY - 10' and this click has a // setTimeout, the minimum time taken by this click is the sum of these values. @@ -248,6 +291,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); // Test related to the first interaction if (onEndSpanSpy.calls.count() === 1) { @@ -288,6 +335,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('Navigation /test_navigation'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(SET_TIMEOUT_TIME); expect(rootSpan.duration).toBeLessThanOrEqual( @@ -311,6 +362,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('Test navigation'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(SET_TIMEOUT_TIME); expect(rootSpan.duration).toBeLessThanOrEqual( @@ -338,6 +393,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.spans.length).toBe(1); const childSpan = rootSpan.spans[0]; @@ -381,6 +440,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(XHR_TIME); expect(rootSpan.duration).toBeLessThanOrEqual(XHR_TIME + TIME_BUFFER); @@ -432,6 +495,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(XHR_TIME); expect(rootSpan.duration).toBeLessThanOrEqual(XHR_TIME + TIME_BUFFER); @@ -509,6 +576,10 @@ describe('InteractionTracker', () => { expect(rootSpan.name).toBe('test interaction'); expect(rootSpan.attributes['EventType']).toBe('click'); expect(rootSpan.attributes['TargetElement']).toBe(BUTTON_TAG_NAME); + expect(rootSpan.attributes['initial_load_trace_id']).toBe( + INITIAL_LOAD_TRACE_ID + ); + expect(rootSpan.links).toEqual(EXPECTED_LINKS); expect(rootSpan.ended).toBeTruthy(); expect(rootSpan.duration).toBeGreaterThanOrEqual(interactionTime); expect(rootSpan.duration).toBeLessThanOrEqual( diff --git a/packages/opencensus-web-instrumentation-zone/test/test-on-page-interaction-stop-watch.ts b/packages/opencensus-web-instrumentation-zone/test/test-on-page-interaction-stop-watch.ts index 4310d7b6..6e825da8 100644 --- a/packages/opencensus-web-instrumentation-zone/test/test-on-page-interaction-stop-watch.ts +++ b/packages/opencensus-web-instrumentation-zone/test/test-on-page-interaction-stop-watch.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { RootSpan, Tracer } from '@opencensus/web-core'; +import { + RootSpan, + Tracer, + WindowWithOcwGlobals, + LinkType, +} from '@opencensus/web-core'; import { OnPageInteractionStopwatch } from '../src/on-page-interaction-stop-watch'; describe('OnPageInteractionStopWatch', () => { @@ -21,6 +26,7 @@ describe('OnPageInteractionStopWatch', () => { let tracer: Tracer; let interaction: OnPageInteractionStopwatch; let target: HTMLElement; + const windowWithOcwGlobals = window as WindowWithOcwGlobals; describe('Tasks tracking', () => { beforeEach(() => { @@ -53,6 +59,9 @@ describe('OnPageInteractionStopWatch', () => { }); describe('stopAndRecord()', () => { + const traceId = '0af7651916cd43dd8448eb211c80319c'; + const spanId = 'b7ad6b7169203331'; + windowWithOcwGlobals.traceparent = `00-${traceId}-${spanId}-01`; beforeEach(() => { tracer = Tracer.instance; root = new RootSpan(tracer, { name: 'root1' }); @@ -72,6 +81,15 @@ describe('OnPageInteractionStopWatch', () => { expect(root.attributes['EventType']).toBe('click'); expect(root.attributes['TargetElement']).toBe(target.tagName); + expect(root.attributes['initial_load_trace_id']).toBe(traceId); + expect(root.links).toEqual([ + { + traceId, + spanId, + type: LinkType.PARENT_LINKED_SPAN, + attributes: {}, + }, + ]); expect(tracer.onEndSpan).toHaveBeenCalledWith(root); }); it('Should not finish the interaction when there are remaining tasks', () => {