Skip to content

Commit 6f2c7b7

Browse files
authored
Control the maximum amount of events in single logRequest to be 1000 in Performance Monitoring (#3896)
1 parent 2541375 commit 6f2c7b7

File tree

5 files changed

+194
-44
lines changed

5 files changed

+194
-44
lines changed

.changeset/snake-ten-some.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/performance': patch
3+
'firebase': patch
4+
---
5+
6+
Dispatch up to 1000 events for each network request when collecting performance events.

packages-exp/performance-exp/src/services/transport_service.test.ts

+89-19
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ describe('Firebase Performance > transport_service', () => {
3131
let fetchStub: SinonStub<[RequestInfo, RequestInit?], Promise<Response>>;
3232
const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000;
3333
const DEFAULT_SEND_INTERVAL_MS = 10 * 1000;
34+
const MAX_EVENT_COUNT_PER_REQUEST = 1000;
35+
const TRANSPORT_DELAY_INTERVAL = 10000;
3436
// Starts date at timestamp 1 instead of 0, otherwise it causes validation errors.
3537
let clock: SinonFakeTimers;
3638
const testTransportHandler = transportHandler((...args) => {
@@ -55,7 +57,7 @@ describe('Firebase Performance > transport_service', () => {
5557
}).to.throw;
5658
});
5759

58-
it('does not attempt to log an event to cc after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => {
60+
it('does not attempt to log an event after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => {
5961
fetchStub.resolves(
6062
new Response('', {
6163
status: 200,
@@ -67,7 +69,7 @@ describe('Firebase Performance > transport_service', () => {
6769
expect(fetchStub).to.not.have.been.called;
6870
});
6971

70-
it('attempts to log an event to cc after DEFAULT_SEND_INTERVAL_MS if queue not empty', () => {
72+
it('attempts to log an event after DEFAULT_SEND_INTERVAL_MS if queue not empty', async () => {
7173
fetchStub.resolves(
7274
new Response('', {
7375
status: 200,
@@ -82,32 +84,100 @@ describe('Firebase Performance > transport_service', () => {
8284
});
8385

8486
it('successful send a meesage to transport', () => {
85-
const transportDelayInterval = 30000;
8687
const setting = SettingsService.getInstance();
8788
const flTransportFullUrl =
8889
setting.flTransportEndpointUrl + '?key=' + setting.transportKey;
8990
fetchStub.withArgs(flTransportFullUrl, match.any).resolves(
9091
// DELETE_REQUEST means event dispatch is successful.
91-
new Response(
92-
'{\
93-
"nextRequestWaitMillis": "' +
94-
transportDelayInterval +
95-
'",\
96-
"logResponseDetails": [\
97-
{\
98-
"responseAction": "DELETE_REQUEST"\
99-
}\
100-
]\
101-
}',
102-
{
103-
status: 200,
104-
headers: { 'Content-type': 'application/json' }
105-
}
106-
)
92+
generateSuccessResponse()
10793
);
10894

10995
testTransportHandler('event1');
11096
clock.tick(INITIAL_SEND_TIME_DELAY_MS);
11197
expect(fetchStub).to.have.been.calledOnce;
11298
});
99+
100+
it('sends up to the maximum event limit in one request', async () => {
101+
// Arrange
102+
const setting = SettingsService.getInstance();
103+
const flTransportFullUrl =
104+
setting.flTransportEndpointUrl + '?key=' + setting.transportKey;
105+
106+
// Returns successful response from fl for logRequests.
107+
const response = generateSuccessResponse();
108+
stub(response, 'json').resolves(JSON.parse(generateSuccessResponseBody()));
109+
fetchStub.resolves(response);
110+
111+
// Act
112+
// Generate 1020 events, which should be dispatched in two batches (1000 events and 20 events).
113+
for (let i = 0; i < 1020; i++) {
114+
testTransportHandler('event' + i);
115+
}
116+
// Wait for first and second event dispatch to happen.
117+
clock.tick(INITIAL_SEND_TIME_DELAY_MS);
118+
// This is to resolve the floating promise chain in transport service.
119+
await Promise.resolve().then().then().then();
120+
clock.tick(DEFAULT_SEND_INTERVAL_MS);
121+
122+
// Assert
123+
// Expects the first logRequest which contains first 1000 events.
124+
const firstLogRequest = generateLogRequest('5501');
125+
for (let i = 0; i < MAX_EVENT_COUNT_PER_REQUEST; i++) {
126+
firstLogRequest['log_event'].push({
127+
'source_extension_json_proto3': 'event' + i,
128+
'event_time_ms': '1'
129+
});
130+
}
131+
expect(fetchStub).which.to.have.been.calledWith(flTransportFullUrl, {
132+
method: 'POST',
133+
body: JSON.stringify(firstLogRequest)
134+
});
135+
// Expects the second logRequest which contains remaining 20 events;
136+
const secondLogRequest = generateLogRequest('15501');
137+
for (let i = 0; i < 20; i++) {
138+
secondLogRequest['log_event'].push({
139+
'source_extension_json_proto3':
140+
'event' + (MAX_EVENT_COUNT_PER_REQUEST + i),
141+
'event_time_ms': '1'
142+
});
143+
}
144+
expect(fetchStub).calledWith(flTransportFullUrl, {
145+
method: 'POST',
146+
body: JSON.stringify(secondLogRequest)
147+
});
148+
});
149+
150+
function generateLogRequest(requestTimeMs: string): any {
151+
return {
152+
'request_time_ms': requestTimeMs,
153+
'client_info': {
154+
'client_type': 1,
155+
'js_client_info': {}
156+
},
157+
'log_source': 462,
158+
'log_event': [] as any
159+
};
160+
}
161+
162+
function generateSuccessResponse(): Response {
163+
return new Response(generateSuccessResponseBody(), {
164+
status: 200,
165+
headers: { 'Content-type': 'application/json' }
166+
});
167+
}
168+
169+
function generateSuccessResponseBody(): string {
170+
return (
171+
'{\
172+
"nextRequestWaitMillis": "' +
173+
TRANSPORT_DELAY_INTERVAL +
174+
'",\
175+
"logResponseDetails": [\
176+
{\
177+
"responseAction": "DELETE_REQUEST"\
178+
}\
179+
]\
180+
}'
181+
);
182+
}
113183
});

packages-exp/performance-exp/src/services/transport_service.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const DEFAULT_SEND_INTERVAL_MS = 10 * 1000;
2323
const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000;
2424
// If end point does not work, the call will be tried for these many times.
2525
const DEFAULT_REMAINING_TRIES = 3;
26+
const MAX_EVENT_COUNT_PER_REQUEST = 1000;
2627
let remainingTries = DEFAULT_REMAINING_TRIES;
2728

2829
interface LogResponseDetails {
@@ -90,9 +91,10 @@ function processQueue(timeOffset: number): void {
9091
}
9192

9293
function dispatchQueueEvents(): void {
93-
// Capture a snapshot of the queue and empty the "official queue".
94-
const staged = [...queue];
95-
queue = [];
94+
// Extract events up to the maximum cap of single logRequest from top of "official queue".
95+
// The staged events will be used for current logRequest attempt, remaining events will be kept
96+
// for next attempt.
97+
const staged = queue.splice(0, MAX_EVENT_COUNT_PER_REQUEST);
9698

9799
/* eslint-disable camelcase */
98100
// We will pass the JSON serialized event to the backend.

packages/performance/src/services/transport_service.test.ts

+89-19
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ describe('Firebase Performance > transport_service', () => {
3131
let fetchStub: SinonStub<[RequestInfo, RequestInit?], Promise<Response>>;
3232
const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000;
3333
const DEFAULT_SEND_INTERVAL_MS = 10 * 1000;
34+
const MAX_EVENT_COUNT_PER_REQUEST = 1000;
35+
const TRANSPORT_DELAY_INTERVAL = 10000;
3436
// Starts date at timestamp 1 instead of 0, otherwise it causes validation errors.
3537
let clock: SinonFakeTimers;
3638
const testTransportHandler = transportHandler((...args) => {
@@ -55,7 +57,7 @@ describe('Firebase Performance > transport_service', () => {
5557
}).to.throw;
5658
});
5759

58-
it('does not attempt to log an event to cc after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => {
60+
it('does not attempt to log an event after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => {
5961
fetchStub.resolves(
6062
new Response('', {
6163
status: 200,
@@ -67,7 +69,7 @@ describe('Firebase Performance > transport_service', () => {
6769
expect(fetchStub).to.not.have.been.called;
6870
});
6971

70-
it('attempts to log an event to cc after DEFAULT_SEND_INTERVAL_MS if queue not empty', () => {
72+
it('attempts to log an event after DEFAULT_SEND_INTERVAL_MS if queue not empty', async () => {
7173
fetchStub.resolves(
7274
new Response('', {
7375
status: 200,
@@ -82,32 +84,100 @@ describe('Firebase Performance > transport_service', () => {
8284
});
8385

8486
it('successful send a meesage to transport', () => {
85-
const transportDelayInterval = 30000;
8687
const setting = SettingsService.getInstance();
8788
const flTransportFullUrl =
8889
setting.flTransportEndpointUrl + '?key=' + setting.transportKey;
8990
fetchStub.withArgs(flTransportFullUrl, match.any).resolves(
9091
// DELETE_REQUEST means event dispatch is successful.
91-
new Response(
92-
'{\
93-
"nextRequestWaitMillis": "' +
94-
transportDelayInterval +
95-
'",\
96-
"logResponseDetails": [\
97-
{\
98-
"responseAction": "DELETE_REQUEST"\
99-
}\
100-
]\
101-
}',
102-
{
103-
status: 200,
104-
headers: { 'Content-type': 'application/json' }
105-
}
106-
)
92+
generateSuccessResponse()
10793
);
10894

10995
testTransportHandler('event1');
11096
clock.tick(INITIAL_SEND_TIME_DELAY_MS);
11197
expect(fetchStub).to.have.been.calledOnce;
11298
});
99+
100+
it('sends up to the maximum event limit in one request', async () => {
101+
// Arrange
102+
const setting = SettingsService.getInstance();
103+
const flTransportFullUrl =
104+
setting.flTransportEndpointUrl + '?key=' + setting.transportKey;
105+
106+
// Returns successful response from fl for logRequests.
107+
const response = generateSuccessResponse();
108+
stub(response, 'json').resolves(JSON.parse(generateSuccessResponseBody()));
109+
fetchStub.resolves(response);
110+
111+
// Act
112+
// Generate 1020 events, which should be dispatched in two batches (1000 events and 20 events).
113+
for (let i = 0; i < 1020; i++) {
114+
testTransportHandler('event' + i);
115+
}
116+
// Wait for first and second event dispatch to happen.
117+
clock.tick(INITIAL_SEND_TIME_DELAY_MS);
118+
// This is to resolve the floating promise chain in transport service.
119+
await Promise.resolve().then().then().then();
120+
clock.tick(DEFAULT_SEND_INTERVAL_MS);
121+
122+
// Assert
123+
// Expects the first logRequest which contains first 1000 events.
124+
const firstLogRequest = generateLogRequest('5501');
125+
for (let i = 0; i < MAX_EVENT_COUNT_PER_REQUEST; i++) {
126+
firstLogRequest['log_event'].push({
127+
'source_extension_json_proto3': 'event' + i,
128+
'event_time_ms': '1'
129+
});
130+
}
131+
expect(fetchStub).which.to.have.been.calledWith(flTransportFullUrl, {
132+
method: 'POST',
133+
body: JSON.stringify(firstLogRequest)
134+
});
135+
// Expects the second logRequest which contains remaining 20 events;
136+
const secondLogRequest = generateLogRequest('15501');
137+
for (let i = 0; i < 20; i++) {
138+
secondLogRequest['log_event'].push({
139+
'source_extension_json_proto3':
140+
'event' + (MAX_EVENT_COUNT_PER_REQUEST + i),
141+
'event_time_ms': '1'
142+
});
143+
}
144+
expect(fetchStub).calledWith(flTransportFullUrl, {
145+
method: 'POST',
146+
body: JSON.stringify(secondLogRequest)
147+
});
148+
});
149+
150+
function generateLogRequest(requestTimeMs: string): any {
151+
return {
152+
'request_time_ms': requestTimeMs,
153+
'client_info': {
154+
'client_type': 1,
155+
'js_client_info': {}
156+
},
157+
'log_source': 462,
158+
'log_event': [] as any
159+
};
160+
}
161+
162+
function generateSuccessResponse(): Response {
163+
return new Response(generateSuccessResponseBody(), {
164+
status: 200,
165+
headers: { 'Content-type': 'application/json' }
166+
});
167+
}
168+
169+
function generateSuccessResponseBody(): string {
170+
return (
171+
'{\
172+
"nextRequestWaitMillis": "' +
173+
TRANSPORT_DELAY_INTERVAL +
174+
'",\
175+
"logResponseDetails": [\
176+
{\
177+
"responseAction": "DELETE_REQUEST"\
178+
}\
179+
]\
180+
}'
181+
);
182+
}
113183
});

packages/performance/src/services/transport_service.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const DEFAULT_SEND_INTERVAL_MS = 10 * 1000;
2323
const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000;
2424
// If end point does not work, the call will be tried for these many times.
2525
const DEFAULT_REMAINING_TRIES = 3;
26+
const MAX_EVENT_COUNT_PER_REQUEST = 1000;
2627
let remainingTries = DEFAULT_REMAINING_TRIES;
2728

2829
interface LogResponseDetails {
@@ -90,9 +91,10 @@ function processQueue(timeOffset: number): void {
9091
}
9192

9293
function dispatchQueueEvents(): void {
93-
// Capture a snapshot of the queue and empty the "official queue".
94-
const staged = [...queue];
95-
queue = [];
94+
// Extract events up to the maximum cap of single logRequest from top of "official queue".
95+
// The staged events will be used for current logRequest attempt, remaining events will be kept
96+
// for next attempt.
97+
const staged = queue.splice(0, MAX_EVENT_COUNT_PER_REQUEST);
9698

9799
/* eslint-disable camelcase */
98100
// We will pass the JSON serialized event to the backend.

0 commit comments

Comments
 (0)