Skip to content
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
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@ const ONYXKEYS = {
/** Indicates whether we should record troubleshoot data or not */
SHOULD_RECORD_TROUBLESHOOT_DATA: 'shouldRecordTroubleshootData',

/** Timestamp when troubleshoot recording was started (for auto-off after 10 minutes) */
TROUBLESHOOT_RECORDING_START_TIME: 'troubleshootRecordingStartTime',

/** Indicates whether we should mask fragile user data while exporting onyx state or not */
SHOULD_MASK_ONYX_STATE: 'shouldMaskOnyxState',

Expand Down Expand Up @@ -1265,6 +1268,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs;
[ONYXKEYS.SHOULD_STORE_LOGS]: boolean;
[ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA]: boolean;
[ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME]: number | null;
[ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean;
[ONYXKEYS.SHOULD_USE_STAGING_SERVER]: boolean;
[ONYXKEYS.IS_DEBUG_MODE_ENABLED]: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
import type JSZip from 'jszip';
import type {RefObject} from 'react';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import React, {useEffect, useState} from 'react';
import {Alert} from 'react-native';
import RNFetchBlob from 'react-native-blob-util';
import DeviceInfo from 'react-native-device-info';
import {startProfiling, stopProfiling} from 'react-native-release-profiler';
import Button from '@components/Button';
import Switch from '@components/Switch';
import TestToolRow from '@components/TestToolRow';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {disableLoggingAndFlushLogs, setShouldStoreLogs} from '@libs/actions/Console';
import toggleProfileTool from '@libs/actions/ProfilingTool';
import {setShouldRecordTroubleshootData} from '@libs/actions/Troubleshoot';
import {cleanupAfterDisable, disableRecording, enableRecording, stopProfilingAndGetData} from '@libs/actions/Troubleshoot';
import type {ProfilingData} from '@libs/actions/Troubleshoot';
import {parseStringifiedMessages} from '@libs/Console';
import getPlatform from '@libs/getPlatform';
import Log from '@libs/Log';
import {Memoize} from '@libs/memoize';
import Performance from '@libs/Performance';
import {shouldShowProfileTool as shouldShowProfileToolUtil} from '@userActions/TestTool';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Log as OnyxLog} from '@src/types/onyx';
import pkg from '../../../package.json';
import RNFS from './RNFS';
import handleStopRecording from './handleStopRecording';
import type StopRecordingParams from './handleStopRecording.types';
import Share from './Share';

type File = {
Expand Down Expand Up @@ -88,52 +81,26 @@ function BaseRecordTroubleshootDataToolMenu({
const styles = useThemeStyles();
const [shouldRecordTroubleshootData] = useOnyx(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, {canBeMissing: true});
const [capturedLogs] = useOnyx(ONYXKEYS.LOGS, {canBeMissing: true});
const [isProfilingInProgress] = useOnyx(ONYXKEYS.APP_PROFILING_IN_PROGRESS, {canBeMissing: true});
const [shareUrls, setShareUrls] = useState<string[]>();
const [isDisabled, setIsDisabled] = useState<boolean>(false);
const [profileTracePath, setProfileTracePath] = useState<string>();

const shouldShowProfileTool = useMemo(() => shouldShowProfileToolUtil(), []);

const onToggleProfiling = useCallback(() => {
const shouldProfiling = !isProfilingInProgress;
if (shouldProfiling) {
setShareUrls(undefined);
Memoize.startMonitoring();
Performance.enableMonitoring();
startProfiling();
} else {
Performance.disableMonitoring();
}
toggleProfileTool(shouldProfiling);
return () => {
Performance.disableMonitoring();
};
}, [isProfilingInProgress]);

const getAppInfo = useCallback(() => {
return Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]).then(([totalMemory, usedMemory]) => {
return JSON.stringify({
appVersion: pkg.version,
environment: CONFIG.ENVIRONMENT,
platform: getPlatform(),
totalMemory: formatBytes(totalMemory, 2),
usedMemory: formatBytes(usedMemory, 2),
memoizeStats: Memoize.stopMonitoring(),
performance: shouldShowProfileTool ? Performance.getPerformanceMeasures() : undefined,
});
const getAppInfo = async (profilingData: ProfilingData) => {
const [totalMemory, usedMemory] = await Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]);
return JSON.stringify({
appVersion: pkg.version,
environment: CONFIG.ENVIRONMENT,
platform: getPlatform(),
totalMemory: formatBytes(totalMemory, 2),
usedMemory: formatBytes(usedMemory, 2),
memoizeStats: profilingData.memoizeStats,
performance: profilingData.performanceMeasures,
});
}, [shouldShowProfileTool]);

const onStopProfiling = useMemo(() => (shouldShowProfileTool ? stopProfiling : () => Promise.resolve()), [shouldShowProfileTool]);
};

const onToggle = () => {
if (shouldShowProfileTool) {
onToggleProfiling();
}
const onToggle = async () => {
if (!shouldRecordTroubleshootData) {
setShouldStoreLogs(true);
setShouldRecordTroubleshootData(true);
enableRecording();

if (onEnableLogging) {
onEnableLogging();
Expand All @@ -146,8 +113,7 @@ function BaseRecordTroubleshootDataToolMenu({

if (!capturedLogs) {
Alert.alert(translate('initialSettingsPage.troubleshoot.noLogsToShare'));
disableLoggingAndFlushLogs();
setShouldRecordTroubleshootData(false);
disableRecording();
return;
}

Expand All @@ -156,103 +122,30 @@ function BaseRecordTroubleshootDataToolMenu({

const infoFileName = `App_Info_${pkg.version}.json`;

if (getPlatform() === CONST.PLATFORM.WEB) {
onStopProfiling(true, newFileName).then(() => {
getAppInfo().then((appInfo) => {
zipRef.current?.file(infoFileName, appInfo);

onDisableLogging(logsWithParsedMessages).then(() => {
disableLoggingAndFlushLogs();
setShouldRecordTroubleshootData(false);
setIsDisabled(false);
onDownloadZip?.();
});
});
});
} else if (getPlatform() === CONST.PLATFORM.IOS) {
onStopProfiling(true, newFileName).then((path) => {
if (!path) {
return;
}

const newFilePath = `${pathToBeUsed}/${newFileName}`;

RNFS.exists(newFilePath)
.then((fileExists) => {
if (!fileExists) {
return;
}

return RNFS.unlink(newFilePath).then(() => {
Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
});
})
.catch((error) => {
const typedError = error as Error;
Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
})
.then(() => {
RNFS.copyFile(path, newFilePath)
.then(() => {
getAppInfo().then((appInfo) => {
zipRef.current?.file(infoFileName, appInfo);

onDisableLogging(logsWithParsedMessages).then(() => {
disableLoggingAndFlushLogs();
setShouldRecordTroubleshootData(false);
setIsDisabled(false);
onDownloadZip?.();
});
});
Log.hmmm('[ProfilingToolMenu] file copied successfully');

setProfileTracePath(newFilePath);
})
.catch((err) => {
console.error('[ProfilingToolMenu] error copying file: ', err);
});
})
.catch((error: Record<string, unknown>) => {
console.error('[ProfilingToolMenu] error copying file: ', error);
Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
});
});
} else if (getPlatform() === CONST.PLATFORM.ANDROID) {
onStopProfiling(true, newFileName).then((path) => {
if (!path) {
return;
}

RNFetchBlob.fs
// Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
.stat(!path.startsWith('content://media/') && path.match(/\/downloads\/\d+$/) ? `content://media/${path}` : path)
.then(({path: realPath}) => setProfileTracePath(realPath))
.catch(() => setProfileTracePath(path));

getAppInfo().then((appInfo) => {
zipRef.current?.file(infoFileName, appInfo);

onDisableLogging(logsWithParsedMessages).then(() => {
disableLoggingAndFlushLogs();
setShouldRecordTroubleshootData(false);
setIsDisabled(false);
});
});
});
} else {
// Desktop
onStopProfiling(true, newFileName).then(() => {
getAppInfo().then((appInfo) => {
zipRef.current?.file(infoFileName, appInfo);

onDisableLogging(logsWithParsedMessages).then(() => {
disableLoggingAndFlushLogs();
setShouldRecordTroubleshootData(false);
setIsDisabled(false);
});
});
});
try {
const profilingData = await stopProfilingAndGetData(newFileName);
const appInfo = await getAppInfo(profilingData);

const params: StopRecordingParams = {
profilingData,
infoFileName,
profileFileName: newFileName,
appInfo,
logsWithParsedMessages,
onDisableLogging,
cleanupAfterDisable,
zipRef,
pathToBeUsed,
onDownloadZip,
setProfileTracePath,
};

await handleStopRecording(params);
} catch (error) {
console.error('[ProfilingToolMenu] error handling stop recording', error);
}

setIsDisabled(false);
};

useEffect(() => {
Expand All @@ -275,7 +168,9 @@ function BaseRecordTroubleshootDataToolMenu({
<Switch
accessibilityLabel={translate('initialSettingsPage.troubleshoot.recordTroubleshootData')}
isOn={!!shouldRecordTroubleshootData}
onToggle={onToggle}
onToggle={() => {
onToggle().catch((error) => console.error('[ProfilingToolMenu] toggle failed', error));
}}
disabled={isDisabled}
/>
</TestToolRow>
Expand Down
Comment thread
pasyukevich marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type StopRecordingParams from './handleStopRecording.types';

type FinalizeStopRecordingParams = Pick<StopRecordingParams, 'infoFileName' | 'appInfo' | 'logsWithParsedMessages' | 'onDisableLogging' | 'cleanupAfterDisable' | 'zipRef' | 'onDownloadZip'>;

export default async function finalizeStopRecording({
infoFileName,
appInfo,
logsWithParsedMessages,
onDisableLogging,
cleanupAfterDisable,
zipRef,
onDownloadZip,
}: FinalizeStopRecordingParams): Promise<void> {
zipRef.current?.file(infoFileName, appInfo);

await onDisableLogging(logsWithParsedMessages);
cleanupAfterDisable();
onDownloadZip?.();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import RNFetchBlob from 'react-native-blob-util';
import finalizeStopRecording from './finalizeStopRecording';
import type StopRecordingParams from './handleStopRecording.types';

export default async function handleStopRecording({
profilingData,
infoFileName,
appInfo,
logsWithParsedMessages,
onDisableLogging,
cleanupAfterDisable,
zipRef,
onDownloadZip,
setProfileTracePath,
}: StopRecordingParams): Promise<void> {
const {profilePath} = profilingData;

if (profilePath) {
try {
// Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
const {path} = await RNFetchBlob.fs.stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath);
setProfileTracePath?.(path);
} catch {
setProfileTracePath?.(profilePath);
}
}

await finalizeStopRecording({
infoFileName,
appInfo,
logsWithParsedMessages,
onDisableLogging,
cleanupAfterDisable,
zipRef,
onDownloadZip,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import RNFS from 'react-native-fs';
import Log from '@libs/Log';
import finalizeStopRecording from './finalizeStopRecording';
import type StopRecordingParams from './handleStopRecording.types';

export default async function handleStopRecording({
profilingData,
infoFileName,
profileFileName,
appInfo,
logsWithParsedMessages,
onDisableLogging,
cleanupAfterDisable,
zipRef,
pathToBeUsed,
onDownloadZip,
setProfileTracePath,
}: StopRecordingParams): Promise<void> {
const {profilePath} = profilingData;

if (!profilePath) {
cleanupAfterDisable();
return;
}

const newFilePath = `${pathToBeUsed}/${profileFileName}`;

try {
const fileExists = await RNFS.exists(newFilePath);
if (fileExists) {
await RNFS.unlink(newFilePath);
Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', message);
}

try {
await RNFS.copyFile(profilePath, newFilePath);
await finalizeStopRecording({
infoFileName,
appInfo,
logsWithParsedMessages,
onDisableLogging,
cleanupAfterDisable,
zipRef,
onDownloadZip,
});

setProfileTracePath?.(newFilePath);
Log.hmmm('[ProfilingToolMenu] file copied successfully');
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Log.hmmm('[ProfilingToolMenu] error copying file: ', message);
}
}
Loading
Loading