Skip to content

Commit 8be5521

Browse files
mountinycursoragent
andcommitted
Perf: Add diagnostic sub-spans to ManualOpenSearchRouter
Add 4 sub-spans and 2 attributes to the ManualOpenSearchRouter Sentry span to identify which phases of the search router opening are bottlenecks. Sub-spans: - SearchRouter.ModalCloseWait: Modal.close() callback latency - SearchRouter.OptionsInit: Cold-path createOptionList() cost - SearchRouter.ComputeOptions: JS computation in SearchAutocompleteList - SearchRouter.ListRender: FlashList rendering + native layout Attributes: - cold_start: whether options needed initialization - trigger: 'button' or 'keyboard' Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 8973418 commit 8be5521

File tree

6 files changed

+212
-178
lines changed

6 files changed

+212
-178
lines changed

src/CONST/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,10 @@ const CONST = {
18081808
SPAN_OD_ND_TRANSITION: 'ManualOdNdTransition',
18091809
SPAN_OD_ND_TRANSITION_LOGGED_OUT: 'ManualOdNdTransitionLoggedOut',
18101810
SPAN_OPEN_SEARCH_ROUTER: 'ManualOpenSearchRouter',
1811+
SPAN_SEARCH_ROUTER_MODAL_CLOSE_WAIT: 'SearchRouter.ModalCloseWait',
1812+
SPAN_SEARCH_ROUTER_OPTIONS_INIT: 'SearchRouter.OptionsInit',
1813+
SPAN_SEARCH_ROUTER_COMPUTE_OPTIONS: 'SearchRouter.ComputeOptions',
1814+
SPAN_SEARCH_ROUTER_LIST_RENDER: 'SearchRouter.ListRender',
18111815
SPAN_OPEN_CREATE_EXPENSE: 'ManualOpenCreateExpense',
18121816
SPAN_SUBMIT_EXPENSE: 'ManualCreateExpenseSubmit',
18131817
SPAN_NAVIGATE_AFTER_EXPENSE_CREATE: 'ManualCreateExpenseNavigation',

src/components/OptionListContextProvider.tsx

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
2-
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
1+
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2+
import type { OnyxCollection, OnyxEntry } from 'react-native-onyx';
33
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
44
import useOnyx from '@hooks/useOnyx';
55
import usePrevious from '@hooks/usePrevious';
66
import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap';
7-
import {createOptionFromReport, createOptionList, processReport, shallowOptionsListCompare} from '@libs/OptionsListUtils';
8-
import type {OptionList, SearchOption} from '@libs/OptionsListUtils';
9-
import {isSelfDM} from '@libs/ReportUtils';
7+
import { createOptionFromReport, createOptionList, processReport, shallowOptionsListCompare } from '@libs/OptionsListUtils';
8+
import type { OptionList, SearchOption } from '@libs/OptionsListUtils';
9+
import { isSelfDM } from '@libs/ReportUtils';
10+
import { endSpan, getSpan, startSpan } from '@libs/telemetry/activeSpans';
11+
import CONST from '@src/CONST';
1012
import ONYXKEYS from '@src/ONYXKEYS';
11-
import type {PersonalDetails, Report} from '@src/types/onyx';
12-
import {usePersonalDetails} from './OnyxListItemProvider';
13+
import type { PersonalDetails, Report } from '@src/types/onyx';
14+
import { usePersonalDetails } from './OnyxListItemProvider';
1315

1416
type OptionsListContextProps = {
1517
/** List of options for reports and personal details */
@@ -43,17 +45,17 @@ const isEqualPersonalDetail = (prevPersonalDetail: PersonalDetails, personalDeta
4345
prevPersonalDetail?.login === personalDetail?.login &&
4446
prevPersonalDetail?.displayName === personalDetail?.displayName;
4547

46-
function OptionsListContextProvider({children}: OptionsListProviderProps) {
48+
function OptionsListContextProvider({ children }: OptionsListProviderProps) {
4749
const areOptionsInitialized = useRef(false);
4850
const [options, setOptions] = useState<OptionList>({
4951
reports: [],
5052
personalDetails: [],
5153
});
52-
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true});
54+
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, { canBeMissing: true });
5355
const prevReportAttributesLocale = usePrevious(reportAttributes?.locale);
54-
const [reports, {sourceValue: changedReports}] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
56+
const [reports, { sourceValue: changedReports }] = useOnyx(ONYXKEYS.COLLECTION.REPORT, { canBeMissing: true });
5557
const prevReports = usePrevious(reports);
56-
const [, {sourceValue: changedReportActions}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true});
58+
const [, { sourceValue: changedReportActions }] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { canBeMissing: true });
5759
const personalDetails = usePersonalDetails();
5860
const prevPersonalDetails = usePrevious(personalDetails);
5961
const privateIsArchivedMap = usePrivateIsArchivedMap();
@@ -123,7 +125,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
123125
for (const reportKey of changedReportKeys) {
124126
const report = changedReportsEntries[reportKey];
125127
const reportID = reportKey.replace(ONYXKEYS.COLLECTION.REPORT, '');
126-
const {reportOption} = processReport(report, personalDetails, currentUserAccountID, reportAttributes?.reports);
128+
const { reportOption } = processReport(report, personalDetails, currentUserAccountID, reportAttributes?.reports);
127129

128130
if (reportOption) {
129131
updatedReportsMap.set(reportID, reportOption);
@@ -157,7 +159,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
157159
}
158160

159161
const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, '');
160-
const {reportOption} = processReport(updatedReportsMap.get(reportID)?.item, personalDetails, currentUserAccountID, reportAttributes?.reports);
162+
const { reportOption } = processReport(updatedReportsMap.get(reportID)?.item, personalDetails, currentUserAccountID, reportAttributes?.reports);
161163

162164
if (reportOption) {
163165
updatedReportsMap.set(reportID, reportOption);
@@ -187,7 +189,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
187189
// Handle initial personal details load. This initialization is required here specifically to prevent
188190
// UI freezing that occurs when resetting the app from the troubleshooting page.
189191
if (!prevPersonalDetails) {
190-
const {personalDetails: newPersonalDetailsOptions, reports: newReports} = createOptionList(
192+
const { personalDetails: newPersonalDetailsOptions, reports: newReports } = createOptionList(
191193
personalDetails,
192194
currentUserAccountID,
193195
privateIsArchivedMap,
@@ -227,7 +229,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
227229
}
228230

229231
const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`];
230-
const newReportOption = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportAttributes?.reports, {showPersonalDetails: true});
232+
const newReportOption = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportAttributes?.reports, { showPersonalDetails: true });
231233
const replaceIndex = options.reports.findIndex((option) => option.reportID === report.reportID);
232234
newReportOptions.push({
233235
newReportOption,
@@ -240,7 +242,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
240242
const newPersonalDetailsOptions = createOptionList(personalDetails, currentUserAccountID, privateIsArchivedMap, reports, reportAttributes?.reports).personalDetails;
241243

242244
setOptions((prevOptions) => {
243-
const newOptions = {...prevOptions};
245+
const newOptions = { ...prevOptions };
244246
newOptions.personalDetails = newPersonalDetailsOptions;
245247
for (const newReportOption of newReportOptions) {
246248
newOptions.reports[newReportOption.replaceIndex] = newReportOption.newReportOption;
@@ -253,8 +255,18 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
253255
}, [personalDetails]);
254256

255257
const initializeOptions = useCallback(() => {
258+
const isSearchRouterSpanActive = !!getSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER);
259+
if (isSearchRouterSpanActive) {
260+
startSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_OPTIONS_INIT, {
261+
name: CONST.TELEMETRY.SPAN_SEARCH_ROUTER_OPTIONS_INIT,
262+
op: 'function',
263+
});
264+
}
256265
loadOptions();
257266
areOptionsInitialized.current = true;
267+
if (isSearchRouterSpanActive) {
268+
endSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_OPTIONS_INIT);
269+
}
258270
}, [loadOptions]);
259271

260272
const resetOptions = useCallback(() => {
@@ -271,7 +283,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
271283

272284
return (
273285
<OptionsListContext.Provider
274-
value={useMemo(() => ({options, initializeOptions, areOptionsInitialized: areOptionsInitialized.current, resetOptions}), [options, initializeOptions, resetOptions])}
286+
value={useMemo(() => ({ options, initializeOptions, areOptionsInitialized: areOptionsInitialized.current, resetOptions }), [options, initializeOptions, resetOptions])}
275287
>
276288
{children}
277289
</OptionsListContext.Provider>
@@ -281,9 +293,9 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) {
281293
const useOptionsListContext = () => useContext(OptionsListContext);
282294

283295
// Hook to use the OptionsListContext with an initializer to load the options
284-
const useOptionsList = (options?: {shouldInitialize: boolean}) => {
285-
const {shouldInitialize = true} = options ?? {};
286-
const {initializeOptions, options: optionsList, areOptionsInitialized, resetOptions} = useOptionsListContext();
296+
const useOptionsList = (options?: { shouldInitialize: boolean }) => {
297+
const { shouldInitialize = true } = options ?? {};
298+
const { initializeOptions, options: optionsList, areOptionsInitialized, resetOptions } = useOptionsListContext();
287299
const [internalOptions, setInternalOptions] = useState<OptionList>(optionsList);
288300
const prevOptions = useRef<OptionList>(null);
289301
const [areInternalOptionsInitialized, setAreInternalOptionsInitialized] = useState(false);
@@ -340,4 +352,4 @@ const useOptionsList = (options?: {shouldInitialize: boolean}) => {
340352

341353
export default OptionsListContextProvider;
342354

343-
export {useOptionsList, OptionsListContext};
355+
export { useOptionsList, OptionsListContext };

0 commit comments

Comments
 (0)