Skip to content

Commit c38c602

Browse files
authored
Merge 00c347e into 6ef39d4
2 parents 6ef39d4 + 00c347e commit c38c602

File tree

5 files changed

+190
-43
lines changed

5 files changed

+190
-43
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

+87-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) => {
@@ -66,8 +68,7 @@ describe('Firebase Performance > transport_service', () => {
6668
clock.tick(INITIAL_SEND_TIME_DELAY_MS);
6769
expect(fetchStub).to.not.have.been.called;
6870
});
69-
70-
it('attempts to log an event to cc after DEFAULT_SEND_INTERVAL_MS if queue not empty', () => {
71+
it('attempts to log an event to cc after DEFAULT_SEND_INTERVAL_MS if queue not empty', async () => {
7172
fetchStub.resolves(
7273
new Response('', {
7374
status: 200,
@@ -82,32 +83,99 @@ describe('Firebase Performance > transport_service', () => {
8283
});
8384

8485
it('successful send a meesage to transport', () => {
85-
const transportDelayInterval = 30000;
8686
const setting = SettingsService.getInstance();
8787
const flTransportFullUrl =
8888
setting.flTransportEndpointUrl + '?key=' + setting.transportKey;
8989
fetchStub.withArgs(flTransportFullUrl, match.any).resolves(
9090
// 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-
)
91+
generateSuccessResponse()
10792
);
10893

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

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

+87-18
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) => {
@@ -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 to cc after DEFAULT_SEND_INTERVAL_MS if queue not empty', async () => {
7173
fetchStub.resolves(
7274
new Response('', {
7375
status: 200,
@@ -82,32 +84,99 @@ 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+
fetchStub.resolves(response);
109+
stub(response, 'json').resolves(JSON.parse(generateSuccessResponseBody()));
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+
await Promise.resolve().then().then().then();
119+
clock.tick(DEFAULT_SEND_INTERVAL_MS);
120+
121+
// Assert
122+
// Expects the first logRequest which contains first 1000 events.
123+
const firstLogRequest = generateLogRequest('5501');
124+
for (let i = 0; i < MAX_EVENT_COUNT_PER_REQUEST; i++) {
125+
firstLogRequest['log_event'].push({
126+
'source_extension_json_proto3': 'event' + i,
127+
'event_time_ms': '1'
128+
});
129+
}
130+
expect(fetchStub).which.to.have.been.calledWith(flTransportFullUrl, {
131+
method: 'POST',
132+
body: JSON.stringify(firstLogRequest)
133+
});
134+
// Expects the second logRequest which contains remaining 20 events;
135+
const secondLogRequest = generateLogRequest('15501');
136+
for (let i = 0; i < 20; i++) {
137+
secondLogRequest['log_event'].push({
138+
'source_extension_json_proto3':
139+
'event' + (MAX_EVENT_COUNT_PER_REQUEST + i),
140+
'event_time_ms': '1'
141+
});
142+
}
143+
expect(fetchStub).calledWith(flTransportFullUrl, {
144+
method: 'POST',
145+
body: JSON.stringify(secondLogRequest)
146+
});
147+
});
148+
149+
function generateLogRequest(requestTimeMs: string): any {
150+
return {
151+
'request_time_ms': requestTimeMs,
152+
'client_info': {
153+
'client_type': 1,
154+
'js_client_info': {}
155+
},
156+
'log_source': 462,
157+
'log_event': [] as any
158+
};
159+
}
160+
161+
function generateSuccessResponse(): Response {
162+
return new Response(generateSuccessResponseBody(), {
163+
status: 200,
164+
headers: { 'Content-type': 'application/json' }
165+
});
166+
}
167+
168+
function generateSuccessResponseBody(): string {
169+
return (
170+
'{\
171+
"nextRequestWaitMillis": "' +
172+
TRANSPORT_DELAY_INTERVAL +
173+
'",\
174+
"logResponseDetails": [\
175+
{\
176+
"responseAction": "DELETE_REQUEST"\
177+
}\
178+
]\
179+
}'
180+
);
181+
}
113182
});

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)