Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rum-core): capture XHR/Fetch spans using resource timing #825

Merged
merged 10 commits into from
Jul 1, 2020
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)
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved
}

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) {
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved
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
hmdhk marked this conversation as resolved.
Show resolved Hide resolved
}

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