Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/supportability-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,11 @@ EventBuffer/soft_navigations/Dropped/Bytes
* rrweb/node/3/bytes
<!-- node type 4 = Meta -->
* rrweb/node/4/bytes

### Browser Connect Response Metrics
<!--- HTTP status code of failed browser connect response --->
* 'Browser/Supportability/BCS/Error/<code>'
<!--- Total dropped payload size of failed browser connect response --->
* Browser/Supportability/BCS/Error/Dropped/Bytes
<!--- Response time of failed browser connect response --->
* Browser/Supportability/BCS/Error/Duration/Ms
50 changes: 50 additions & 0 deletions src/features/page_view_event/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
import { now } from '../../../common/timing/now'
import { TimeKeeper } from '../../../common/timing/time-keeper'
import { applyFnToProps } from '../../../common/util/traverse'
import { send } from '../../../common/harvest/harvester'
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
import { getSubmitMethod } from '../../../common/util/submit-data'

export class Aggregate extends AggregateBase {
static featureName = CONSTANTS.FEATURE_NAME
Expand Down Expand Up @@ -136,6 +139,53 @@ export class Aggregate extends AggregateBase {

if (status >= 400 || status === 0) {
warn(18, status)

// Get estimated payload size of our backlog
const textEncoder = new TextEncoder()
const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
if (!value) return acc

const encoded = textEncoder.encode(value)
return acc + encoded.byteLength
}, 0)

// Send SMs about failed RUM request
const body = {
sm: [{
params: {
name: `Browser/Supportability/BCS/Error/${status}`
},
stats: {
c: 1
}
},
{
params: {
name: 'Browser/Supportability/BCS/Error/Dropped/Bytes'
},
stats: {
c: 1,
t: payloadSize
}
},
{
params: {
name: 'Browser/Supportability/BCS/Error/Duration/Ms'
},
stats: {
c: 1,
t: rumEndTime - this.rumStartTime
}
}]
}

send(this.agentRef, {
endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
payload: { body },
submitMethod: getSubmitMethod(),
featureName: FEATURE_NAMES.metrics
})

// Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
this.ee.abort()
return
Expand Down
8 changes: 6 additions & 2 deletions tests/specs/harvesting/final-harvesting.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe('final harvesting', () => {
expect(pageHideTimingEvents.length).toEqual(1)
})

it('should not send any final harvest when RUM fails, e.g. 400 code', async () => {
it.only('should only send browser connect response metrics on final harvest when RUM fails, e.g. 400 code', async () => {
// Capture all BAM requests
const bamCapture = await browser.testHandle.createNetworkCaptures('bamServer', {
test: function () {
Expand All @@ -194,10 +194,14 @@ describe('final harvesting', () => {
.then(async () => browser.url(await browser.testHandle.assetURL('/')))
])

expect(bamHarvests.length).toEqual(1)
expect(bamHarvests.length).toEqual(2)
expect(bamHarvests[0].reply).toEqual(expect.objectContaining({
statusCode: 400,
body: ''
}))

// browser connect response metrics
expect(bamHarvests[1].reply.statusCode).toEqual(200)
expect(bamHarvests[1].request.body.sm.length).toBeGreaterThan(0)
})
})
53 changes: 53 additions & 0 deletions tests/specs/rum/supportability-metrics.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { testRumRequest, testMetricsRequest } from '../../../tools/testing-server/utils/expect-tests'

describe('basic pve capturing', () => {
let rumCapture
let metricsCapture

beforeEach(async () => {
[rumCapture, metricsCapture] = await browser.testHandle.createNetworkCaptures('bamServer', [
{ test: testRumRequest },
{ test: testMetricsRequest }
])
})

it('should report SMs when RUM call fails to browser connect service', async () => {
// will reply with http status 500 to fake error response from browser connect service
await browser.testHandle.scheduleReply('bamServer', {
test: testRumRequest,
statusCode: 500
})

// visit the webpage, not waiting for agent load since we don't expect the rum feature to load properly
await browser.url(await browser.testHandle.assetURL('instrumented.html'))

// wait for rum response harvest
const rumHarvest = await rumCapture.waitForResult({ totalCount: 1 })

// RUM harvest should have the expected http status code from the bam server
expect(rumHarvest[0].reply.statusCode).toBe(500)

// wait for supportability metrics harvest
const smHarvest = await metricsCapture.waitForResult({ totalCount: 1 })

// check for expected properties on status code supportability metric
const smHarvestStatusCode = smHarvest[0].request.body.sm.find(sm => sm.params.name === 'Browser/Supportability/BCS/Error/500')
expect(smHarvestStatusCode).toBeDefined()
expect(smHarvestStatusCode.stats).toBeDefined()
expect(smHarvestStatusCode.stats.c).toBe(1)

// check for expected properties on dropped bytes supportability metric
const smHarvestDroppedBytes = smHarvest[0].request.body.sm.find(sm => sm.params.name === 'Browser/Supportability/BCS/Error/Dropped/Bytes')
expect(smHarvestDroppedBytes).toBeDefined()
expect(smHarvestDroppedBytes.stats).toBeDefined()
expect(smHarvestDroppedBytes.stats.c).toBe(1)
expect(smHarvestDroppedBytes.stats.t).toBeGreaterThan(0)

// check for expected properties on response time supportability metric
const smHarvestResponseTime = smHarvest[0].request.body.sm.find(sm => sm.params.name === 'Browser/Supportability/BCS/Error/Duration/Ms')
expect(smHarvestResponseTime).toBeDefined()
expect(smHarvestResponseTime.stats).toBeDefined()
expect(smHarvestResponseTime.stats.c).toBe(1)
expect(smHarvestResponseTime.stats.t).toBeGreaterThan(0)
})
})
Loading