Skip to content

Commit

Permalink
feat(rum-core): capture XHR/Fetch spans using resource timing (elasti…
Browse files Browse the repository at this point in the history
…c#825)

* feat(rum-core): capture XHR/Fetch using resource timing

* chore: add tests and update fixtures

* chore: filter intake api from spans

* chore: handle duplicate url fetch before patch

* chore: add more tests for fitering logic

* chore: modify the logic as per review

* chore: fix  test

* chore: move to state object and fix test

* chore: add guard for patch time

* chore: address review
  • Loading branch information
vigneshshanmugam authored Jul 1, 2020
1 parent 09709d4 commit 9aa6fe3
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 252 deletions.
3 changes: 2 additions & 1 deletion packages/rum-core/src/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*
*/

import { isPlatformSupported, isBrowser } from './common/utils'
import { isPlatformSupported, isBrowser, now } from './common/utils'
import { patchAll } from './common/patching'
import { state } from './state'

Expand All @@ -32,6 +32,7 @@ export function bootstrap() {
if (isPlatformSupported()) {
patchAll()
bootstrapPerf()
state.bootstrapTime = now()
enabled = true
} else if (isBrowser) {
/**
Expand Down
4 changes: 1 addition & 3 deletions packages/rum-core/src/common/patching/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ import { patchFetch } from './fetch-patch'
import { patchHistory } from './history-patch'
import { patchEventTarget } from './event-target-patch'
import EventHandler from '../event-handler'

import { HISTORY, FETCH, XMLHTTPREQUEST, EVENT_TARGET } from '../constants'

const patchEventHandler = new EventHandler()

let alreadyPatched = false

function patchAll() {
if (!alreadyPatched) {
alreadyPatched = true
Expand All @@ -46,7 +45,6 @@ function patchAll() {
patchHistory(function(event, task) {
patchEventHandler.send(HISTORY, [event, task])
})

patchEventTarget(function(event, task) {
patchEventHandler.send(EVENT_TARGET, [event, task])
})
Expand Down
101 changes: 42 additions & 59 deletions packages/rum-core/src/performance-monitoring/capture-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
PERF,
isPerfTimelineSupported
} from '../common/utils'
import { state } from '../state'

/**
* Navigation Timing Spans
Expand Down Expand Up @@ -119,60 +120,57 @@ function createResourceTimingSpan(resourceTimingEntry) {
return span
}

function createResourceTimingSpans(entries, filterUrls, trStart, trEnd) {
/**
* Checks if the span is already captured via XHR/Fetch patch by
* comparing the given resource startTime(fetchStart) aganist the
* patch code execution time.
*/
function isCapturedByPatching(resourceStartTime, requestPatchTime) {
return requestPatchTime != null && resourceStartTime > requestPatchTime
}

/**
* Check if the given url matches APM Server's Intake
* API endpoint and ignore it from Spans
*/
function isIntakeAPIEndpoint(url) {
return /intake\/v\d+\/rum\/events/.test(url)
}

function createResourceTimingSpans(entries, requestPatchTime, trStart, trEnd) {
const spans = []
for (let i = 0; i < entries.length; i++) {
let { initiatorType, name, startTime, responseEnd } = entries[i]
const { initiatorType, name, startTime, responseEnd } = entries[i]
/**
* Skipping the timing information of API calls because of auto patching XHR and Fetch
* Skip span creation if initiatorType is other than known types specified as part of RESOURCE_INITIATOR_TYPES
* The reason being, there are other types like embed, video, audio, navigation etc
*
* Check the below webplatform test to know more
* https://github.com/web-platform-tests/wpt/blob/b0020d5df18998609b38786878f7a0b92cc680aa/resource-timing/resource_initiator_types.html#L93
*/
if (
initiatorType === 'xmlhttprequest' ||
initiatorType === 'fetch' ||
!name
RESOURCE_INITIATOR_TYPES.indexOf(initiatorType) === -1 ||
name == null
) {
continue
}

/**
* Create spans for all known resource initiator types
* Create Spans for API calls (XHR, Fetch) only if its not captured by the patch
*
* This would happen if our agent is downlaoded asyncrhonously and page does
* API requests before the agent patches the required modules.
*/
if (RESOURCE_INITIATOR_TYPES.indexOf(initiatorType) !== -1) {
if (!shouldCreateSpan(startTime, responseEnd, trStart, trEnd)) {
continue
}
spans.push(createResourceTimingSpan(entries[i]))
} else {
/**
* Since IE does not support initiatorType in Resource timing entry,
* We have to manually filter the API calls from creating duplicate Spans
*
* Skip span creation if initiatorType is other than known types specified as part of RESOURCE_INITIATOR_TYPES
* The reason being, there are other types like embed, video, audio, navigation etc
*
* Check the below webplatform test to know more
* https://github.com/web-platform-tests/wpt/blob/b0020d5df18998609b38786878f7a0b92cc680aa/resource-timing/resource_initiator_types.html#L93
*/
if (initiatorType != null) {
continue
}
if (
(initiatorType === 'xmlhttprequest' || initiatorType === 'fetch') &&
(isIntakeAPIEndpoint(name) ||
isCapturedByPatching(startTime, requestPatchTime))
) {
continue
}

let foundAjaxReq = false
for (let j = 0; j < filterUrls.length; j++) {
const idx = name.lastIndexOf(filterUrls[j])
if (idx > -1 && idx === name.length - filterUrls[j].length) {
foundAjaxReq = true
break
}
}
/**
* Create span if its not an ajax request
*/
if (
!foundAjaxReq &&
shouldCreateSpan(startTime, responseEnd, trStart, trEnd)
) {
spans.push(createResourceTimingSpan(entries[i]))
}
if (shouldCreateSpan(startTime, responseEnd, trStart, trEnd)) {
spans.push(createResourceTimingSpan(entries[i]))
}
}
return spans
Expand Down Expand Up @@ -200,18 +198,6 @@ function createUserTimingSpans(entries, trStart, trEnd) {
return userTimingSpans
}

function getApiSpanNames({ spans }) {
const apiCalls = []

for (let i = 0; i < spans.length; i++) {
const span = spans[i]
if (span.type === 'external' && span.subtype === 'http') {
apiCalls.push(span.name.split(' ')[1])
}
}
return apiCalls
}

/**
* Navigation timing marks are reported only for page-load transactions
*
Expand Down Expand Up @@ -312,7 +298,6 @@ function captureNavigation(transaction) {
* for few extra spans than soft navigations which
* happens on single page applications
*/

if (transaction.type === PAGE_LOAD) {
/**
* Adjust custom marks properly to fit in the transaction timeframe
Expand Down Expand Up @@ -356,11 +341,9 @@ function captureNavigation(transaction) {
* Capture resource timing information as spans
*/
const resourceEntries = PERF.getEntriesByType(RESOURCE)
const apiCalls = getApiSpanNames(transaction)

createResourceTimingSpans(
resourceEntries,
apiCalls,
state.bootstrapTime,
trStart,
trEnd
).forEach(span => transaction.spans.push(span))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import {
checkSameOrigin,
isDtHeaderValid,
parseDtHeaderValue,
stripQueryStringFromUrl,
getDtHeaderValue
getDtHeaderValue,
stripQueryStringFromUrl
} from '../common/utils'
import Url from '../common/url'
import { patchEventHandler } from '../common/patching'
Expand Down
10 changes: 7 additions & 3 deletions packages/rum-core/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
*/

const __DEV__ = process.env.NODE_ENV !== 'production'
const state = {
// Time when agent is bootstrapped and patching of modules happens
bootstrapTime: null,
// Time when the document is last backgrounded
lastHiddenStart: Number.MIN_SAFE_INTEGER
}

export { __DEV__ }

export const state = {}
export { __DEV__, state }
89 changes: 55 additions & 34 deletions packages/rum-core/test/fixtures/navigation-timing-span-snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,128 +26,149 @@
export default [
{
name: 'http://beacon.test',
type: 'resource.beacon',
ended: true,
type: 'resource',
subtype: 'beacon',
_end: 168.25,
_start: 25.220000000000002
},
{
name: 'http://testing.com',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 168.25,
_start: 25.220000000000002
},
{
name: 'http://localhost:9876/base/node_modules/karma-jasmine/lib/boot.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 172.8,
_start: 25.385
},
{
name:
'http://localhost:9876/base/node_modules/karma-jasmine/lib/adapter.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 174.04500000000002,
_start: 25.515000000000004
},
{
name: 'http://localhost:9876/base/tmp/globals.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 175.33,
_start: 25.640000000000004
},
{
name:
'http://localhost:9876/base/node_modules/elastic-apm-js-zone/dist/zone.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 176.12000000000003,
_start: 25.76
},
{
name: 'http://localhost:9876/base/test/utils/polyfill.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 176.865,
_start: 25.880000000000003
},
{
name: 'http://localhost:9876/base/test/common/apm-server.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 340.65500000000003,
_start: 26.000000000000004
},
{
name: 'http://localhost:9876/base/test/common/config-service.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 178.85000000000002,
_start: 26.150000000000002
},
{
name: 'http://localhost:9876/base/test/common/service-factory.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 180.935,
_start: 26.285000000000004
},
{
name: 'http://localhost:9876/base/test/common/utils.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 181.735,
_start: 26.405
},
{
name: 'http://localhost:9876/base/test/error-logging/error-logging.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 349.685,
_start: 26.520000000000003
},
{
name:
'http://localhost:9876/base/test/error-logging/stack-trace-service.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 350.44000000000005,
_start: 26.635
},
{
name:
'http://localhost:9876/base/test/performance-monitoring/performance-monitoring.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 351.72,
_start: 26.755000000000003
},
{
name:
'http://localhost:9876/base/test/performance-monitoring/transaction-service.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 257.22,
_start: 26.875000000000004
},
{
name:
'http://localhost:9876/base/test/performance-monitoring/transaction.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 187.60500000000002,
_start: 27.035000000000004
},
{
name:
'http://localhost:9876/base/test/performance-monitoring/zone-service.spec.js',
type: 'resource.script',
ended: true,
type: 'resource',
subtype: 'script',
_end: 188.72000000000003,
_start: 27.180000000000003
},
{
name: 'http://non-existing.com/v1/client-side/transactions',
type: 'resource',
subtype: 'xmlhttprequest',
_end: 857.8950000000001,
_start: 707.23
},
{
name: 'http://localhost:8200/v1/client-side/errors',
type: 'resource',
subtype: 'xmlhttprequest',
_end: 1625.49,
_start: 1619.825
},
{
name: 'http://localhost:8200/v1/client-side/transactions',
type: 'resource',
subtype: 'fetch',
_end: 2832.545,
_start: 2796.3600000000006
}
]
Loading

0 comments on commit 9aa6fe3

Please sign in to comment.