Skip to content

Commit

Permalink
Optimize data passing from backend to frontend
Browse files Browse the repository at this point in the history
Summary:
The devtools does a `JSON.parse(JSON.stringify(data))` on all Relay events to get around the limitation that all data passed through postMessages need to be serializable. But this is getting expensive as we add more information in Relay loggers.

This diff gets rid of the JSON calls, and instead preprocess data and filter out unused expensive information.

Reviewed By: andreqi

Differential Revision: D60936078

fbshipit-source-id: ba64679ee0df8db729dd2ec1d50e3c2a37c999be
  • Loading branch information
tyao1 authored and facebook-github-bot committed Aug 8, 2024
1 parent a19aabc commit fe1ec33
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 32 deletions.
2 changes: 1 addition & 1 deletion shells/browser/shared/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function setup(hook: any) {
window.postMessage(
{
source: 'relay-devtools-bridge',
payload: JSON.parse(JSON.stringify(events)),
payload: events,
},
'*'
);
Expand Down
2 changes: 1 addition & 1 deletion shells/dev/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const bridge = new Bridge<any, any>({
};
},
sendAll(events: Array<WallEvent>) {
window.parent.postMessage(JSON.parse(JSON.stringify(events)), '*');
window.parent.postMessage(events, '*');
},
});

Expand Down
102 changes: 73 additions & 29 deletions src/backend/EnvironmentWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,64 @@ import type {
EnvironmentWrapper,
} from './types';

const UNUSED_EXPENSIVE_FIELDS = [
'selector',
'sourceOperation',
'updatedOwners',
'operation',
'profilerContext',
];

const SUPPORTED_EVENTS = new Set([
'queryresource.fetch',
'network.info',
'store.publish',
'store.gc',
'store.restore',
'store.snapshot',
'store.notify.start',
'store.notify.complete',
'network.info',
'network.start',
'network.next',
'network.error',
'network.complete',
'network.unsubscribe',
]);

function sanitizeEvent(event: Object): Object {
// Convert some common data structures to objects or arrays
// Remove unused Relay data structures
const newEvent: Object = {};
const keys = Object.keys(event);
for (const key of keys) {
const value = event[key];
if (typeof value === 'function' || UNUSED_EXPENSIVE_FIELDS.includes(key)) {
continue;
} else if (value == null) {
newEvent[key] = value;
} else if (typeof value !== 'object') {
newEvent[key] = value;
} else if (value instanceof Map) {
newEvent[key] = Object.fromEntries((value: Map<mixed, mixed>));
} else if (value instanceof Set) {
newEvent[key] = Array.from(value);
} else if (typeof value.toJSON == 'function') {
// Convert RecordSource to Object, there are arbitary values for resolvers
newEvent[key] = JSON.parse(JSON.stringify(value.toJSON()));
} else if (
(key === 'info' && event.name === 'network.info') ||
key === 'cacheConfig'
) {
// Network info contains arbitary data
newEvent[key] = JSON.parse(JSON.stringify(value));
} else {
newEvent[key] = value;
}
}
return newEvent;
}

export function attach(
hook: DevToolsHook,
rendererID: number,
Expand All @@ -25,13 +83,17 @@ export function attach(
const originalLog = environment.__log;
environment.__log = event => {
originalLog(event);
if (!SUPPORTED_EVENTS.has(event.name)) {
return;
}
const sanitizedEvent = sanitizeEvent(event);
// TODO(damassart): Make this a modular function
if (pendingEventsQueue !== null) {
pendingEventsQueue.push(event);
pendingEventsQueue.push(sanitizedEvent);
} else {
hook.emit('environment.event', {
id: rendererID,
data: event,
data: sanitizedEvent,
eventType: 'environment',
});
}
Expand All @@ -43,33 +105,15 @@ export function attach(
if (storeOriginalLog !== null) {
storeOriginalLog(event);
}
switch (event.name) {
case 'store.gc':
// references is a Set, but we can't serialize Sets,
// so we convert references to an Array
event.references = Array.from(event.references);
hook.emit('environment.event', {
id: rendererID,
data: event,
eventType: 'store',
});
break;
case 'store.notify.complete':
event.invalidatedRecordIDs = Array.from(event.invalidatedRecordIDs);
hook.emit('environment.event', {
id: rendererID,
data: event,
eventType: 'store',
});
break;
default:
hook.emit('environment.event', {
id: rendererID,
data: event,
eventType: 'store',
});
break;
if (!SUPPORTED_EVENTS.has(event.name)) {
return;
}
const sanitizedEvent = sanitizeEvent(event);
hook.emit('environment.event', {
id: rendererID,
data: sanitizedEvent,
eventType: 'store',
});
};

function cleanup() {
Expand All @@ -79,7 +123,7 @@ export function attach(
}

function sendStoreRecords() {
const records = store.getSource().toJSON();
const records = JSON.parse(JSON.stringify(store.getSource().toJSON()));
hook.emit('environment.store', {
name: 'refresh.store',
id: rendererID,
Expand Down
3 changes: 2 additions & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type UpdatedRecords = { [dataID: DataID]: boolean, ... };
export type StoreRecords = { [DataID]: ?Record, ... };

// Copied from relay
// TODO: keep this up to date with relay
export type LogEvent =
| {|
+name: 'queryresource.fetch',
Expand All @@ -42,7 +43,7 @@ export type LogEvent =
+optimistic: boolean,
|}
| {|
+name: 'store.gc',
+name: 'store.gc', // TODO: Support the new GC event name
references: Array<DataID>,
gcRecords: StoreRecords,
|}
Expand Down

0 comments on commit fe1ec33

Please sign in to comment.