Skip to content

Commit 734045e

Browse files
committed
Add a "debug" mode to expose performance timing of sync functions
1 parent afc6b46 commit 734045e

3 files changed

Lines changed: 79 additions & 24 deletions

File tree

packages/sync/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Y.Map key representing the Y.Doc client ID of the user who performed the last sa
4646

4747
The sync manager orchestrates the lifecycle of syncing entity records. It creates Yjs documents, connects to providers, creates awareness instances, and coordinates with the `core-data` store.
4848

49+
_Parameters_
50+
51+
- _debug_ Whether to enable performance and debug logging.
52+
4953
### Delta
5054

5155
Deltas are used to calculate incremental Y.Text updates.

packages/sync/src/manager.ts

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
CRDT_RECORD_METADATA_MAP_KEY as RECORD_METADATA_KEY,
1313
CRDT_RECORD_METADATA_SAVED_AT_KEY as SAVED_AT_KEY,
1414
} from './config';
15+
import { logPerformanceTiming, passThru } from './performance';
1516
import { createPersistedCRDTDoc, getPersistedCrdtDoc } from './persistence';
1617
import { getProviderCreators } from './providers';
1718
import type {
@@ -50,12 +51,28 @@ interface EntityState {
5051
ydoc: CRDTDoc;
5152
}
5253

54+
/**
55+
* Get the entity ID for the given object type and object ID.
56+
*
57+
* @param {ObjectType} objectType Object type.
58+
* @param {ObjectID|null} objectId Object ID.
59+
*/
60+
function getEntityId(
61+
objectType: ObjectType,
62+
objectId: ObjectID | null
63+
): EntityID {
64+
return `${ objectType }_${ objectId }`;
65+
}
66+
5367
/**
5468
* The sync manager orchestrates the lifecycle of syncing entity records. It
5569
* creates Yjs documents, connects to providers, creates awareness instances,
5670
* and coordinates with the `core-data` store.
71+
*
72+
* @param debug Whether to enable performance and debug logging.
5773
*/
58-
export function createSyncManager(): SyncManager {
74+
export function createSyncManager( debug = false ): SyncManager {
75+
const debugWrap = debug ? logPerformanceTiming : passThru;
5976
const collectionStates: Map< ObjectType, CollectionState > = new Map();
6077
const entityStates: Map< EntityID, EntityState > = new Map();
6178

@@ -118,6 +135,15 @@ export function createSyncManager(): SyncManager {
118135
return; // Already bootstrapped.
119136
}
120137

138+
handlers = {
139+
addUndoMeta: debugWrap( handlers.addUndoMeta ),
140+
editRecord: debugWrap( handlers.editRecord ),
141+
getEditedRecord: debugWrap( handlers.getEditedRecord ),
142+
refetchRecord: debugWrap( handlers.refetchRecord ),
143+
restoreUndoMeta: debugWrap( handlers.restoreUndoMeta ),
144+
saveRecord: debugWrap( handlers.saveRecord ),
145+
};
146+
121147
const ydoc = createYjsDoc( { objectType } );
122148
const recordMap = ydoc.getMap( RECORD_KEY );
123149
const recordMetaMap = ydoc.getMap( RECORD_METADATA_KEY );
@@ -149,7 +175,7 @@ export function createSyncManager(): SyncManager {
149175
return;
150176
}
151177

152-
void updateEntityRecord( objectType, objectId );
178+
void internal.updateEntityRecord( objectType, objectId );
153179
};
154180

155181
const onRecordMetaUpdate = (
@@ -209,7 +235,7 @@ export function createSyncManager(): SyncManager {
209235
recordMetaMap.observe( onRecordMetaUpdate );
210236

211237
// Get and apply the persisted CRDT document, if it exists.
212-
applyPersistedCrdtDoc( objectType, objectId, record );
238+
internal.applyPersistedCrdtDoc( objectType, objectId, record );
213239
}
214240

215241
/**
@@ -310,19 +336,6 @@ export function createSyncManager(): SyncManager {
310336
updateCRDTDoc( objectType, null, {}, origin, { isSave: true } );
311337
}
312338

313-
/**
314-
* Get the entity ID for the given object type and object ID.
315-
*
316-
* @param {ObjectType} objectType Object type.
317-
* @param {ObjectID|null} objectId Object ID.
318-
*/
319-
function getEntityId(
320-
objectType: ObjectType,
321-
objectId: ObjectID | null
322-
): EntityID {
323-
return `${ objectType }_${ objectId }`;
324-
}
325-
326339
/**
327340
* Get the awareness instance for the given object type and object ID, if supported.
328341
*
@@ -353,7 +366,7 @@ export function createSyncManager(): SyncManager {
353366
* @param {ObjectID} objectId Object ID.
354367
* @param {ObjectData} record Entity record representing this object type.
355368
*/
356-
function applyPersistedCrdtDoc(
369+
function _applyPersistedCrdtDoc(
357370
objectType: ObjectType,
358371
objectId: ObjectID,
359372
record: ObjectData
@@ -506,7 +519,7 @@ export function createSyncManager(): SyncManager {
506519
* @param {ObjectType} objectType Object type of record to update.
507520
* @param {ObjectID} objectId Object ID of record to update.
508521
*/
509-
async function updateEntityRecord(
522+
async function _updateEntityRecord(
510523
objectType: ObjectType,
511524
objectId: ObjectID
512525
): Promise< void > {
@@ -557,16 +570,23 @@ export function createSyncManager(): SyncManager {
557570
return createPersistedCRDTDoc( entityState.ydoc );
558571
}
559572

573+
// Collect internal functions so that they can be wrapped before calling.
574+
const internal = {
575+
applyPersistedCrdtDoc: debugWrap( _applyPersistedCrdtDoc ),
576+
updateEntityRecord: debugWrap( _updateEntityRecord ),
577+
};
578+
579+
// Wrap and return the public API.
560580
return {
561-
createMeta: createEntityMeta,
562-
getAwareness,
563-
load: loadEntity,
564-
loadCollection,
581+
createMeta: debugWrap( createEntityMeta ),
582+
getAwareness: debugWrap( getAwareness ),
583+
load: debugWrap( loadEntity ),
584+
loadCollection: debugWrap( loadCollection ),
565585
// Use getter to ensure we always return the current value of `undoManager`.
566586
get undoManager(): SyncUndoManager | undefined {
567587
return undoManager;
568588
},
569-
unload: unloadEntity,
570-
update: updateCRDTDoc,
589+
unload: debugWrap( unloadEntity ),
590+
update: debugWrap( updateCRDTDoc ),
571591
};
572592
}

packages/sync/src/performance.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Wraps a function and logs the time it takes to execute.
3+
*
4+
* @param fn - The function to be wrapped.
5+
*/
6+
export function logPerformanceTiming<
7+
Params extends Array< unknown >,
8+
ReturnType = void,
9+
>( fn: ( ...args: Params ) => ReturnType ): typeof fn {
10+
return function ( this: unknown, ...args: Params ): ReturnType {
11+
const start = performance.now();
12+
const result = fn.apply( this, args );
13+
const end = performance.now();
14+
15+
// eslint-disable-next-line no-console
16+
console.log( `${ fn.name } took ${ ( end - start ).toFixed( 2 ) } ms` );
17+
18+
return result;
19+
};
20+
}
21+
22+
/**
23+
* A pass-through function that invokes the provided function with its arguments
24+
* without moidyfing its type.
25+
*
26+
* @param fn - The function to be invoked.
27+
*/
28+
export function passThru< T extends ( ...args: any[] ) => any >( fn: T ): T {
29+
return ( ( ...args: Parameters< T > ): ReturnType< T > =>
30+
fn( ...args ) ) as T;
31+
}

0 commit comments

Comments
 (0)