Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NoQA] Memoize: Fix debugging stats issues #48322

Merged
merged 4 commits into from
Sep 2, 2024
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
2 changes: 1 addition & 1 deletion src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function Search({queryJSON, isCustomQuery}: SearchProps) {
[isLargeScreenWidth],
);

const getItemHeightMemoized = memoize((item: TransactionListItemType | ReportListItemType) => getItemHeight(item), {
const getItemHeightMemoized = memoize(getItemHeight, {
transformKey: ([item]) => {
// List items are displayed differently on "L"arge and "N"arrow screens so the height will differ
// in addition the same items might be displayed as part of different Search screens ("Expenses", "All", "Finished")
Expand Down
2 changes: 1 addition & 1 deletion src/libs/NumberFormatUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import initPolyfill from './intlPolyfill';

initPolyfill();

const MemoizedNumberFormat = memoize(Intl.NumberFormat, {maxSize: 10});
const MemoizedNumberFormat = memoize(Intl.NumberFormat, {maxSize: 10, monitoringName: 'NumberFormatUtils'});

function format(locale: ValueOf<typeof CONST.LOCALES>, number: number, options?: Intl.NumberFormatOptions): string {
return new MemoizedNumberFormat(locale, options).format(number);
Expand Down
11 changes: 7 additions & 4 deletions src/libs/freezeScreenWithLazyLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ function FrozenScreen<TProps extends React.JSX.IntrinsicAttributes>(WrappedCompo
}

export default function freezeScreenWithLazyLoading(lazyComponent: () => React.ComponentType) {
return memoize(() => {
const Component = lazyComponent();
return FrozenScreen(Component);
});
return memoize(
() => {
const Component = lazyComponent();
return FrozenScreen(Component);
},
{monitoringName: 'freezeScreenWithLazyLoading'},
);
}
19 changes: 11 additions & 8 deletions src/libs/memoize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,32 @@ function memoize<Fn extends IsomorphicFn, MaxArgs extends number = NonPartial<Is
const stats = new MemoizeStats(options.monitor || Memoize.isMonitoringEnabled);

const memoized = function memoized(...args: IsomorphicParameters<Fn>): IsomorphicReturnType<Fn> {
const statsEntry = stats.createEntry();
const retrievalTimeStart = performance.now();

// Detect if memoized function was called with `new` keyword. If so we need to call the original function as constructor.
const constructable = !!new.target;

const truncatedArgs = truncateArgs(args, options.maxArgs);

const key = options.transformKey ? options.transformKey(truncatedArgs) : (truncatedArgs as Key);

const statsEntry = stats.createEntry();
statsEntry.track('didHit', true);

const retrievalTimeStart = performance.now();
const cached = cache.getSet(key, () => {
const fnTimeStart = performance.now();

const result = (constructable ? new (fn as Constructable)(...args) : (fn as Callable)(...args)) as IsomorphicReturnType<Fn>;

statsEntry.trackTime('fnTime', fnTimeStart);
// Track processing time
statsEntry.trackTime('processingTime', fnTimeStart);
statsEntry.track('didHit', false);

return result;
});
// Subtract the time it took to run the function from the total retrieval time
statsEntry.trackTime('cacheRetrievalTime', retrievalTimeStart + (statsEntry.get('fnTime') ?? 0));

// If processing time was not tracked inside getSet callback, track it as a cache retrieval
if (statsEntry.get('processingTime') === undefined) {
statsEntry.trackTime('processingTime', retrievalTimeStart);
statsEntry.track('didHit', true);
}

statsEntry.track('cacheSize', cache.size);
statsEntry.save();
Expand Down
24 changes: 11 additions & 13 deletions src/libs/memoize/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@ import Log from '@libs/Log';

type MemoizeStatsEntry = {
didHit: boolean;
cacheRetrievalTime: number;
fnTime?: number;
processingTime: number;
cacheSize: number;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isMemoizeStatsEntry(entry: any): entry is MemoizeStatsEntry {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return entry.didHit !== undefined && entry.cacheRetrievalTime !== undefined;
return entry.didHit !== undefined && entry.processingTime !== undefined;
}

class MemoizeStats {
private calls = 0;

private hits = 0;

private avgCacheRetrievalTime = 0;
private avgCacheTime = 0;

private avgFnTime = 0;

Expand All @@ -39,14 +38,13 @@ class MemoizeStats {

private cumulateEntry(entry: MemoizeStatsEntry) {
this.calls++;
this.hits += entry.didHit ? 1 : 0;

this.cacheSize = entry.cacheSize;

this.avgCacheRetrievalTime = this.calculateCumulativeAvg(this.avgCacheRetrievalTime, this.hits, entry.cacheRetrievalTime);

if (entry.fnTime !== undefined) {
this.avgFnTime = this.calculateCumulativeAvg(this.avgFnTime, this.calls - this.hits, entry.fnTime);
if (entry.didHit) {
this.hits++;
this.avgCacheTime = this.calculateCumulativeAvg(this.avgCacheTime, this.hits, entry.processingTime);
} else {
this.avgFnTime = this.calculateCumulativeAvg(this.avgFnTime, this.calls - this.hits, entry.processingTime);
}
}

Expand Down Expand Up @@ -80,7 +78,7 @@ class MemoizeStats {
track: <P extends keyof MemoizeStatsEntry>(cacheProp: P, value: MemoizeStatsEntry[P]) => {
entry[cacheProp] = value;
},
trackTime: <P extends keyof Pick<MemoizeStatsEntry, 'cacheRetrievalTime' | 'fnTime'>>(cacheProp: P, startTime: number, endTime = performance.now()) => {
trackTime: <P extends keyof Pick<MemoizeStatsEntry, 'processingTime'>>(cacheProp: P, startTime: number, endTime = performance.now()) => {
entry[cacheProp] = endTime - startTime;
},
get: <P extends keyof MemoizeStatsEntry>(cacheProp: P) => entry[cacheProp],
Expand All @@ -92,7 +90,7 @@ class MemoizeStats {
this.isEnabled = true;
this.calls = 0;
this.hits = 0;
this.avgCacheRetrievalTime = 0;
this.avgCacheTime = 0;
this.avgFnTime = 0;
this.cacheSize = 0;
}
Expand All @@ -103,7 +101,7 @@ class MemoizeStats {
return {
calls: this.calls,
hits: this.hits,
avgCacheRetrievalTime: this.avgCacheRetrievalTime,
avgCacheTime: this.avgCacheTime,
avgFnTime: this.avgFnTime,
cacheSize: this.cacheSize,
};
Expand Down
Loading